Remove 2 suffix for project

Max Brunsfeld and Mikayla created

Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

Cargo.lock                              |  103 
Cargo.toml                              |    1 
crates/activity_indicator/Cargo.toml    |    2 
crates/assistant/Cargo.toml             |    4 
crates/auto_update/Cargo.toml           |    2 
crates/breadcrumbs/Cargo.toml           |    2 
crates/call2/Cargo.toml                 |    4 
crates/collab2/Cargo.toml               |    2 
crates/collab_ui/Cargo.toml             |    4 
crates/command_palette/Cargo.toml       |    4 
crates/diagnostics/Cargo.toml           |    2 
crates/editor/Cargo.toml                |    4 
crates/feedback/Cargo.toml              |    2 
crates/file_finder/Cargo.toml           |    2 
crates/language_selector/Cargo.toml     |    2 
crates/language_tools/Cargo.toml        |    2 
crates/multi_buffer/Cargo.toml          |    2 
crates/project/Cargo.toml               |   45 
crates/project/src/lsp_command.rs       |  423 
crates/project/src/lsp_ext_command.rs   |   16 
crates/project/src/prettier_support.rs  |  230 
crates/project/src/project.rs           |  336 
crates/project/src/project_settings.rs  |    7 
crates/project/src/project_tests.rs     |  474 
crates/project/src/terminals.rs         |   22 
crates/project/src/worktree.rs          |  274 
crates/project/src/worktree_tests.rs    |  110 
crates/project2/Cargo.toml              |   85 
crates/project2/src/ignore.rs           |   53 
crates/project2/src/lsp_command.rs      | 2364 -------
crates/project2/src/lsp_ext_command.rs  |  137 
crates/project2/src/prettier_support.rs |  772 --
crates/project2/src/project2.rs         | 8737 ---------------------------
crates/project2/src/project_settings.rs |   50 
crates/project2/src/project_tests.rs    | 4317 -------------
crates/project2/src/search.rs           |  463 -
crates/project2/src/terminals.rs        |  128 
crates/project2/src/worktree.rs         | 4576 --------------
crates/project2/src/worktree_tests.rs   | 2462 -------
crates/project_panel/Cargo.toml         |    2 
crates/project_symbols/Cargo.toml       |    4 
crates/search/Cargo.toml                |    2 
crates/semantic_index2/Cargo.toml       |    4 
crates/terminal_view/Cargo.toml         |    4 
crates/vim/Cargo.toml                   |    2 
crates/welcome/Cargo.toml               |    2 
crates/workspace/Cargo.toml             |    4 
crates/zed/Cargo.toml                   |    4 
48 files changed, 1,105 insertions(+), 25,148 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -12,7 +12,7 @@ dependencies = [
  "futures 0.3.28",
  "gpui2",
  "language2",
- "project2",
+ "project",
  "settings2",
  "smallvec",
  "theme2",
@@ -321,7 +321,7 @@ dependencies = [
  "multi_buffer",
  "ordered-float 2.10.0",
  "parking_lot 0.11.2",
- "project2",
+ "project",
  "rand 0.8.5",
  "regex",
  "schemars",
@@ -694,7 +694,7 @@ dependencies = [
  "lazy_static",
  "log",
  "menu2",
- "project2",
+ "project",
  "serde",
  "serde_derive",
  "serde_json",
@@ -1027,7 +1027,7 @@ dependencies = [
  "itertools 0.10.5",
  "language2",
  "outline",
- "project2",
+ "project",
  "search",
  "settings2",
  "theme2",
@@ -1157,7 +1157,7 @@ dependencies = [
  "log",
  "media",
  "postage",
- "project2",
+ "project",
  "schemars",
  "serde",
  "serde_derive",
@@ -1671,7 +1671,7 @@ dependencies = [
  "notifications2",
  "parking_lot 0.11.2",
  "pretty_assertions",
- "project2",
+ "project",
  "prometheus",
  "prost 0.8.0",
  "rand 0.8.5",
@@ -1729,7 +1729,7 @@ dependencies = [
  "picker",
  "postage",
  "pretty_assertions",
- "project2",
+ "project",
  "recent_projects",
  "rich_text",
  "rpc2",
@@ -1793,7 +1793,7 @@ dependencies = [
  "language2",
  "menu2",
  "picker",
- "project2",
+ "project",
  "serde",
  "serde_json",
  "settings2",
@@ -2442,7 +2442,7 @@ dependencies = [
  "log",
  "lsp2",
  "postage",
- "project2",
+ "project",
  "schemars",
  "serde",
  "serde_derive",
@@ -2616,7 +2616,7 @@ dependencies = [
  "ordered-float 2.10.0",
  "parking_lot 0.11.2",
  "postage",
- "project2",
+ "project",
  "rand 0.8.5",
  "rich_text",
  "rpc2",
@@ -2835,7 +2835,7 @@ dependencies = [
  "log",
  "menu2",
  "postage",
- "project2",
+ "project",
  "regex",
  "search",
  "serde",
@@ -2866,7 +2866,7 @@ dependencies = [
  "menu2",
  "picker",
  "postage",
- "project2",
+ "project",
  "serde",
  "serde_json",
  "settings2",
@@ -4307,7 +4307,7 @@ dependencies = [
  "gpui2",
  "language2",
  "picker",
- "project2",
+ "project",
  "settings2",
  "theme2",
  "ui2",
@@ -4328,7 +4328,7 @@ dependencies = [
  "gpui2",
  "language2",
  "lsp2",
- "project2",
+ "project",
  "serde",
  "settings2",
  "theme2",
@@ -4961,7 +4961,7 @@ dependencies = [
  "ordered-float 2.10.0",
  "parking_lot 0.11.2",
  "postage",
- "project2",
+ "project",
  "pulldown-cmark",
  "rand 0.8.5",
  "rich_text",
@@ -6091,61 +6091,6 @@ dependencies = [
 [[package]]
 name = "project"
 version = "0.1.0"
-dependencies = [
- "aho-corasick",
- "anyhow",
- "async-trait",
- "backtrace",
- "client",
- "clock",
- "collections",
- "copilot",
- "ctor",
- "db",
- "env_logger",
- "fs",
- "fsevent",
- "futures 0.3.28",
- "fuzzy",
- "git",
- "git2",
- "globset",
- "gpui",
- "ignore",
- "itertools 0.10.5",
- "language",
- "lazy_static",
- "log",
- "lsp",
- "node_runtime",
- "parking_lot 0.11.2",
- "postage",
- "prettier",
- "pretty_assertions",
- "rand 0.8.5",
- "regex",
- "rpc",
- "schemars",
- "serde",
- "serde_derive",
- "serde_json",
- "settings",
- "sha2 0.10.7",
- "similar",
- "smol",
- "sum_tree",
- "tempdir",
- "terminal",
- "text",
- "thiserror",
- "toml 0.5.11",
- "unindent",
- "util",
-]
-
-[[package]]
-name = "project2"
-version = "0.1.0"
 dependencies = [
  "aho-corasick",
  "anyhow",
@@ -6213,7 +6158,7 @@ dependencies = [
  "menu2",
  "postage",
  "pretty_assertions",
- "project2",
+ "project",
  "schemars",
  "search",
  "serde",
@@ -6242,7 +6187,7 @@ dependencies = [
  "ordered-float 2.10.0",
  "picker",
  "postage",
- "project2",
+ "project",
  "settings2",
  "smol",
  "text2",
@@ -7415,7 +7360,7 @@ dependencies = [
  "log",
  "menu2",
  "postage",
- "project2",
+ "project",
  "semantic_index2",
  "serde",
  "serde_derive",
@@ -7530,7 +7475,7 @@ dependencies = [
  "parking_lot 0.11.2",
  "postage",
  "pretty_assertions",
- "project2",
+ "project",
  "rand 0.8.5",
  "rpc2",
  "rusqlite",
@@ -8707,7 +8652,7 @@ dependencies = [
  "mio-extras",
  "ordered-float 2.10.0",
  "procinfo",
- "project2",
+ "project",
  "rand 0.8.5",
  "serde",
  "serde_derive",
@@ -9950,7 +9895,7 @@ dependencies = [
  "lsp2",
  "nvim-rs",
  "parking_lot 0.11.2",
- "project2",
+ "project",
  "search",
  "serde",
  "serde_derive",
@@ -10365,7 +10310,7 @@ dependencies = [
  "install_cli",
  "log",
  "picker",
- "project2",
+ "project",
  "schemars",
  "serde",
  "settings2",
@@ -10640,7 +10585,7 @@ dependencies = [
  "node_runtime",
  "parking_lot 0.11.2",
  "postage",
- "project2",
+ "project",
  "schemars",
  "serde",
  "serde_derive",
@@ -10791,7 +10736,7 @@ dependencies = [
  "outline",
  "parking_lot 0.11.2",
  "postage",
- "project2",
+ "project",
  "project_panel",
  "project_symbols",
  "quick_action_bar",

Cargo.toml 🔗

@@ -70,7 +70,6 @@ members = [
     "crates/prettier",
     "crates/prettier2",
     "crates/project",
-    "crates/project2",
     "crates/project_panel",
     "crates/project_symbols",
     "crates/quick_action_bar",

crates/activity_indicator/Cargo.toml 🔗

@@ -13,7 +13,7 @@ auto_update = { path = "../auto_update" }
 editor = { path = "../editor" }
 language = { path = "../language2", package = "language2" }
 gpui = { path = "../gpui2", package = "gpui2" }
-project = { path = "../project2", package = "project2" }
+project = { path = "../project" }
 settings = { path = "../settings2", package = "settings2" }
 ui = { path = "../ui2", package = "ui2" }
 util = { path = "../util" }

crates/assistant/Cargo.toml 🔗

@@ -18,7 +18,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 menu = { package = "menu2", path = "../menu2" }
 multi_buffer = { path = "../multi_buffer" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 search = { path = "../search" }
 semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
 settings = { package = "settings2", path = "../settings2" }
@@ -46,7 +46,7 @@ tiktoken-rs.workspace = true
 [dev-dependencies]
 ai = { path = "../ai", features = ["test-support"]}
 editor = { path = "../editor", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 
 ctor.workspace = true
 env_logger.workspace = true

crates/auto_update/Cargo.toml 🔗

@@ -13,7 +13,7 @@ db = { package = "db2", path = "../db2" }
 client = { package = "client2", path = "../client2" }
 gpui = { package = "gpui2", path = "../gpui2" }
 menu = { package = "menu2", path = "../menu2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 workspace = { path = "../workspace" }

crates/breadcrumbs/Cargo.toml 🔗

@@ -14,7 +14,7 @@ editor = { path = "../editor" }
 gpui = { package = "gpui2", path = "../gpui2" }
 ui = { package = "ui2", path = "../ui2" }
 language = { package = "language2", path = "../language2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 search = { path = "../search" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }

crates/call2/Cargo.toml 🔗

@@ -28,7 +28,7 @@ live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" }
 fs = { package = "fs2", path = "../fs2" }
 language = { package = "language2", path = "../language2" }
 media = { path = "../media" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 util = { path = "../util" }
 
@@ -50,5 +50,5 @@ language = { package = "language2", path = "../language2", features = ["test-sup
 collections = { path = "../collections", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }

crates/collab2/Cargo.toml 🔗

@@ -75,7 +75,7 @@ lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
 node_runtime = { path = "../node_runtime" }
 notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] }
 
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2" }

crates/collab_ui/Cargo.toml 🔗

@@ -40,7 +40,7 @@ menu = { package = "menu2",  path = "../menu2" }
 notifications = { package = "notifications2",  path = "../notifications2" }
 rich_text = { path = "../rich_text" }
 picker = { path = "../picker" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 recent_projects = { path = "../recent_projects" }
 rpc = { package ="rpc2",  path = "../rpc2" }
 settings = { package = "settings2", path = "../settings2" }
@@ -71,7 +71,7 @@ collections = { path = "../collections", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }

crates/command_palette/Cargo.toml 🔗

@@ -14,7 +14,7 @@ editor = { path = "../editor" }
 fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
 gpui = { package = "gpui2", path = "../gpui2" }
 picker = { path = "../picker" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
@@ -28,7 +28,7 @@ serde.workspace = true
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 language = { package="language2", path = "../language2", features = ["test-support"] }
-project = { package="project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 menu = { package = "menu2", path = "../menu2" }
 go_to_line = { path = "../go_to_line" }
 serde_json.workspace = true

crates/diagnostics/Cargo.toml 🔗

@@ -15,7 +15,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
 ui = { package = "ui2", path = "../ui2" }
 language = { package = "language2", path = "../language2" }
 lsp = { package = "lsp2", path = "../lsp2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }

crates/editor/Cargo.toml 🔗

@@ -35,7 +35,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 lsp = { package = "lsp2", path = "../lsp2" }
 multi_buffer = { path = "../multi_buffer" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 rpc = { package = "rpc2", path = "../rpc2" }
 rich_text = { path = "../rich_text" }
 settings = { package="settings2", path = "../settings2" }
@@ -78,7 +78,7 @@ language = { package="language2", path = "../language2", features = ["test-suppo
 lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
 multi_buffer = { path = "../multi_buffer", features = ["test-support"] }

crates/feedback/Cargo.toml 🔗

@@ -17,7 +17,7 @@ editor = { path = "../editor" }
 gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 menu = { package = "menu2", path = "../menu2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 search = { path = "../search" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }

crates/file_finder/Cargo.toml 🔗

@@ -15,7 +15,7 @@ fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
 gpui = { package = "gpui2", path = "../gpui2" }
 menu = { package = "menu2", path = "../menu2" }
 picker = { path = "../picker" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 text = { package = "text2", path = "../text2" }
 util = { path = "../util" }

crates/language_selector/Cargo.toml 🔗

@@ -14,7 +14,7 @@ fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
 language = { package = "language2", path = "../language2" }
 gpui = { package = "gpui2", path = "../gpui2" }
 picker = { path = "../picker" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
 settings = { package = "settings2", path = "../settings2" }

crates/language_tools/Cargo.toml 🔗

@@ -14,7 +14,7 @@ editor = { path = "../editor" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 language = { package = "language2", path = "../language2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 workspace = { path = "../workspace" }
 gpui = { package = "gpui2", path = "../gpui2" }
 ui = { package = "ui2", path = "../ui2" }

crates/multi_buffer/Cargo.toml 🔗

@@ -65,7 +65,7 @@ language = { package = "language2", path = "../language2", features = ["test-sup
 lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
 
 ctor.workspace = true

crates/project/Cargo.toml 🔗

@@ -16,28 +16,29 @@ test-support = [
     "settings/test-support",
     "text/test-support",
     "prettier/test-support",
+    "gpui/test-support",
 ]
 
 [dependencies]
-text = { path = "../text" }
+text = { package = "text2", path = "../text2" }
 copilot = { path = "../copilot" }
-client = { path = "../client" }
+client = { package = "client2", path = "../client2" }
 clock = { path = "../clock" }
 collections = { path = "../collections" }
-db = { path = "../db" }
-fs = { path = "../fs" }
+db = { package = "db2", path = "../db2" }
+fs = { package = "fs2", path = "../fs2" }
 fsevent = { path = "../fsevent" }
-fuzzy = { path = "../fuzzy" }
-git = { path = "../git" }
-gpui = { path = "../gpui" }
-language = { path = "../language" }
-lsp = { path = "../lsp" }
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
+git = { package = "git3", path = "../git3" }
+gpui = { package = "gpui2", path = "../gpui2" }
+language = { package = "language2", path = "../language2" }
+lsp = { package = "lsp2", path = "../lsp2" }
 node_runtime = { path = "../node_runtime" }
-prettier = { path = "../prettier" }
-rpc = { path = "../rpc" }
-settings = { path = "../settings" }
+prettier = { package = "prettier2", path = "../prettier2" }
+rpc = { package = "rpc2", path = "../rpc2" }
+settings = { package = "settings2", path = "../settings2" }
 sum_tree = { path = "../sum_tree" }
-terminal = { path = "../terminal" }
+terminal = { package = "terminal2", path = "../terminal2" }
 util = { path = "../util" }
 
 aho-corasick = "1.1"
@@ -68,17 +69,17 @@ itertools = "0.10"
 ctor.workspace = true
 env_logger.workspace = true
 pretty_assertions.workspace = true
-client = { path = "../client", features = ["test-support"] }
+client = { package = "client2", path = "../client2", features = ["test-support"] }
 collections = { path = "../collections", features = ["test-support"] }
-db = { path = "../db", features = ["test-support"] }
-fs = { path = "../fs",  features = ["test-support"] }
-gpui = { path = "../gpui", features = ["test-support"] }
-language = { path = "../language", features = ["test-support"] }
-lsp = { path = "../lsp", features = ["test-support"] }
-settings = { path = "../settings", features = ["test-support"] }
-prettier = { path = "../prettier", features = ["test-support"] }
+db = { package = "db2", path = "../db2", features = ["test-support"] }
+fs = { package = "fs2", path = "../fs2",  features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+language = { package = "language2", path = "../language2", features = ["test-support"] }
+lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
+settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
+prettier = { package = "prettier2", path = "../prettier2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
-rpc = { path = "../rpc", features = ["test-support"] }
+rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
 git2.workspace = true
 tempdir.workspace = true
 unindent.workspace = true

crates/project/src/lsp_command.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client::proto::{self, PeerId};
 use futures::future;
-use gpui::{AppContext, AsyncAppContext, ModelHandle};
+use gpui::{AppContext, AsyncAppContext, Model};
 use language::{
     language_settings::{language_settings, InlayHintKind},
     point_from_lsp, point_to_lsp, prepare_completion_documentation,
@@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
 }
 
 #[async_trait(?Send)]
-pub trait LspCommand: 'static + Sized {
+pub trait LspCommand: 'static + Sized + Send {
     type Response: 'static + Default + Send;
     type LspRequest: 'static + Send + lsp::request::Request;
     type ProtoRequest: 'static + Send + proto::RequestMessage;
@@ -53,8 +53,8 @@ pub trait LspCommand: 'static + Sized {
     async fn response_from_lsp(
         self,
         message: <Self::LspRequest as lsp::request::Request>::Result,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
@@ -63,8 +63,8 @@ pub trait LspCommand: 'static + Sized {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self>;
 
@@ -79,8 +79,8 @@ pub trait LspCommand: 'static + Sized {
     async fn response_from_proto(
         self,
         message: <Self::ProtoRequest as proto::RequestMessage>::Response,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
@@ -180,12 +180,12 @@ impl LspCommand for PrepareRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp::PrepareRenameResponse>,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
-        buffer.read_with(&cx, |buffer, _| {
+        buffer.update(&mut cx, |buffer, _| {
             if let Some(
                 lsp::PrepareRenameResponse::Range(range)
                 | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
@@ -199,7 +199,7 @@ impl LspCommand for PrepareRename {
                 }
             }
             Ok(None)
-        })
+        })?
     }
 
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
@@ -215,8 +215,8 @@ impl LspCommand for PrepareRename {
 
     async fn from_proto(
         message: proto::PrepareRename,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -226,11 +226,11 @@ impl LspCommand for PrepareRename {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -256,15 +256,15 @@ impl LspCommand for PrepareRename {
     async fn response_from_proto(
         self,
         message: proto::PrepareRenameResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
         if message.can_rename {
             buffer
                 .update(&mut cx, |buffer, _| {
                     buffer.wait_for_version(deserialize_version(&message.version))
-                })
+                })?
                 .await?;
             let start = message.start.and_then(deserialize_anchor);
             let end = message.end.and_then(deserialize_anchor);
@@ -307,8 +307,8 @@ impl LspCommand for PerformRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp::WorkspaceEdit>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
@@ -343,8 +343,8 @@ impl LspCommand for PerformRename {
 
     async fn from_proto(
         message: proto::PerformRename,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -354,10 +354,10 @@ impl LspCommand for PerformRename {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
             new_name: message.new_name,
             push_to_history: false,
         })
@@ -379,8 +379,8 @@ impl LspCommand for PerformRename {
     async fn response_from_proto(
         self,
         message: proto::PerformRenameResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
         let message = message
@@ -389,7 +389,7 @@ impl LspCommand for PerformRename {
         project
             .update(&mut cx, |project, cx| {
                 project.deserialize_project_transaction(message, self.push_to_history, cx)
-            })
+            })?
             .await
     }
 
@@ -426,8 +426,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp::GotoDefinitionResponse>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -447,8 +447,8 @@ impl LspCommand for GetDefinition {
 
     async fn from_proto(
         message: proto::GetDefinition,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -458,10 +458,10 @@ impl LspCommand for GetDefinition {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -479,8 +479,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetDefinitionResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -527,8 +527,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp::GotoTypeDefinitionResponse>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -548,8 +548,8 @@ impl LspCommand for GetTypeDefinition {
 
     async fn from_proto(
         message: proto::GetTypeDefinition,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -559,10 +559,10 @@ impl LspCommand for GetTypeDefinition {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -580,8 +580,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetTypeDefinitionResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -593,23 +593,23 @@ impl LspCommand for GetTypeDefinition {
 }
 
 fn language_server_for_buffer(
-    project: &ModelHandle<Project>,
-    buffer: &ModelHandle<Buffer>,
+    project: &Model<Project>,
+    buffer: &Model<Buffer>,
     server_id: LanguageServerId,
     cx: &mut AsyncAppContext,
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
     project
-        .read_with(cx, |project, cx| {
+        .update(cx, |project, cx| {
             project
                 .language_server_for_buffer(buffer.read(cx), server_id, cx)
                 .map(|(adapter, server)| (adapter.clone(), server.clone()))
-        })
+        })?
         .ok_or_else(|| anyhow!("no language server found for buffer"))
 }
 
 async fn location_links_from_proto(
     proto_links: Vec<proto::LocationLink>,
-    project: ModelHandle<Project>,
+    project: Model<Project>,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
     let mut links = Vec::new();
@@ -620,7 +620,7 @@ async fn location_links_from_proto(
                 let buffer = project
                     .update(&mut cx, |this, cx| {
                         this.wait_for_remote_buffer(origin.buffer_id, cx)
-                    })
+                    })?
                     .await?;
                 let start = origin
                     .start
@@ -631,7 +631,7 @@ async fn location_links_from_proto(
                     .and_then(deserialize_anchor)
                     .ok_or_else(|| anyhow!("missing origin end"))?;
                 buffer
-                    .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+                    .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                     .await?;
                 Some(Location {
                     buffer,
@@ -645,7 +645,7 @@ async fn location_links_from_proto(
         let buffer = project
             .update(&mut cx, |this, cx| {
                 this.wait_for_remote_buffer(target.buffer_id, cx)
-            })
+            })?
             .await?;
         let start = target
             .start
@@ -656,7 +656,7 @@ async fn location_links_from_proto(
             .and_then(deserialize_anchor)
             .ok_or_else(|| anyhow!("missing target end"))?;
         buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
             .await?;
         let target = Location {
             buffer,
@@ -671,8 +671,8 @@ async fn location_links_from_proto(
 
 async fn location_links_from_lsp(
     message: Option<lsp::GotoDefinitionResponse>,
-    project: ModelHandle<Project>,
-    buffer: ModelHandle<Buffer>,
+    project: Model<Project>,
+    buffer: Model<Buffer>,
     server_id: LanguageServerId,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
@@ -714,10 +714,10 @@ async fn location_links_from_lsp(
                     lsp_adapter.name.clone(),
                     cx,
                 )
-            })
+            })?
             .await?;
 
-        cx.read(|cx| {
+        cx.update(|cx| {
             let origin_location = origin_range.map(|origin_range| {
                 let origin_buffer = buffer.read(cx);
                 let origin_start =
@@ -746,7 +746,7 @@ async fn location_links_from_lsp(
                 origin: origin_location,
                 target: target_location,
             })
-        });
+        })?;
     }
     Ok(definitions)
 }
@@ -815,8 +815,8 @@ impl LspCommand for GetReferences {
     async fn response_from_lsp(
         self,
         locations: Option<Vec<lsp::Location>>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
@@ -834,21 +834,22 @@ impl LspCommand for GetReferences {
                             lsp_adapter.name.clone(),
                             cx,
                         )
-                    })
+                    })?
                     .await?;
 
-                cx.read(|cx| {
-                    let target_buffer = target_buffer_handle.read(cx);
-                    let target_start = target_buffer
-                        .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
-                    let target_end = target_buffer
-                        .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
-                    references.push(Location {
-                        buffer: target_buffer_handle,
-                        range: target_buffer.anchor_after(target_start)
-                            ..target_buffer.anchor_before(target_end),
-                    });
-                });
+                target_buffer_handle
+                    .clone()
+                    .update(&mut cx, |target_buffer, _| {
+                        let target_start = target_buffer
+                            .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
+                        let target_end = target_buffer
+                            .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
+                        references.push(Location {
+                            buffer: target_buffer_handle,
+                            range: target_buffer.anchor_after(target_start)
+                                ..target_buffer.anchor_before(target_end),
+                        });
+                    })?;
             }
         }
 
@@ -868,8 +869,8 @@ impl LspCommand for GetReferences {
 
     async fn from_proto(
         message: proto::GetReferences,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -879,10 +880,10 @@ impl LspCommand for GetReferences {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -910,8 +911,8 @@ impl LspCommand for GetReferences {
     async fn response_from_proto(
         self,
         message: proto::GetReferencesResponse,
-        project: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
         let mut locations = Vec::new();
@@ -919,7 +920,7 @@ impl LspCommand for GetReferences {
             let target_buffer = project
                 .update(&mut cx, |this, cx| {
                     this.wait_for_remote_buffer(location.buffer_id, cx)
-                })
+                })?
                 .await?;
             let start = location
                 .start
@@ -930,7 +931,7 @@ impl LspCommand for GetReferences {
                 .and_then(deserialize_anchor)
                 .ok_or_else(|| anyhow!("missing target end"))?;
             target_buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                 .await?;
             locations.push(Location {
                 buffer: target_buffer,
@@ -977,15 +978,15 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_lsp(
         self,
         lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
-        buffer.read_with(&cx, |buffer, _| {
+        buffer.update(&mut cx, |buffer, _| {
             let mut lsp_highlights = lsp_highlights.unwrap_or_default();
             lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end)));
-            Ok(lsp_highlights
+            lsp_highlights
                 .into_iter()
                 .map(|lsp_highlight| {
                     let start = buffer
@@ -999,7 +1000,7 @@ impl LspCommand for GetDocumentHighlights {
                             .unwrap_or(lsp::DocumentHighlightKind::READ),
                     }
                 })
-                .collect())
+                .collect()
         })
     }
 
@@ -1016,8 +1017,8 @@ impl LspCommand for GetDocumentHighlights {
 
     async fn from_proto(
         message: proto::GetDocumentHighlights,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1027,10 +1028,10 @@ impl LspCommand for GetDocumentHighlights {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -1060,8 +1061,8 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_proto(
         self,
         message: proto::GetDocumentHighlightsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
         let mut highlights = Vec::new();
@@ -1075,7 +1076,7 @@ impl LspCommand for GetDocumentHighlights {
                 .and_then(deserialize_anchor)
                 .ok_or_else(|| anyhow!("missing target end"))?;
             buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))
+                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                 .await?;
             let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
                 Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
@@ -1123,73 +1124,71 @@ impl LspCommand for GetHover {
     async fn response_from_lsp(
         self,
         message: Option<lsp::Hover>,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
-        Ok(message.and_then(|hover| {
-            let (language, range) = cx.read(|cx| {
-                let buffer = buffer.read(cx);
-                (
-                    buffer.language().cloned(),
-                    hover.range.map(|range| {
-                        let token_start =
-                            buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
-                        let token_end =
-                            buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
-                        buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
-                    }),
-                )
-            });
+        let Some(hover) = message else {
+            return Ok(None);
+        };
 
-            fn hover_blocks_from_marked_string(
-                marked_string: lsp::MarkedString,
-            ) -> Option<HoverBlock> {
-                let block = match marked_string {
-                    lsp::MarkedString::String(content) => HoverBlock {
-                        text: content,
-                        kind: HoverBlockKind::Markdown,
-                    },
-                    lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
-                        HoverBlock {
-                            text: value,
-                            kind: HoverBlockKind::Code { language },
-                        }
+        let (language, range) = buffer.update(&mut cx, |buffer, _| {
+            (
+                buffer.language().cloned(),
+                hover.range.map(|range| {
+                    let token_start =
+                        buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
+                    let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
+                    buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
+                }),
+            )
+        })?;
+
+        fn hover_blocks_from_marked_string(marked_string: lsp::MarkedString) -> Option<HoverBlock> {
+            let block = match marked_string {
+                lsp::MarkedString::String(content) => HoverBlock {
+                    text: content,
+                    kind: HoverBlockKind::Markdown,
+                },
+                lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
+                    HoverBlock {
+                        text: value,
+                        kind: HoverBlockKind::Code { language },
                     }
-                };
-                if block.text.is_empty() {
-                    None
-                } else {
-                    Some(block)
                 }
+            };
+            if block.text.is_empty() {
+                None
+            } else {
+                Some(block)
             }
+        }
 
-            let contents = cx.read(|_| match hover.contents {
-                lsp::HoverContents::Scalar(marked_string) => {
-                    hover_blocks_from_marked_string(marked_string)
-                        .into_iter()
-                        .collect()
-                }
-                lsp::HoverContents::Array(marked_strings) => marked_strings
+        let contents = match hover.contents {
+            lsp::HoverContents::Scalar(marked_string) => {
+                hover_blocks_from_marked_string(marked_string)
                     .into_iter()
-                    .filter_map(hover_blocks_from_marked_string)
-                    .collect(),
-                lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
-                    text: markup_content.value,
-                    kind: if markup_content.kind == lsp::MarkupKind::Markdown {
-                        HoverBlockKind::Markdown
-                    } else {
-                        HoverBlockKind::PlainText
-                    },
-                }],
-            });
+                    .collect()
+            }
+            lsp::HoverContents::Array(marked_strings) => marked_strings
+                .into_iter()
+                .filter_map(hover_blocks_from_marked_string)
+                .collect(),
+            lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
+                text: markup_content.value,
+                kind: if markup_content.kind == lsp::MarkupKind::Markdown {
+                    HoverBlockKind::Markdown
+                } else {
+                    HoverBlockKind::PlainText
+                },
+            }],
+        };
 
-            Some(Hover {
-                contents,
-                range,
-                language,
-            })
+        Ok(Some(Hover {
+            contents,
+            range,
+            language,
         }))
     }
 
@@ -1206,8 +1205,8 @@ impl LspCommand for GetHover {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1217,10 +1216,10 @@ impl LspCommand for GetHover {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -1272,9 +1271,9 @@ impl LspCommand for GetHover {
     async fn response_from_proto(
         self,
         message: proto::GetHoverResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
-        cx: AsyncAppContext,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
+        mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         let contents: Vec<_> = message
             .contents
@@ -1294,7 +1293,7 @@ impl LspCommand for GetHover {
             return Ok(None);
         }
 
-        let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned());
+        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
         let range = if let (Some(start), Some(end)) = (message.start, message.end) {
             language::proto::deserialize_anchor(start)
                 .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
@@ -1341,10 +1340,10 @@ impl LspCommand for GetCompletions {
     async fn response_from_lsp(
         self,
         completions: Option<lsp::CompletionResponse>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         let mut response_list = None;
         let completions = if let Some(completions) = completions {
@@ -1358,10 +1357,10 @@ impl LspCommand for GetCompletions {
                 }
             }
         } else {
-            Vec::new()
+            Default::default()
         };
 
-        let completions = buffer.read_with(&cx, |buffer, cx| {
+        let completions = buffer.update(&mut cx, |buffer, cx| {
             let language_registry = project.read(cx).languages().clone();
             let language = buffer.language().cloned();
             let snapshot = buffer.snapshot();
@@ -1371,14 +1370,6 @@ impl LspCommand for GetCompletions {
             completions
                 .into_iter()
                 .filter_map(move |mut lsp_completion| {
-                    if let Some(response_list) = &response_list {
-                        if let Some(item_defaults) = &response_list.item_defaults {
-                            if let Some(data) = &item_defaults.data {
-                                lsp_completion.data = Some(data.clone());
-                            }
-                        }
-                    }
-
                     let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
                         // If the language server provides a range to overwrite, then
                         // check that the range is valid.
@@ -1454,10 +1445,9 @@ impl LspCommand for GetCompletions {
                         }
                     };
 
-                    LineEnding::normalize(&mut new_text);
                     let language_registry = language_registry.clone();
                     let language = language.clone();
-
+                    LineEnding::normalize(&mut new_text);
                     Some(async move {
                         let mut label = None;
                         if let Some(language) = language.as_ref() {
@@ -1493,7 +1483,7 @@ impl LspCommand for GetCompletions {
                         }
                     })
                 })
-        });
+        })?;
 
         Ok(future::join_all(completions).await)
     }
@@ -1510,23 +1500,23 @@ impl LspCommand for GetCompletions {
 
     async fn from_proto(
         message: proto::GetCompletions,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let version = deserialize_version(&message.version);
         buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))
+            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))?
             .await?;
         let position = message
             .position
             .and_then(language::proto::deserialize_anchor)
             .map(|p| {
-                buffer.read_with(&cx, |buffer, _| {
+                buffer.update(&mut cx, |buffer, _| {
                     buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
                 })
             })
-            .ok_or_else(|| anyhow!("invalid position"))?;
+            .ok_or_else(|| anyhow!("invalid position"))??;
         Ok(Self { position })
     }
 
@@ -1549,17 +1539,17 @@ impl LspCommand for GetCompletions {
     async fn response_from_proto(
         self,
         message: proto::GetCompletionsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
-        let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned());
+        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
         let completions = message.completions.into_iter().map(|completion| {
             language::proto::deserialize_completion(completion, language.clone())
         });
@@ -1615,8 +1605,8 @@ impl LspCommand for GetCodeActions {
     async fn response_from_lsp(
         self,
         actions: Option<lsp::CodeActionResponse>,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         server_id: LanguageServerId,
         _: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
@@ -1649,8 +1639,8 @@ impl LspCommand for GetCodeActions {
 
     async fn from_proto(
         message: proto::GetCodeActions,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -1664,7 +1654,7 @@ impl LspCommand for GetCodeActions {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         Ok(Self { range: start..end })
@@ -1689,14 +1679,14 @@ impl LspCommand for GetCodeActions {
     async fn response_from_proto(
         self,
         message: proto::GetCodeActionsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
         message
             .actions
@@ -1752,8 +1742,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp::TextEdit>>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
@@ -1789,8 +1779,8 @@ impl LspCommand for OnTypeFormatting {
 
     async fn from_proto(
         message: proto::OnTypeFormatting,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1800,15 +1790,15 @@ impl LspCommand for OnTypeFormatting {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
-        let tab_size = buffer.read_with(&cx, |buffer, cx| {
+        let tab_size = buffer.update(&mut cx, |buffer, cx| {
             language_settings(buffer.language(), buffer.file(), cx).tab_size
-        });
+        })?;
 
         Ok(Self {
-            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
             trigger: message.trigger.clone(),
             options: lsp_formatting_options(tab_size.get()).into(),
             push_to_history: false,
@@ -1831,8 +1821,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_proto(
         self,
         message: proto::OnTypeFormattingResponse,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
         let Some(transaction) = message.transaction else {
@@ -1849,7 +1839,7 @@ impl LspCommand for OnTypeFormatting {
 impl InlayHints {
     pub async fn lsp_to_project_hint(
         lsp_hint: lsp::InlayHint,
-        buffer_handle: &ModelHandle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         server_id: LanguageServerId,
         resolve_state: ResolveState,
         force_no_type_left_padding: bool,
@@ -1861,15 +1851,14 @@ impl InlayHints {
             _ => None,
         });
 
-        let position = cx.update(|cx| {
-            let buffer = buffer_handle.read(cx);
+        let position = buffer_handle.update(cx, |buffer, _| {
             let position = buffer.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
             if kind == Some(InlayHintKind::Parameter) {
                 buffer.anchor_before(position)
             } else {
                 buffer.anchor_after(position)
             }
-        });
+        })?;
         let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id)
             .await
             .context("lsp to project inlay hint conversion")?;
@@ -2255,8 +2244,8 @@ impl LspCommand for InlayHints {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp::InlayHint>>,
-        project: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
@@ -2280,7 +2269,7 @@ impl LspCommand for InlayHints {
             };
 
             let buffer = buffer.clone();
-            cx.spawn(|mut cx| async move {
+            cx.spawn(move |mut cx| async move {
                 InlayHints::lsp_to_project_hint(
                     lsp_hint,
                     &buffer,
@@ -2311,8 +2300,8 @@ impl LspCommand for InlayHints {
 
     async fn from_proto(
         message: proto::InlayHints,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -2326,7 +2315,7 @@ impl LspCommand for InlayHints {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         Ok(Self { range: start..end })
@@ -2351,14 +2340,14 @@ impl LspCommand for InlayHints {
     async fn response_from_proto(
         self,
         message: proto::InlayHintsResponse,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
         buffer
             .update(&mut cx, |buffer, _| {
                 buffer.wait_for_version(deserialize_version(&message.version))
-            })
+            })?
             .await?;
 
         let mut hints = Vec::new();

crates/project/src/lsp_ext_command.rs 🔗

@@ -2,7 +2,7 @@ use std::{path::Path, sync::Arc};
 
 use anyhow::Context;
 use async_trait::async_trait;
-use gpui::{AppContext, AsyncAppContext, ModelHandle};
+use gpui::{AppContext, AsyncAppContext, Model};
 use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
 use lsp::{LanguageServer, LanguageServerId};
 use rpc::proto::{self, PeerId};
@@ -67,8 +67,8 @@ impl LspCommand for ExpandMacro {
     async fn response_from_lsp(
         self,
         message: Option<ExpandedMacro>,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: LanguageServerId,
         _: AsyncAppContext,
     ) -> anyhow::Result<ExpandedMacro> {
@@ -92,8 +92,8 @@ impl LspCommand for ExpandMacro {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        _: ModelHandle<Project>,
-        buffer: ModelHandle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Self> {
         let position = message
@@ -101,7 +101,7 @@ impl LspCommand for ExpandMacro {
             .and_then(deserialize_anchor)
             .context("invalid position")?;
         Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer)),
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
         })
     }
 
@@ -121,8 +121,8 @@ impl LspCommand for ExpandMacro {
     async fn response_from_proto(
         self,
         message: proto::LspExtExpandMacroResponse,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: AsyncAppContext,
     ) -> anyhow::Result<ExpandedMacro> {
         Ok(ExpandedMacro {

crates/project/src/prettier_support.rs 🔗

@@ -11,7 +11,7 @@ use futures::{
     future::{self, Shared},
     FutureExt,
 };
-use gpui::{AsyncAppContext, ModelContext, ModelHandle, Task};
+use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
 use language::{
     language_settings::{Formatter, LanguageSettings},
     Buffer, Language, LanguageServerName, LocalFile,
@@ -49,21 +49,24 @@ pub fn prettier_plugins_for_language(
 }
 
 pub(super) async fn format_with_prettier(
-    project: &ModelHandle<Project>,
-    buffer: &ModelHandle<Buffer>,
+    project: &WeakModel<Project>,
+    buffer: &Model<Buffer>,
     cx: &mut AsyncAppContext,
 ) -> Option<FormatOperation> {
     if let Some((prettier_path, prettier_task)) = project
         .update(cx, |project, cx| {
             project.prettier_instance_for_buffer(buffer, cx)
         })
+        .ok()?
         .await
     {
         match prettier_task.await {
             Ok(prettier) => {
-                let buffer_path = buffer.update(cx, |buffer, cx| {
-                    File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
-                });
+                let buffer_path = buffer
+                    .update(cx, |buffer, cx| {
+                        File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
+                    })
+                    .ok()?;
                 match prettier.format(buffer, buffer_path, cx).await {
                     Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
                     Err(e) => {
@@ -73,28 +76,30 @@ pub(super) async fn format_with_prettier(
                     }
                 }
             }
-            Err(e) => project.update(cx, |project, _| {
-                let instance_to_update = match prettier_path {
-                    Some(prettier_path) => {
-                        log::error!(
+            Err(e) => project
+                .update(cx, |project, _| {
+                    let instance_to_update = match prettier_path {
+                        Some(prettier_path) => {
+                            log::error!(
                             "Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
                         );
-                        project.prettier_instances.get_mut(&prettier_path)
-                    }
-                    None => {
-                        log::error!("Default prettier instance failed to spawn: {e:#}");
-                        match &mut project.default_prettier.prettier {
-                            PrettierInstallation::NotInstalled { .. } => None,
-                            PrettierInstallation::Installed(instance) => Some(instance),
+                            project.prettier_instances.get_mut(&prettier_path)
                         }
-                    }
-                };
+                        None => {
+                            log::error!("Default prettier instance failed to spawn: {e:#}");
+                            match &mut project.default_prettier.prettier {
+                                PrettierInstallation::NotInstalled { .. } => None,
+                                PrettierInstallation::Installed(instance) => Some(instance),
+                            }
+                        }
+                    };
 
-                if let Some(instance) = instance_to_update {
-                    instance.attempt += 1;
-                    instance.prettier = None;
-                }
-            }),
+                    if let Some(instance) = instance_to_update {
+                        instance.attempt += 1;
+                        instance.prettier = None;
+                    }
+                })
+                .ok()?,
         }
     }
 
@@ -200,7 +205,7 @@ impl PrettierInstance {
                         project
                             .update(&mut cx, |_, cx| {
                                 start_default_prettier(node, worktree_id, cx)
-                            })
+                            })?
                             .await
                     })
                 }
@@ -225,7 +230,7 @@ fn start_default_prettier(
                         ControlFlow::Break(default_prettier.clone())
                     }
                 }
-            });
+            })?;
             match installation_task {
                 ControlFlow::Continue(None) => {
                     anyhow::bail!("Default prettier is not installed and cannot be started")
@@ -243,7 +248,7 @@ fn start_default_prettier(
                                 *installation_task = None;
                                 *attempts += 1;
                             }
-                        });
+                        })?;
                         anyhow::bail!(
                             "Cannot start default prettier due to its installation failure: {e:#}"
                         );
@@ -257,7 +262,7 @@ fn start_default_prettier(
                                 prettier: Some(new_default_prettier.clone()),
                             });
                         new_default_prettier
-                    });
+                    })?;
                     return Ok(new_default_prettier);
                 }
                 ControlFlow::Break(instance) => match instance.prettier {
@@ -272,7 +277,7 @@ fn start_default_prettier(
                                     prettier: Some(new_default_prettier.clone()),
                                 });
                             new_default_prettier
-                        });
+                        })?;
                         return Ok(new_default_prettier);
                     }
                 },
@@ -291,7 +296,7 @@ fn start_prettier(
         log::info!("Starting prettier at path {prettier_dir:?}");
         let new_server_id = project.update(&mut cx, |project, _| {
             project.languages.next_language_server_id()
-        });
+        })?;
 
         let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
             .await
@@ -305,7 +310,7 @@ fn start_prettier(
 }
 
 fn register_new_prettier(
-    project: &ModelHandle<Project>,
+    project: &WeakModel<Project>,
     prettier: &Prettier,
     worktree_id: Option<WorktreeId>,
     new_server_id: LanguageServerId,
@@ -319,38 +324,40 @@ fn register_new_prettier(
         log::info!("Started prettier in {prettier_dir:?}");
     }
     if let Some(prettier_server) = prettier.server() {
-        project.update(cx, |project, cx| {
-            let name = if is_default {
-                LanguageServerName(Arc::from("prettier (default)"))
-            } else {
-                let worktree_path = worktree_id
-                    .and_then(|id| project.worktree_for_id(id, cx))
-                    .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
-                let name = match worktree_path {
-                    Some(worktree_path) => {
-                        if prettier_dir == worktree_path.as_ref() {
-                            let name = prettier_dir
-                                .file_name()
-                                .and_then(|name| name.to_str())
-                                .unwrap_or_default();
-                            format!("prettier ({name})")
-                        } else {
-                            let dir_to_display = prettier_dir
-                                .strip_prefix(worktree_path.as_ref())
-                                .ok()
-                                .unwrap_or(prettier_dir);
-                            format!("prettier ({})", dir_to_display.display())
+        project
+            .update(cx, |project, cx| {
+                let name = if is_default {
+                    LanguageServerName(Arc::from("prettier (default)"))
+                } else {
+                    let worktree_path = worktree_id
+                        .and_then(|id| project.worktree_for_id(id, cx))
+                        .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
+                    let name = match worktree_path {
+                        Some(worktree_path) => {
+                            if prettier_dir == worktree_path.as_ref() {
+                                let name = prettier_dir
+                                    .file_name()
+                                    .and_then(|name| name.to_str())
+                                    .unwrap_or_default();
+                                format!("prettier ({name})")
+                            } else {
+                                let dir_to_display = prettier_dir
+                                    .strip_prefix(worktree_path.as_ref())
+                                    .ok()
+                                    .unwrap_or(prettier_dir);
+                                format!("prettier ({})", dir_to_display.display())
+                            }
                         }
-                    }
-                    None => format!("prettier ({})", prettier_dir.display()),
+                        None => format!("prettier ({})", prettier_dir.display()),
+                    };
+                    LanguageServerName(Arc::from(name))
                 };
-                LanguageServerName(Arc::from(name))
-            };
-            project
-                .supplementary_language_servers
-                .insert(new_server_id, (name, Arc::clone(prettier_server)));
-            cx.emit(Event::LanguageServerAdded(new_server_id));
-        });
+                project
+                    .supplementary_language_servers
+                    .insert(new_server_id, (name, Arc::clone(prettier_server)));
+                cx.emit(Event::LanguageServerAdded(new_server_id));
+            })
+            .ok();
     }
 }
 
@@ -405,7 +412,7 @@ async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
 impl Project {
     pub fn update_prettier_settings(
         &self,
-        worktree: &ModelHandle<Worktree>,
+        worktree: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<'_, Project>,
     ) {
@@ -446,7 +453,7 @@ impl Project {
                     }))
                     .collect::<Vec<_>>();
 
-            cx.background()
+            cx.background_executor()
                 .spawn(async move {
                     let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
                         async move {
@@ -477,7 +484,7 @@ impl Project {
 
     fn prettier_instance_for_buffer(
         &mut self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
         let buffer = buffer.read(cx);
@@ -500,7 +507,7 @@ impl Project {
                     let installed_prettiers = self.prettier_instances.keys().cloned().collect();
                     return cx.spawn(|project, mut cx| async move {
                         match cx
-                            .background()
+                            .background_executor()
                             .spawn(async move {
                                 Prettier::locate_prettier_installation(
                                     fs.as_ref(),
@@ -515,30 +522,34 @@ impl Project {
                                 return None;
                             }
                             Ok(ControlFlow::Continue(None)) => {
-                                let default_instance = project.update(&mut cx, |project, cx| {
-                                    project
-                                        .prettiers_per_worktree
-                                        .entry(worktree_id)
-                                        .or_default()
-                                        .insert(None);
-                                    project.default_prettier.prettier_task(
-                                        &node,
-                                        Some(worktree_id),
-                                        cx,
-                                    )
-                                });
+                                let default_instance = project
+                                    .update(&mut cx, |project, cx| {
+                                        project
+                                            .prettiers_per_worktree
+                                            .entry(worktree_id)
+                                            .or_default()
+                                            .insert(None);
+                                        project.default_prettier.prettier_task(
+                                            &node,
+                                            Some(worktree_id),
+                                            cx,
+                                        )
+                                    })
+                                    .ok()?;
                                 Some((None, default_instance?.log_err().await?))
                             }
                             Ok(ControlFlow::Continue(Some(prettier_dir))) => {
-                                project.update(&mut cx, |project, _| {
-                                    project
-                                        .prettiers_per_worktree
-                                        .entry(worktree_id)
-                                        .or_default()
-                                        .insert(Some(prettier_dir.clone()))
-                                });
-                                if let Some(prettier_task) =
-                                    project.update(&mut cx, |project, cx| {
+                                project
+                                    .update(&mut cx, |project, _| {
+                                        project
+                                            .prettiers_per_worktree
+                                            .entry(worktree_id)
+                                            .or_default()
+                                            .insert(Some(prettier_dir.clone()))
+                                    })
+                                    .ok()?;
+                                if let Some(prettier_task) = project
+                                    .update(&mut cx, |project, cx| {
                                         project.prettier_instances.get_mut(&prettier_dir).map(
                                             |existing_instance| {
                                                 existing_instance.prettier_task(
@@ -550,6 +561,7 @@ impl Project {
                                             },
                                         )
                                     })
+                                    .ok()?
                                 {
                                     log::debug!(
                                         "Found already started prettier in {prettier_dir:?}"
@@ -561,22 +573,24 @@ impl Project {
                                 }
 
                                 log::info!("Found prettier in {prettier_dir:?}, starting.");
-                                let new_prettier_task = project.update(&mut cx, |project, cx| {
-                                    let new_prettier_task = start_prettier(
-                                        node,
-                                        prettier_dir.clone(),
-                                        Some(worktree_id),
-                                        cx,
-                                    );
-                                    project.prettier_instances.insert(
-                                        prettier_dir.clone(),
-                                        PrettierInstance {
-                                            attempt: 0,
-                                            prettier: Some(new_prettier_task.clone()),
-                                        },
-                                    );
-                                    new_prettier_task
-                                });
+                                let new_prettier_task = project
+                                    .update(&mut cx, |project, cx| {
+                                        let new_prettier_task = start_prettier(
+                                            node,
+                                            prettier_dir.clone(),
+                                            Some(worktree_id),
+                                            cx,
+                                        );
+                                        project.prettier_instances.insert(
+                                            prettier_dir.clone(),
+                                            PrettierInstance {
+                                                attempt: 0,
+                                                prettier: Some(new_prettier_task.clone()),
+                                            },
+                                        );
+                                        new_prettier_task
+                                    })
+                                    .ok()?;
                                 Some((Some(prettier_dir), new_prettier_task))
                             }
                             Err(e) => {
@@ -633,7 +647,7 @@ impl Project {
         }) {
             Some(locate_from) => {
                 let installed_prettiers = self.prettier_instances.keys().cloned().collect();
-                cx.background().spawn(async move {
+                cx.background_executor().spawn(async move {
                     Prettier::locate_prettier_installation(
                         fs.as_ref(),
                         &installed_prettiers,
@@ -696,7 +710,7 @@ impl Project {
                                         installation_attempt = *attempts;
                                         needs_install = true;
                                     };
-                                });
+                                })?;
                             }
                         };
                         if installation_attempt > prettier::FAIL_THRESHOLD {
@@ -704,7 +718,7 @@ impl Project {
                                 if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut project.default_prettier.prettier {
                                     *installation_task = None;
                                 };
-                            });
+                            })?;
                             log::warn!(
                                 "Default prettier installation had failed {installation_attempt} times, not attempting again",
                             );
@@ -721,10 +735,10 @@ impl Project {
                                 not_installed_plugins.extend(new_plugins.iter());
                             }
                             needs_install |= !new_plugins.is_empty();
-                        });
+                        })?;
                         if needs_install {
                             let installed_plugins = new_plugins.clone();
-                            cx.background()
+                            cx.background_executor()
                                 .spawn(async move {
                                     save_prettier_server_file(fs.as_ref()).await?;
                                     install_prettier_packages(new_plugins, node).await
@@ -742,7 +756,7 @@ impl Project {
                                 project.default_prettier
                                     .installed_plugins
                                     .extend(installed_plugins);
-                            });
+                            })?;
                         }
                     }
                 }

crates/project/src/project.rs 🔗

@@ -12,7 +12,7 @@ mod project_tests;
 #[cfg(test)]
 mod worktree_tests;
 
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
 use clock::ReplicaId;
 use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
@@ -28,8 +28,8 @@ use futures::{
 };
 use globset::{Glob, GlobSet, GlobSetBuilder};
 use gpui::{
-    executor::Background, AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity,
-    ModelContext, ModelHandle, Task, WeakModelHandle,
+    AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
+    Model, ModelContext, Task, WeakModel,
 };
 use itertools::Itertools;
 use language::{
@@ -59,13 +59,11 @@ use project_settings::{LspSettings, ProjectSettings};
 use rand::prelude::*;
 use search::SearchQuery;
 use serde::Serialize;
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use sha2::{Digest, Sha256};
 use similar::{ChangeTag, TextDiff};
-use smol::{
-    channel::{Receiver, Sender},
-    lock::Semaphore,
-};
+use smol::channel::{Receiver, Sender};
+use smol::lock::Semaphore;
 use std::{
     cmp::{self, Ordering},
     convert::TryInto,
@@ -130,7 +128,7 @@ pub struct Project {
     next_entry_id: Arc<AtomicUsize>,
     join_project_response_message_id: u32,
     next_diagnostic_group_id: usize,
-    user_store: ModelHandle<UserStore>,
+    user_store: Model<UserStore>,
     fs: Arc<dyn Fs>,
     client_state: Option<ProjectClientState>,
     collaborators: HashMap<proto::PeerId, Collaborator>,
@@ -142,24 +140,24 @@ pub struct Project {
     #[allow(clippy::type_complexity)]
     loading_buffers_by_path: HashMap<
         ProjectPath,
-        postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
+        postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
     >,
     #[allow(clippy::type_complexity)]
     loading_local_worktrees:
-        HashMap<Arc<Path>, Shared<Task<Result<ModelHandle<Worktree>, Arc<anyhow::Error>>>>>,
+        HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
     opened_buffers: HashMap<u64, OpenBuffer>,
     local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
     local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
     /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
     /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>,
+    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
     buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     buffers_being_formatted: HashSet<u64>,
-    buffers_needing_diff: HashSet<WeakModelHandle<Buffer>>,
+    buffers_needing_diff: HashSet<WeakModel<Buffer>>,
     git_diff_debouncer: DelayedDebounced,
     nonce: u128,
     _maintain_buffer_languages: Task<()>,
-    _maintain_workspace_config: Task<()>,
+    _maintain_workspace_config: Task<Result<()>>,
     terminals: Terminals,
     copilot_lsp_subscription: Option<gpui::Subscription>,
     copilot_log_subscription: Option<lsp::Subscription>,
@@ -190,7 +188,7 @@ impl DelayedDebounced {
 
     fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
     where
-        F: 'static + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
+        F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
     {
         if let Some(channel) = self.cancel_channel.take() {
             _ = channel.send(());
@@ -200,8 +198,8 @@ impl DelayedDebounced {
         self.cancel_channel = Some(sender);
 
         let previous_task = self.task.take();
-        self.task = Some(cx.spawn(|workspace, mut cx| async move {
-            let mut timer = cx.background().timer(delay).fuse();
+        self.task = Some(cx.spawn(move |project, mut cx| async move {
+            let mut timer = cx.background_executor().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
             }
@@ -211,9 +209,9 @@ impl DelayedDebounced {
                     _ = timer => {}
             }
 
-            workspace
-                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
-                .await;
+            if let Ok(task) = project.update(&mut cx, |project, cx| (func)(project, cx)) {
+                task.await;
+            }
         }));
     }
 }
@@ -245,22 +243,22 @@ enum LocalProjectUpdate {
 }
 
 enum OpenBuffer {
-    Strong(ModelHandle<Buffer>),
-    Weak(WeakModelHandle<Buffer>),
+    Strong(Model<Buffer>),
+    Weak(WeakModel<Buffer>),
     Operations(Vec<Operation>),
 }
 
 #[derive(Clone)]
 enum WorktreeHandle {
-    Strong(ModelHandle<Worktree>),
-    Weak(WeakModelHandle<Worktree>),
+    Strong(Model<Worktree>),
+    Weak(WeakModel<Worktree>),
 }
 
 enum ProjectClientState {
     Local {
         remote_id: u64,
         updates_tx: mpsc::UnboundedSender<LocalProjectUpdate>,
-        _send_updates: Task<()>,
+        _send_updates: Task<Result<()>>,
     },
     Remote {
         sharing_has_stopped: bool,
@@ -346,7 +344,7 @@ pub struct DiagnosticSummary {
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
-    pub buffer: ModelHandle<Buffer>,
+    pub buffer: Model<Buffer>,
     pub range: Range<language::Anchor>,
 }
 
@@ -459,7 +457,7 @@ impl Hover {
 }
 
 #[derive(Default)]
-pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
+pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
 
 impl DiagnosticSummary {
     fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
@@ -529,7 +527,7 @@ pub enum FormatTrigger {
 }
 
 struct ProjectLspAdapterDelegate {
-    project: ModelHandle<Project>,
+    project: Model<Project>,
     http_client: Arc<dyn HttpClient>,
 }
 
@@ -553,7 +551,7 @@ impl FormatTrigger {
 #[derive(Clone, Debug, PartialEq)]
 enum SearchMatchCandidate {
     OpenBuffer {
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         // This might be an unnamed file without representation on filesystem
         path: Option<Arc<Path>>,
     },
@@ -576,7 +574,7 @@ impl SearchMatchCandidate {
 
 impl Project {
     pub fn init_settings(cx: &mut AppContext) {
-        settings::register::<ProjectSettings>(cx);
+        ProjectSettings::register(cx);
     }
 
     pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
@@ -606,7 +604,6 @@ impl Project {
         client.add_model_request_handler(Self::handle_apply_code_action);
         client.add_model_request_handler(Self::handle_on_type_formatting);
         client.add_model_request_handler(Self::handle_inlay_hints);
-        client.add_model_request_handler(Self::handle_resolve_completion_documentation);
         client.add_model_request_handler(Self::handle_resolve_inlay_hint);
         client.add_model_request_handler(Self::handle_refresh_inlay_hints);
         client.add_model_request_handler(Self::handle_reload_buffers);
@@ -634,14 +631,14 @@ impl Project {
     pub fn local(
         client: Arc<Client>,
         node: Arc<dyn NodeRuntime>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut AppContext,
-    ) -> ModelHandle<Self> {
-        cx.add_model(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.new_model(|cx: &mut ModelContext<Self>| {
             let (tx, rx) = mpsc::unbounded();
-            cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
+            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
             let copilot_lsp_subscription =
                 Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
@@ -663,7 +660,9 @@ impl Project {
                 opened_buffer: watch::channel(),
                 client_subscriptions: Vec::new(),
                 _subscriptions: vec![
-                    cx.observe_global::<SettingsStore, _>(Self::on_settings_changed)
+                    cx.observe_global::<SettingsStore>(Self::on_settings_changed),
+                    cx.on_release(Self::release),
+                    cx.on_app_quit(Self::shutdown_language_servers),
                 ],
                 _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
                 _maintain_workspace_config: Self::maintain_workspace_config(cx),
@@ -688,7 +687,7 @@ impl Project {
                 },
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
-                current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
+                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
                 node: Some(node),
                 default_prettier: DefaultPrettier::default(),
                 prettiers_per_worktree: HashMap::default(),
@@ -700,11 +699,11 @@ impl Project {
     pub async fn remote(
         remote_id: u64,
         client: Arc<Client>,
-        user_store: ModelHandle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         mut cx: AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         client.authenticate_and_connect(true, &cx).await?;
 
         let subscription = client.subscribe_to_entity(remote_id)?;
@@ -713,19 +712,18 @@ impl Project {
                 project_id: remote_id,
             })
             .await?;
-        let this = cx.add_model(|cx| {
+        let this = cx.new_model(|cx| {
             let replica_id = response.payload.replica_id as ReplicaId;
 
             let mut worktrees = Vec::new();
             for worktree in response.payload.worktrees {
-                let worktree = cx.update(|cx| {
-                    Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx)
-                });
+                let worktree =
+                    Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
                 worktrees.push(worktree);
             }
 
             let (tx, rx) = mpsc::unbounded();
-            cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
+            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
             let copilot_lsp_subscription =
                 Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
@@ -751,7 +749,10 @@ impl Project {
                 next_entry_id: Default::default(),
                 next_diagnostic_group_id: Default::default(),
                 client_subscriptions: Default::default(),
-                _subscriptions: Default::default(),
+                _subscriptions: vec![
+                    cx.on_release(Self::release),
+                    cx.on_app_quit(Self::shutdown_language_servers),
+                ],
                 client: client.clone(),
                 client_state: Some(ProjectClientState::Remote {
                     sharing_has_stopped: false,
@@ -789,7 +790,7 @@ impl Project {
                 },
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
-                current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
+                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
                 node: None,
                 default_prettier: DefaultPrettier::default(),
                 prettiers_per_worktree: HashMap::default(),
@@ -799,7 +800,7 @@ impl Project {
                 let _ = this.add_worktree(&worktree, cx);
             }
             this
-        });
+        })?;
         let subscription = subscription.set_model(&this, &mut cx);
 
         let user_ids = response
@@ -809,29 +810,65 @@ impl Project {
             .map(|peer| peer.user_id)
             .collect();
         user_store
-            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
+            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
             .await?;
 
         this.update(&mut cx, |this, cx| {
             this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
             this.client_subscriptions.push(subscription);
             anyhow::Ok(())
-        })?;
+        })??;
 
         Ok(this)
     }
 
+    fn release(&mut self, cx: &mut AppContext) {
+        match &self.client_state {
+            Some(ProjectClientState::Local { .. }) => {
+                let _ = self.unshare_internal(cx);
+            }
+            Some(ProjectClientState::Remote { remote_id, .. }) => {
+                let _ = self.client.send(proto::LeaveProject {
+                    project_id: *remote_id,
+                });
+                self.disconnected_from_host_internal(cx);
+            }
+            _ => {}
+        }
+    }
+
+    fn shutdown_language_servers(
+        &mut self,
+        _cx: &mut ModelContext<Self>,
+    ) -> impl Future<Output = ()> {
+        let shutdown_futures = self
+            .language_servers
+            .drain()
+            .map(|(_, server_state)| async {
+                use LanguageServerState::*;
+                match server_state {
+                    Running { server, .. } => server.shutdown()?.await,
+                    Starting(task) => task.await?.shutdown()?.await,
+                }
+            })
+            .collect::<Vec<_>>();
+
+        async move {
+            futures::future::join_all(shutdown_futures).await;
+        }
+    }
+
     #[cfg(any(test, feature = "test-support"))]
     pub async fn test(
         fs: Arc<dyn Fs>,
         root_paths: impl IntoIterator<Item = &Path>,
         cx: &mut gpui::TestAppContext,
-    ) -> ModelHandle<Project> {
+    ) -> Model<Project> {
         let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.background());
+        languages.set_executor(cx.executor());
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
-        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
+        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
         let project = cx.update(|cx| {
             Project::local(
                 client,
@@ -849,7 +886,7 @@ impl Project {
                 })
                 .await
                 .unwrap();
-            tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
+            tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
                 .await;
         }
         project
@@ -859,7 +896,7 @@ impl Project {
         let mut language_servers_to_start = Vec::new();
         let mut language_formatters_to_check = Vec::new();
         for buffer in self.opened_buffers.values() {
-            if let Some(buffer) = buffer.upgrade(cx) {
+            if let Some(buffer) = buffer.upgrade() {
                 let buffer = buffer.read(cx);
                 let buffer_file = File::from_dyn(buffer.file());
                 let buffer_language = buffer.language();
@@ -884,7 +921,7 @@ impl Project {
         let mut language_servers_to_restart = Vec::new();
         let languages = self.languages.to_vec();
 
-        let new_lsp_settings = settings::get::<ProjectSettings>(cx).lsp.clone();
+        let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
         let current_lsp_settings = &self.current_lsp_settings;
         for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
             let language = languages.iter().find_map(|l| {
@@ -957,7 +994,7 @@ impl Project {
         if self.copilot_lsp_subscription.is_none() {
             if let Some(copilot) = Copilot::global(cx) {
                 for buffer in self.opened_buffers.values() {
-                    if let Some(buffer) = buffer.upgrade(cx) {
+                    if let Some(buffer) = buffer.upgrade() {
                         self.register_buffer_with_copilot(&buffer, cx);
                     }
                 }
@@ -968,10 +1005,10 @@ impl Project {
         cx.notify();
     }
 
-    pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
+    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
         self.opened_buffers
             .get(&remote_id)
-            .and_then(|buffer| buffer.upgrade(cx))
+            .and_then(|buffer| buffer.upgrade())
     }
 
     pub fn languages(&self) -> &Arc<LanguageRegistry> {
@@ -982,14 +1019,14 @@ impl Project {
         self.client.clone()
     }
 
-    pub fn user_store(&self) -> ModelHandle<UserStore> {
+    pub fn user_store(&self) -> Model<UserStore> {
         self.user_store.clone()
     }
 
-    pub fn opened_buffers(&self, cx: &AppContext) -> Vec<ModelHandle<Buffer>> {
+    pub fn opened_buffers(&self) -> Vec<Model<Buffer>> {
         self.opened_buffers
             .values()
-            .filter_map(|b| b.upgrade(cx))
+            .filter_map(|b| b.upgrade())
             .collect()
     }
 
@@ -998,7 +1035,7 @@ impl Project {
         let path = path.into();
         if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
             self.opened_buffers.iter().any(|(_, buffer)| {
-                if let Some(buffer) = buffer.upgrade(cx) {
+                if let Some(buffer) = buffer.upgrade() {
                     if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
                         if file.worktree == worktree && file.path() == &path.path {
                             return true;
@@ -1048,22 +1085,19 @@ impl Project {
     }
 
     /// Collect all worktrees, including ones that don't appear in the project panel
-    pub fn worktrees<'a>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = ModelHandle<Worktree>> {
+    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees
             .iter()
-            .filter_map(move |worktree| worktree.upgrade(cx))
+            .filter_map(move |worktree| worktree.upgrade())
     }
 
     /// Collect all user-visible worktrees, the ones that appear in the project panel
     pub fn visible_worktrees<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = ModelHandle<Worktree>> {
+    ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees.iter().filter_map(|worktree| {
-            worktree.upgrade(cx).and_then(|worktree| {
+            worktree.upgrade().and_then(|worktree| {
                 if worktree.read(cx).is_visible() {
                     Some(worktree)
                 } else {
@@ -1078,12 +1112,8 @@ impl Project {
             .map(|tree| tree.read(cx).root_name())
     }
 
-    pub fn worktree_for_id(
-        &self,
-        id: WorktreeId,
-        cx: &AppContext,
-    ) -> Option<ModelHandle<Worktree>> {
-        self.worktrees(cx)
+    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
+        self.worktrees()
             .find(|worktree| worktree.read(cx).id() == id)
     }
 
@@ -1091,8 +1121,8 @@ impl Project {
         &self,
         entry_id: ProjectEntryId,
         cx: &AppContext,
-    ) -> Option<ModelHandle<Worktree>> {
-        self.worktrees(cx)
+    ) -> Option<Model<Worktree>> {
+        self.worktrees()
             .find(|worktree| worktree.read(cx).contains_entry(entry_id))
     }
 
@@ -1110,7 +1140,7 @@ impl Project {
     }
 
     pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
-        for worktree in self.worktrees(cx) {
+        for worktree in self.worktrees() {
             let worktree = worktree.read(cx).as_local();
             if worktree.map_or(false, |w| w.contains_abs_path(path)) {
                 return true;
@@ -1139,7 +1169,7 @@ impl Project {
         } else {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::CreateProjectEntry {
                         worktree_id: project_path.worktree_id.to_proto(),
@@ -1156,7 +1186,7 @@ impl Project {
                                 response.worktree_scan_id as usize,
                                 cx,
                             )
-                        })
+                        })?
                         .await
                         .map(Some),
                     None => Ok(None),
@@ -1186,7 +1216,7 @@ impl Project {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
 
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::CopyProjectEntry {
                         project_id,
@@ -1202,7 +1232,7 @@ impl Project {
                                 response.worktree_scan_id as usize,
                                 cx,
                             )
-                        })
+                        })?
                         .await
                         .map(Some),
                     None => Ok(None),
@@ -1232,7 +1262,7 @@ impl Project {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
 
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::RenameProjectEntry {
                         project_id,
@@ -1248,7 +1278,7 @@ impl Project {
                                 response.worktree_scan_id as usize,
                                 cx,
                             )
-                        })
+                        })?
                         .await
                         .map(Some),
                     None => Ok(None),
@@ -1273,7 +1303,7 @@ impl Project {
         } else {
             let client = self.client.clone();
             let project_id = self.remote_id().unwrap();
-            Some(cx.spawn_weak(|_, mut cx| async move {
+            Some(cx.spawn(move |_, mut cx| async move {
                 let response = client
                     .request(proto::DeleteProjectEntry {
                         project_id,
@@ -1287,7 +1317,7 @@ impl Project {
                             response.worktree_scan_id as usize,
                             cx,
                         )
-                    })
+                    })?
                     .await
             }))
         }
@@ -1310,16 +1340,16 @@ impl Project {
                 project_id: self.remote_id().unwrap(),
                 entry_id: entry_id.to_proto(),
             });
-            Some(cx.spawn_weak(|_, mut cx| async move {
+            Some(cx.spawn(move |_, mut cx| async move {
                 let response = request.await?;
-                if let Some(worktree) = worktree.upgrade(&cx) {
+                if let Some(worktree) = worktree.upgrade() {
                     worktree
                         .update(&mut cx, |worktree, _| {
                             worktree
                                 .as_remote_mut()
                                 .unwrap()
                                 .wait_for_snapshot(response.worktree_scan_id as usize)
-                        })
+                        })?
                         .await?;
                 }
                 Ok(())
@@ -1341,7 +1371,7 @@ impl Project {
             match open_buffer {
                 OpenBuffer::Strong(_) => {}
                 OpenBuffer::Weak(buffer) => {
-                    if let Some(buffer) = buffer.upgrade(cx) {
+                    if let Some(buffer) = buffer.upgrade() {
                         *open_buffer = OpenBuffer::Strong(buffer);
                     }
                 }
@@ -1353,7 +1383,7 @@ impl Project {
             match worktree_handle {
                 WorktreeHandle::Strong(_) => {}
                 WorktreeHandle::Weak(worktree) => {
-                    if let Some(worktree) = worktree.upgrade(cx) {
+                    if let Some(worktree) = worktree.upgrade() {
                         *worktree_handle = WorktreeHandle::Strong(worktree);
                     }
                 }
@@ -1373,9 +1403,9 @@ impl Project {
         }
 
         let store = cx.global::<SettingsStore>();
-        for worktree in self.worktrees(cx) {
+        for worktree in self.worktrees() {
             let worktree_id = worktree.read(cx).id().to_proto();
-            for (path, content) in store.local_settings(worktree.id()) {
+            for (path, content) in store.local_settings(worktree.entity_id().as_u64() as usize) {
                 self.client
                     .send(proto::UpdateWorktreeSettings {
                         project_id,
@@ -1392,28 +1422,27 @@ impl Project {
         self.client_state = Some(ProjectClientState::Local {
             remote_id: project_id,
             updates_tx,
-            _send_updates: cx.spawn_weak(move |this, mut cx| async move {
+            _send_updates: cx.spawn(move |this, mut cx| async move {
                 while let Some(update) = updates_rx.next().await {
-                    let Some(this) = this.upgrade(&cx) else { break };
-
                     match update {
                         LocalProjectUpdate::WorktreesChanged => {
-                            let worktrees = this
-                                .read_with(&cx, |this, cx| this.worktrees(cx).collect::<Vec<_>>());
+                            let worktrees = this.update(&mut cx, |this, _cx| {
+                                this.worktrees().collect::<Vec<_>>()
+                            })?;
                             let update_project = this
-                                .read_with(&cx, |this, cx| {
+                                .update(&mut cx, |this, cx| {
                                     this.client.request(proto::UpdateProject {
                                         project_id,
                                         worktrees: this.worktree_metadata_protos(cx),
                                     })
-                                })
+                                })?
                                 .await;
                             if update_project.is_ok() {
                                 for worktree in worktrees {
                                     worktree.update(&mut cx, |worktree, cx| {
                                         let worktree = worktree.as_local_mut().unwrap();
                                         worktree.share(project_id, cx).detach_and_log_err(cx)
-                                    });
+                                    })?;
                                 }
                             }
                         }
@@ -1431,13 +1460,13 @@ impl Project {
                                 } else {
                                     None
                                 }
-                            });
+                            })?;
 
                             let Some(buffer) = buffer else { continue };
                             let operations =
-                                buffer.read_with(&cx, |b, cx| b.serialize_ops(None, cx));
+                                buffer.update(&mut cx, |b, cx| b.serialize_ops(None, cx))?;
                             let operations = operations.await;
-                            let state = buffer.read_with(&cx, |buffer, _| buffer.to_proto());
+                            let state = buffer.update(&mut cx, |buffer, _| buffer.to_proto())?;
 
                             let initial_state = proto::CreateBufferForPeer {
                                 project_id,
@@ -1446,7 +1475,7 @@ impl Project {
                             };
                             if client.send(initial_state).log_err().is_some() {
                                 let client = client.clone();
-                                cx.background()
+                                cx.background_executor()
                                     .spawn(async move {
                                         let mut chunks = split_operations(operations).peekable();
                                         while let Some(chunk) = chunks.next() {
@@ -1473,6 +1502,7 @@ impl Project {
                         }
                     }
                 }
+                Ok(())
             }),
         });
 
@@ -1499,7 +1529,7 @@ impl Project {
         message_id: u32,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             for worktree in &self.worktrees {
                 store
                     .clear_local_settings(worktree.handle_id(), cx)
@@ -1563,7 +1593,7 @@ impl Project {
 
             for open_buffer in self.opened_buffers.values_mut() {
                 // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade(cx) {
+                if let Some(buffer) = open_buffer.upgrade() {
                     buffer.update(cx, |buffer, _| buffer.give_up_waiting());
                 }
 
@@ -1599,7 +1629,7 @@ impl Project {
             self.collaborators.clear();
 
             for worktree in &self.worktrees {
-                if let Some(worktree) = worktree.upgrade(cx) {
+                if let Some(worktree) = worktree.upgrade() {
                     worktree.update(cx, |worktree, _| {
                         if let Some(worktree) = worktree.as_remote_mut() {
                             worktree.disconnected_from_host();
@@ -1610,7 +1640,7 @@ impl Project {
 
             for open_buffer in self.opened_buffers.values_mut() {
                 // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade(cx) {
+                if let Some(buffer) = open_buffer.upgrade() {
                     buffer.update(cx, |buffer, _| buffer.give_up_waiting());
                 }
 
@@ -1655,12 +1685,12 @@ impl Project {
         text: &str,
         language: Option<Arc<Language>>,
         cx: &mut ModelContext<Self>,
-    ) -> Result<ModelHandle<Buffer>> {
+    ) -> Result<Model<Buffer>> {
         if self.is_remote() {
             return Err(anyhow!("creating buffers as a guest is not supported yet"));
         }
         let id = post_inc(&mut self.next_buffer_id);
-        let buffer = cx.add_model(|cx| {
+        let buffer = cx.new_model(|cx| {
             Buffer::new(self.replica_id(), id, text)
                 .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
         });
@@ -1672,14 +1702,15 @@ impl Project {
         &mut self,
         path: ProjectPath,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Option<ProjectEntryId>, AnyModelHandle)>> {
+    ) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
         let task = self.open_buffer(path.clone(), cx);
-        cx.spawn_weak(|_, cx| async move {
+        cx.spawn(move |_, cx| async move {
             let buffer = task.await?;
             let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
                 File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
-            });
-            let buffer: &AnyModelHandle = &buffer;
+            })?;
+
+            let buffer: &AnyModel = &buffer;
             Ok((project_entry_id, buffer.clone()))
         })
     }
@@ -1688,7 +1719,7 @@ impl Project {
         &mut self,
         abs_path: impl AsRef<Path>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
             self.open_buffer((worktree.read(cx).id(), relative_path), cx)
         } else {
@@ -1700,7 +1731,7 @@ impl Project {
         &mut self,
         path: impl Into<ProjectPath>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let project_path = path.into();
         let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
             worktree
@@ -1738,14 +1769,15 @@ impl Project {
                         this.loading_buffers_by_path.remove(&project_path);
                         let buffer = load_result.map_err(Arc::new)?;
                         Ok(buffer)
-                    }));
+                    })?);
+                    anyhow::Ok(())
                 })
                 .detach();
                 rx
             }
         };
 
-        cx.foreground().spawn(async move {
+        cx.background_executor().spawn(async move {
             wait_for_loading_buffer(loading_watch)
                 .await
                 .map_err(|error| anyhow!("{project_path:?} opening failure: {error:#}"))
@@ -1755,17 +1787,17 @@ impl Project {
     fn open_local_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &ModelHandle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let buffer_id = post_inc(&mut self.next_buffer_id);
         let load_buffer = worktree.update(cx, |worktree, cx| {
             let worktree = worktree.as_local_mut().unwrap();
             worktree.load_buffer(buffer_id, path, cx)
         });
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let buffer = load_buffer.await?;
-            this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))?;
+            this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))??;
             Ok(buffer)
         })
     }
@@ -1773,15 +1805,15 @@ impl Project {
     fn open_remote_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &ModelHandle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let rpc = self.client.clone();
         let project_id = self.remote_id().unwrap();
         let remote_worktree_id = worktree.read(cx).id();
         let path = path.clone();
         let path_string = path.to_string_lossy().to_string();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let response = rpc
                 .request(proto::OpenBufferByPath {
                     project_id,
@@ -1791,7 +1823,7 @@ impl Project {
                 .await?;
             this.update(&mut cx, |this, cx| {
                 this.wait_for_remote_buffer(response.buffer_id, cx)
-            })
+            })?
             .await
         })
     }
@@ -1803,35 +1835,36 @@ impl Project {
         language_server_id: LanguageServerId,
         language_server_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
-        cx.spawn(|this, mut cx| async move {
+    ) -> Task<Result<Model<Buffer>>> {
+        cx.spawn(move |this, mut cx| async move {
             let abs_path = abs_path
                 .to_file_path()
                 .map_err(|_| anyhow!("can't convert URI to path"))?;
             let (worktree, relative_path) = if let Some(result) =
-                this.read_with(&cx, |this, cx| this.find_local_worktree(&abs_path, cx))
+                this.update(&mut cx, |this, cx| this.find_local_worktree(&abs_path, cx))?
             {
                 result
             } else {
                 let worktree = this
                     .update(&mut cx, |this, cx| {
                         this.create_local_worktree(&abs_path, false, cx)
-                    })
+                    })?
                     .await?;
                 this.update(&mut cx, |this, cx| {
                     this.language_server_ids.insert(
                         (worktree.read(cx).id(), language_server_name),
                         language_server_id,
                     );
-                });
+                })
+                .ok();
                 (worktree, PathBuf::new())
             };
 
             let project_path = ProjectPath {
-                worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
+                worktree_id: worktree.update(&mut cx, |worktree, _| worktree.id())?,
                 path: relative_path.into(),
             };
-            this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
+            this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))?
                 .await
         })
     }
@@ -1840,8 +1873,8 @@ impl Project {
         &mut self,
         id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
-        if let Some(buffer) = self.buffer_for_id(id, cx) {
+    ) -> Task<Result<Model<Buffer>>> {
+        if let Some(buffer) = self.buffer_for_id(id) {
             Task::ready(Ok(buffer))
         } else if self.is_local() {
             Task::ready(Err(anyhow!("buffer {} does not exist", id)))
@@ -1849,11 +1882,11 @@ impl Project {
             let request = self
                 .client
                 .request(proto::OpenBufferById { project_id, id });
-            cx.spawn(|this, mut cx| async move {
+            cx.spawn(move |this, mut cx| async move {
                 let buffer_id = request.await?.buffer_id;
                 this.update(&mut cx, |this, cx| {
                     this.wait_for_remote_buffer(buffer_id, cx)
-                })
+                })?
                 .await
             })
         } else {
@@ -1863,13 +1896,14 @@ impl Project {
 
     pub fn save_buffers(
         &self,
-        buffers: HashSet<ModelHandle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
-        cx.spawn(|this, mut cx| async move {
-            let save_tasks = buffers
-                .into_iter()
-                .map(|buffer| this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx)));
+        cx.spawn(move |this, mut cx| async move {
+            let save_tasks = buffers.into_iter().filter_map(|buffer| {
+                this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
+                    .ok()
+            });
             try_join_all(save_tasks).await?;
             Ok(())
         })
@@ -1877,7 +1911,7 @@ impl Project {
 
     pub fn save_buffer(
         &self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
@@ -1893,7 +1927,7 @@ impl Project {
 
     pub fn save_buffer_as(
         &mut self,
-        buffer: ModelHandle<Buffer>,
+        buffer: Model<Buffer>,
         abs_path: PathBuf,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
@@ -1901,11 +1935,11 @@ impl Project {
         let old_file = File::from_dyn(buffer.read(cx).file())
             .filter(|f| f.is_local())
             .cloned();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             if let Some(old_file) = &old_file {
                 this.update(&mut cx, |this, cx| {
                     this.unregister_buffer_from_language_servers(&buffer, old_file, cx);
-                });
+                })?;
             }
             let (worktree, path) = worktree_task.await?;
             worktree
@@ -1914,13 +1948,13 @@ impl Project {
                         worktree.save_buffer(buffer.clone(), path.into(), true, cx)
                     }
                     Worktree::Remote(_) => panic!("cannot remote buffers as new files"),
-                })
+                })?
                 .await?;
 
             this.update(&mut cx, |this, cx| {
                 this.detect_language_for_buffer(&buffer, cx);
                 this.register_buffer_with_language_servers(&buffer, cx);
-            });
+            })?;
             Ok(())
         })
     }
@@ -1929,10 +1963,10 @@ impl Project {
         &mut self,
         path: &ProjectPath,
         cx: &mut ModelContext<Self>,
-    ) -> Option<ModelHandle<Buffer>> {
+    ) -> Option<Model<Buffer>> {
         let worktree = self.worktree_for_id(path.worktree_id, cx)?;
         self.opened_buffers.values().find_map(|buffer| {
-            let buffer = buffer.upgrade(cx)?;
+            let buffer = buffer.upgrade()?;
             let file = File::from_dyn(buffer.read(cx).file())?;
             if file.worktree == worktree && file.path() == &path.path {
                 Some(buffer)

crates/project/src/project_settings.rs 🔗

@@ -1,7 +1,8 @@
 use collections::HashMap;
+use gpui::AppContext;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 use std::sync::Arc;
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
@@ -34,7 +35,7 @@ pub struct LspSettings {
     pub initialization_options: Option<serde_json::Value>,
 }
 
-impl Setting for ProjectSettings {
+impl Settings for ProjectSettings {
     const KEY: Option<&'static str> = None;
 
     type FileContent = Self;
@@ -42,7 +43,7 @@ impl Setting for ProjectSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/project/src/project_tests.rs 🔗

@@ -1,7 +1,7 @@
-use crate::{worktree::WorktreeModelHandle, Event, *};
-use fs::{FakeFs, RealFs};
+use crate::{Event, *};
+use fs::FakeFs;
 use futures::{future, StreamExt};
-use gpui::{executor::Deterministic, test::subscribe, AppContext};
+use gpui::AppContext;
 use language::{
     language_settings::{AllLanguageSettings, LanguageSettingsContent},
     tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
@@ -11,22 +11,44 @@ use lsp::Url;
 use parking_lot::Mutex;
 use pretty_assertions::assert_eq;
 use serde_json::json;
-use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
+use std::{os, task::Poll};
 use unindent::Unindent as _;
 use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
 
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
+#[gpui::test]
+async fn test_block_via_channel(cx: &mut gpui::TestAppContext) {
+    cx.executor().allow_parking();
+
+    let (tx, mut rx) = futures::channel::mpsc::unbounded();
+    let _thread = std::thread::spawn(move || {
+        std::fs::metadata("/Users").unwrap();
+        std::thread::sleep(Duration::from_millis(1000));
+        tx.unbounded_send(1).unwrap();
+    });
+    rx.next().await.unwrap();
+}
+
+#[gpui::test]
+async fn test_block_via_smol(cx: &mut gpui::TestAppContext) {
+    cx.executor().allow_parking();
+
+    let io_task = smol::unblock(move || {
+        println!("sleeping on thread {:?}", std::thread::current().id());
+        std::thread::sleep(Duration::from_millis(10));
+        1
+    });
+
+    let task = cx.foreground_executor().spawn(async move {
+        io_task.await;
+    });
+
+    task.await;
 }
 
 #[gpui::test]
 async fn test_symlinks(cx: &mut gpui::TestAppContext) {
     init_test(cx);
-    cx.foreground().allow_parking();
+    cx.executor().allow_parking();
 
     let dir = temp_tree(json!({
         "root": {
@@ -44,16 +66,17 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
     }));
 
     let root_link_path = dir.path().join("root_link");
-    unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
-    unix::fs::symlink(
+    os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
+    os::unix::fs::symlink(
         &dir.path().join("root/fennel"),
         &dir.path().join("root/finnochio"),
     )
     .unwrap();
 
     let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
-    project.read_with(cx, |project, cx| {
-        let tree = project.worktrees(cx).next().unwrap().read(cx);
+
+    project.update(cx, |project, cx| {
+        let tree = project.worktrees().next().unwrap().read(cx);
         assert_eq!(tree.file_count(), 5);
         assert_eq!(
             tree.inode_for_path("fennel/grape"),
@@ -63,13 +86,10 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_managing_project_specific_settings(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
+async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -90,10 +110,10 @@ async fn test_managing_project_specific_settings(
     .await;
 
     let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
+    let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
 
-    deterministic.run_until_parked();
-    cx.read(|cx| {
+    cx.executor().run_until_parked();
+    cx.update(|cx| {
         let tree = worktree.read(cx);
 
         let settings_a = language_settings(
@@ -123,10 +143,7 @@ async fn test_managing_project_specific_settings(
 }
 
 #[gpui::test]
-async fn test_managing_language_servers(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
+async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let mut rust_language = Language::new(
@@ -172,7 +189,7 @@ async fn test_managing_language_servers(
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -201,7 +218,7 @@ async fn test_managing_language_servers(
         })
         .await
         .unwrap();
-    rust_buffer.read_with(cx, |buffer, _| {
+    rust_buffer.update(cx, |buffer, _| {
         assert_eq!(buffer.language().map(|l| l.name()), None);
     });
 
@@ -211,8 +228,8 @@ async fn test_managing_language_servers(
         project.languages.add(Arc::new(json_language));
         project.languages.add(Arc::new(rust_language));
     });
-    deterministic.run_until_parked();
-    rust_buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    rust_buffer.update(cx, |buffer, _| {
         assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
     });
 
@@ -232,13 +249,13 @@ async fn test_managing_language_servers(
     );
 
     // The buffer is configured based on the language server's capabilities.
-    rust_buffer.read_with(cx, |buffer, _| {
+    rust_buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer.completion_triggers(),
             &[".".to_string(), "::".to_string()]
         );
     });
-    toml_buffer.read_with(cx, |buffer, _| {
+    toml_buffer.update(cx, |buffer, _| {
         assert!(buffer.completion_triggers().is_empty());
     });
 
@@ -280,7 +297,7 @@ async fn test_managing_language_servers(
 
     // This buffer is configured based on the second language server's
     // capabilities.
-    json_buffer.read_with(cx, |buffer, _| {
+    json_buffer.update(cx, |buffer, _| {
         assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
     });
 
@@ -292,7 +309,7 @@ async fn test_managing_language_servers(
         })
         .await
         .unwrap();
-    rust_buffer2.read_with(cx, |buffer, _| {
+    rust_buffer2.update(cx, |buffer, _| {
         assert_eq!(
             buffer.completion_triggers(),
             &[".".to_string(), "::".to_string()]
@@ -358,7 +375,7 @@ async fn test_managing_language_servers(
         lsp::TextDocumentItem {
             uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
             version: 0,
-            text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
+            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
             language_id: Default::default()
         },
     );
@@ -408,13 +425,13 @@ async fn test_managing_language_servers(
         lsp::TextDocumentItem {
             uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
             version: 0,
-            text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
+            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
             language_id: Default::default()
         },
     );
 
     // We clear the diagnostics, since the language has changed.
-    rust_buffer2.read_with(cx, |buffer, _| {
+    rust_buffer2.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -463,7 +480,7 @@ async fn test_managing_language_servers(
         lsp::TextDocumentItem {
             uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
             version: 0,
-            text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
+            text: rust_buffer.update(cx, |buffer, _| buffer.text()),
             language_id: Default::default()
         }
     );
@@ -484,13 +501,13 @@ async fn test_managing_language_servers(
             lsp::TextDocumentItem {
                 uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
                 version: 0,
-                text: json_buffer.read_with(cx, |buffer, _| buffer.text()),
+                text: json_buffer.update(cx, |buffer, _| buffer.text()),
                 language_id: Default::default()
             },
             lsp::TextDocumentItem {
                 uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
                 version: 0,
-                text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
+                text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
                 language_id: Default::default()
             }
         ]
@@ -530,7 +547,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/the-root",
         json!({
@@ -564,7 +581,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     project.update(cx, |project, _| {
         project.languages.add(Arc::new(language));
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     // Start the language server by opening a buffer with a compatible file extension.
     let _buffer = project
@@ -575,8 +592,8 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         .unwrap();
 
     // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
-    project.read_with(cx, |project, cx| {
-        let worktree = project.worktrees(cx).next().unwrap();
+    project.update(cx, |project, cx| {
+        let worktree = project.worktrees().next().unwrap();
         assert_eq!(
             worktree
                 .read(cx)
@@ -643,14 +660,14 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         }
     });
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
     assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4);
 
     // Now the language server has asked us to watch an ignored directory path,
     // so we recursively load it.
-    project.read_with(cx, |project, cx| {
-        let worktree = project.worktrees(cx).next().unwrap();
+    project.update(cx, |project, cx| {
+        let worktree = project.worktrees().next().unwrap();
         assert_eq!(
             worktree
                 .read(cx)
@@ -693,7 +710,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         .unwrap();
 
     // The language server receives events for the FS mutations that match its watch patterns.
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(
         &*file_changes.lock(),
         &[
@@ -717,7 +734,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
 async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -775,7 +792,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             .unwrap();
     });
 
-    buffer_a.read_with(cx, |buffer, _| {
+    buffer_a.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -789,7 +806,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             ]
         );
     });
-    buffer_b.read_with(cx, |buffer, _| {
+    buffer_b.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -809,7 +826,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/root",
         json!({
@@ -841,7 +858,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    let other_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+    let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
 
     let server_id = LanguageServerId(0);
     project.update(cx, |project, cx| {
@@ -887,7 +904,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    main_ignored_buffer.read_with(cx, |buffer, _| {
+    main_ignored_buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -908,7 +925,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         })
         .await
         .unwrap();
-    other_buffer.read_with(cx, |buffer, _| {
+    other_buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -924,7 +941,7 @@ async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
         );
     });
 
-    project.read_with(cx, |project, cx| {
+    project.update(cx, |project, cx| {
         assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
         assert_eq!(
             project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
@@ -966,7 +983,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -978,7 +995,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
     project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let worktree_id = project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
+    let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id());
 
     // Cause worktree to start the fake language server
     let _buffer = project
@@ -986,7 +1003,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         .await
         .unwrap();
 
-    let mut events = subscribe(&project, cx);
+    let mut events = cx.events(&project);
 
     let fake_server = fake_servers.next().await.unwrap();
     assert_eq!(
@@ -1035,7 +1052,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         .await
         .unwrap();
 
-    buffer.read_with(cx, |buffer, _| {
+    buffer.update(cx, |buffer, _| {
         let snapshot = buffer.snapshot();
         let diagnostics = snapshot
             .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
@@ -1074,7 +1091,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         version: None,
         diagnostics: Default::default(),
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(futures::poll!(events.next()), Poll::Pending);
 }
 
@@ -1098,7 +1115,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1117,7 +1134,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
     project.update(cx, |project, cx| {
         project.restart_language_servers_for_buffers([buffer], cx);
     });
-    let mut events = subscribe(&project, cx);
+    let mut events = cx.events(&project);
 
     // Simulate the newly started server sending more diagnostics.
     let fake_server = fake_servers.next().await.unwrap();
@@ -1132,7 +1149,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
             language_server_id: LanguageServerId(1)
         }
     );
-    project.read_with(cx, |project, _| {
+    project.update(cx, |project, _| {
         assert_eq!(
             project
                 .language_servers_running_disk_based_diagnostics()
@@ -1150,7 +1167,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
             language_server_id: LanguageServerId(1)
         }
     );
-    project.read_with(cx, |project, _| {
+    project.update(cx, |project, _| {
         assert_eq!(
             project
                 .language_servers_running_disk_based_diagnostics()
@@ -1177,7 +1194,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1201,8 +1218,8 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
         }],
     });
 
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1212,7 +1229,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
             ["the message".to_string()]
         );
     });
-    project.read_with(cx, |project, cx| {
+    project.update(cx, |project, cx| {
         assert_eq!(
             project.diagnostic_summary(false, cx),
             DiagnosticSummary {
@@ -1227,8 +1244,8 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
     });
 
     // The diagnostics are cleared.
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1238,7 +1255,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
             Vec::<String>::new(),
         );
     });
-    project.read_with(cx, |project, cx| {
+    project.update(cx, |project, cx| {
         assert_eq!(
             project.diagnostic_summary(false, cx),
             DiagnosticSummary {
@@ -1267,7 +1284,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1285,7 +1302,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
         version: Some(10000),
         diagnostics: Vec::new(),
     });
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     project.update(cx, |project, cx| {
         project.restart_language_servers_for_buffers([buffer.clone()], cx);
@@ -1331,7 +1348,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
         .await;
 
@@ -1453,7 +1470,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1506,9 +1523,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
     });
 
     // The diagnostics have moved down since they were created.
-    buffer.next_notification(cx).await;
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1585,9 +1601,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
         ],
     });
 
-    buffer.next_notification(cx).await;
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1678,9 +1693,8 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
         ],
     });
 
-    buffer.next_notification(cx).await;
-    cx.foreground().run_until_parked();
-    buffer.read_with(cx, |buffer, _| {
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, _| {
         assert_eq!(
             buffer
                 .snapshot()
@@ -1726,7 +1740,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
         "let three = 3;\n",
     );
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": text })).await;
 
     let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@@ -1767,7 +1781,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
     // An empty range is extended forward to include the following character.
     // At the end of a line, an empty range is extended backward to include
     // the preceding character.
-    buffer.read_with(cx, |buffer, _| {
+    buffer.update(cx, |buffer, _| {
         let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
         assert_eq!(
             chunks
@@ -1789,7 +1803,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
 async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
         .await;
 
@@ -1842,7 +1856,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
 }
 
 #[gpui::test]
-async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
+async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let mut language = Language::new(
@@ -1868,7 +1882,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2000,7 +2014,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
+async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let text = "
@@ -2014,7 +2028,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2108,7 +2122,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
 }
 
 #[gpui::test]
-async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
+async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
     let text = "
@@ -2122,7 +2136,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
     "
     .unindent();
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2242,7 +2256,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
     );
     let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2283,7 +2297,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
         .unwrap();
 
     // Assert no new language server started
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert!(fake_servers.try_next().is_err());
 
     assert_eq!(definitions.len(), 1);
@@ -2307,17 +2321,17 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
 
         drop(definition);
     });
-    cx.read(|cx| {
+    cx.update(|cx| {
         assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
     });
 
     fn list_worktrees<'a>(
-        project: &'a ModelHandle<Project>,
+        project: &'a Model<Project>,
         cx: &'a AppContext,
     ) -> Vec<(&'a Path, bool)> {
         project
             .read(cx)
-            .worktrees(cx)
+            .worktrees()
             .map(|worktree| {
                 let worktree = worktree.read(cx);
                 (
@@ -2354,7 +2368,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2391,7 +2405,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         .next()
         .await;
     let completions = completions.await.unwrap();
-    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
     assert_eq!(completions.len(), 1);
     assert_eq!(completions[0].new_text, "fullyQualifiedName");
     assert_eq!(
@@ -2417,7 +2431,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
         .next()
         .await;
     let completions = completions.await.unwrap();
-    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
+    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
     assert_eq!(completions.len(), 1);
     assert_eq!(completions[0].new_text, "component");
     assert_eq!(
@@ -2451,7 +2465,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
         }))
         .await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2506,7 +2520,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
     );
     let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2614,7 +2628,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
 async fn test_save_file(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2639,14 +2653,133 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
         .unwrap();
 
     let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
+    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
+}
+
+#[gpui::test(iterations = 30)]
+async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "file1": "the original contents",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
+    let buffer = project
+        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
+        .await
+        .unwrap();
+
+    // Simulate buffer diffs being slow, so that they don't complete before
+    // the next file change occurs.
+    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
+
+    // Change the buffer's file on disk, and then wait for the file change
+    // to be detected by the worktree, so that the buffer starts reloading.
+    fs.save(
+        "/dir/file1".as_ref(),
+        &"the first contents".into(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    worktree.next_event(cx);
+
+    // Change the buffer's file again. Depending on the random seed, the
+    // previous file change may still be in progress.
+    fs.save(
+        "/dir/file1".as_ref(),
+        &"the second contents".into(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    worktree.next_event(cx);
+
+    cx.executor().run_until_parked();
+    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
+    buffer.read_with(cx, |buffer, _| {
+        assert_eq!(buffer.text(), on_disk_text);
+        assert!(!buffer.is_dirty(), "buffer should not be dirty");
+        assert!(!buffer.has_conflict(), "buffer should not be dirty");
+    });
+}
+
+#[gpui::test(iterations = 30)]
+async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor().clone());
+    fs.insert_tree(
+        "/dir",
+        json!({
+            "file1": "the original contents",
+        }),
+    )
+    .await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
+    let buffer = project
+        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
+        .await
+        .unwrap();
+
+    // Simulate buffer diffs being slow, so that they don't complete before
+    // the next file change occurs.
+    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
+
+    // Change the buffer's file on disk, and then wait for the file change
+    // to be detected by the worktree, so that the buffer starts reloading.
+    fs.save(
+        "/dir/file1".as_ref(),
+        &"the first contents".into(),
+        Default::default(),
+    )
+    .await
+    .unwrap();
+    worktree.next_event(cx);
+
+    cx.executor()
+        .spawn(cx.executor().simulate_random_delay())
+        .await;
+
+    // Perform a noop edit, causing the buffer's version to increase.
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit([(0..0, " ")], None, cx);
+        buffer.undo(cx);
+    });
+
+    cx.executor().run_until_parked();
+    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
+    buffer.read_with(cx, |buffer, _| {
+        let buffer_text = buffer.text();
+        if buffer_text == on_disk_text {
+            assert!(
+                !buffer.is_dirty() && !buffer.has_conflict(),
+                "buffer shouldn't be dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}",
+            );
+        }
+        // If the file change occurred while the buffer was processing the first
+        // change, the buffer will be in a conflicting state.
+        else {
+            assert!(buffer.is_dirty(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
+            assert!(buffer.has_conflict(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
+        }
+    });
 }
 
 #[gpui::test]
 async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
     init_test(cx);
 
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.executor());
     fs.insert_tree(
         "/dir",
         json!({
@@ -2670,75 +2803,72 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
         .unwrap();
 
     let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
+    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
 }
 
-// #[gpui::test]
-// async fn test_save_as(cx: &mut gpui::TestAppContext) {
-//     init_test(cx);
-
-//     let fs = FakeFs::new(cx.background());
-//     fs.insert_tree("/dir", json!({})).await;
-
-//     let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-//     let languages = project.read_with(cx, |project, _| project.languages().clone());
-//     languages.register(
-//         "/some/path",
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".into()],
-//             ..Default::default()
-//         },
-//         tree_sitter_rust::language(),
-//         vec![],
-//         |_| Default::default(),
-//     );
-
-//     let buffer = project.update(cx, |project, cx| {
-//         project.create_buffer("", None, cx).unwrap()
-//     });
-//     buffer.update(cx, |buffer, cx| {
-//         buffer.edit([(0..0, "abc")], None, cx);
-//         assert!(buffer.is_dirty());
-//         assert!(!buffer.has_conflict());
-//         assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
-//     });
-//     project
-//         .update(cx, |project, cx| {
-//             project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
-
-//     cx.foreground().run_until_parked();
-//     buffer.read_with(cx, |buffer, cx| {
-//         assert_eq!(
-//             buffer.file().unwrap().full_path(cx),
-//             Path::new("dir/file1.rs")
-//         );
-//         assert!(!buffer.is_dirty());
-//         assert!(!buffer.has_conflict());
-//         assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
-//     });
-
-//     let opened_buffer = project
-//         .update(cx, |project, cx| {
-//             project.open_local_buffer("/dir/file1.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(opened_buffer, buffer);
-// }
+#[gpui::test]
+async fn test_save_as(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
+    let fs = FakeFs::new(cx.executor());
+    fs.insert_tree("/dir", json!({})).await;
+
+    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+
+    let languages = project.update(cx, |project, _| project.languages().clone());
+    languages.register(
+        "/some/path",
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".into()],
+            ..Default::default()
+        },
+        tree_sitter_rust::language(),
+        vec![],
+        |_| Default::default(),
+    );
+
+    let buffer = project.update(cx, |project, cx| {
+        project.create_buffer("", None, cx).unwrap()
+    });
+    buffer.update(cx, |buffer, cx| {
+        buffer.edit([(0..0, "abc")], None, cx);
+        assert!(buffer.is_dirty());
+        assert!(!buffer.has_conflict());
+        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
+    });
+    project
+        .update(cx, |project, cx| {
+            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
+
+    cx.executor().run_until_parked();
+    buffer.update(cx, |buffer, cx| {
+        assert_eq!(
+            buffer.file().unwrap().full_path(cx),
+            Path::new("dir/file1.rs")
+        );
+        assert!(!buffer.is_dirty());
+        assert!(!buffer.has_conflict());
+        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
+    });
+
+    let opened_buffer = project
+        .update(cx, |project, cx| {
+            project.open_local_buffer("/dir/file1.rs", cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(opened_buffer, buffer);
+}
 
 #[gpui::test(retries = 5)]
-async fn test_rescan_and_remote_updates(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
+async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
     init_test(cx);
-    cx.foreground().allow_parking();
+    cx.executor().allow_parking();
 
     let dir = temp_tree(json!({
         "a": {

crates/project/src/terminals.rs 🔗

@@ -1,5 +1,6 @@
 use crate::Project;
-use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
+use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
+use settings::Settings;
 use std::path::{Path, PathBuf};
 use terminal::{
     terminal_settings::{self, TerminalSettings, VenvSettingsContent},
@@ -10,7 +11,7 @@ use terminal::{
 use std::os::unix::ffi::OsStrExt;
 
 pub struct Terminals {
-    pub(crate) local_handles: Vec<WeakModelHandle<terminal::Terminal>>,
+    pub(crate) local_handles: Vec<WeakModel<terminal::Terminal>>,
 }
 
 impl Project {
@@ -19,13 +20,13 @@ impl Project {
         working_directory: Option<PathBuf>,
         window: AnyWindowHandle,
         cx: &mut ModelContext<Self>,
-    ) -> anyhow::Result<ModelHandle<Terminal>> {
+    ) -> anyhow::Result<Model<Terminal>> {
         if self.is_remote() {
             return Err(anyhow::anyhow!(
                 "creating terminals as a guest is not supported yet"
             ));
         } else {
-            let settings = settings::get::<TerminalSettings>(cx);
+            let settings = TerminalSettings::get_global(cx);
             let python_settings = settings.detect_venv.clone();
             let shell = settings.shell.clone();
 
@@ -38,17 +39,20 @@ impl Project {
                 window,
             )
             .map(|builder| {
-                let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));
+                let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
 
                 self.terminals
                     .local_handles
                     .push(terminal_handle.downgrade());
 
-                let id = terminal_handle.id();
+                let id = terminal_handle.entity_id();
                 cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
                     let handles = &mut project.terminals.local_handles;
 
-                    if let Some(index) = handles.iter().position(|terminal| terminal.id() == id) {
+                    if let Some(index) = handles
+                        .iter()
+                        .position(|terminal| terminal.entity_id() == id)
+                    {
                         handles.remove(index);
                         cx.notify();
                     }
@@ -103,7 +107,7 @@ impl Project {
     fn activate_python_virtual_environment(
         &mut self,
         activate_script: Option<PathBuf>,
-        terminal_handle: &ModelHandle<Terminal>,
+        terminal_handle: &Model<Terminal>,
         cx: &mut ModelContext<Project>,
     ) {
         if let Some(activate_script) = activate_script {
@@ -116,7 +120,7 @@ impl Project {
         }
     }
 
-    pub fn local_terminal_handles(&self) -> &Vec<WeakModelHandle<terminal::Terminal>> {
+    pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
         &self.terminals.local_handles
     }
 }

crates/project/src/worktree.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     ProjectEntryId, RemoveOptions,
 };
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use client::{proto, Client};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet, VecDeque};
@@ -18,12 +18,13 @@ use futures::{
     },
     select_biased,
     task::Poll,
-    FutureExt, Stream, StreamExt,
+    FutureExt as _, Stream, StreamExt,
 };
 use fuzzy::CharBag;
 use git::{DOT_GIT, GITIGNORE};
 use gpui::{
-    executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Subscription, Task,
+    AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
+    Task,
 };
 use itertools::Itertools;
 use language::{
@@ -40,7 +41,7 @@ use postage::{
     prelude::{Sink as _, Stream as _},
     watch,
 };
-use settings::SettingsStore;
+use settings::{Settings, SettingsStore};
 use smol::channel::{self, Sender};
 use std::{
     any::Any,
@@ -78,7 +79,6 @@ pub struct LocalWorktree {
     scan_requests_tx: channel::Sender<ScanRequest>,
     path_prefixes_to_scan_tx: channel::Sender<Arc<Path>>,
     is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
-    _settings_subscription: Subscription,
     _background_scanner_tasks: Vec<Task<()>>,
     share: Option<ShareState>,
     diagnostics: HashMap<
@@ -283,14 +283,13 @@ struct ShareState {
     _maintain_remote_snapshot: Task<Option<()>>,
 }
 
+#[derive(Clone)]
 pub enum Event {
     UpdatedEntries(UpdatedEntriesSet),
     UpdatedGitRepositories(UpdatedGitRepositoriesSet),
 }
 
-impl Entity for Worktree {
-    type Event = Event;
-}
+impl EventEmitter<Event> for Worktree {}
 
 impl Worktree {
     pub async fn local(
@@ -300,10 +299,11 @@ impl Worktree {
         fs: Arc<dyn Fs>,
         next_entry_id: Arc<AtomicUsize>,
         cx: &mut AsyncAppContext,
-    ) -> Result<ModelHandle<Self>> {
+    ) -> Result<Model<Self>> {
         // After determining whether the root entry is a file or a directory, populate the
         // snapshot's "root name", which will be used for the purpose of fuzzy matching.
         let abs_path = path.into();
+
         let metadata = fs
             .metadata(&abs_path)
             .await
@@ -312,11 +312,11 @@ impl Worktree {
         let closure_fs = Arc::clone(&fs);
         let closure_next_entry_id = Arc::clone(&next_entry_id);
         let closure_abs_path = abs_path.to_path_buf();
-        Ok(cx.add_model(move |cx: &mut ModelContext<Worktree>| {
-            let settings_subscription = cx.observe_global::<SettingsStore, _>(move |this, cx| {
+        cx.new_model(move |cx: &mut ModelContext<Worktree>| {
+            cx.observe_global::<SettingsStore>(move |this, cx| {
                 if let Self::Local(this) = this {
                     let new_file_scan_exclusions =
-                        file_scan_exclusions(settings::get::<ProjectSettings>(cx));
+                        file_scan_exclusions(ProjectSettings::get_global(cx));
                     if new_file_scan_exclusions != this.snapshot.file_scan_exclusions {
                         this.snapshot.file_scan_exclusions = new_file_scan_exclusions;
                         log::info!(
@@ -345,17 +345,19 @@ impl Worktree {
                         this.is_scanning = watch::channel_with(true);
                     }
                 }
-            });
+            })
+            .detach();
 
             let root_name = abs_path
                 .file_name()
                 .map_or(String::new(), |f| f.to_string_lossy().to_string());
+
             let mut snapshot = LocalSnapshot {
-                file_scan_exclusions: file_scan_exclusions(settings::get::<ProjectSettings>(cx)),
+                file_scan_exclusions: file_scan_exclusions(ProjectSettings::get_global(cx)),
                 ignores_by_parent_abs_path: Default::default(),
                 git_repositories: Default::default(),
                 snapshot: Snapshot {
-                    id: WorktreeId::from_usize(cx.model_id()),
+                    id: WorktreeId::from_usize(cx.entity_id().as_u64() as usize),
                     abs_path: abs_path.to_path_buf().into(),
                     root_name: root_name.clone(),
                     root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
@@ -388,7 +390,6 @@ impl Worktree {
                 share: None,
                 scan_requests_tx,
                 path_prefixes_to_scan_tx,
-                _settings_subscription: settings_subscription,
                 _background_scanner_tasks: start_background_scan_tasks(
                     &abs_path,
                     task_snapshot,
@@ -404,18 +405,17 @@ impl Worktree {
                 fs,
                 visible,
             })
-        }))
+        })
     }
 
-    // abcdefghi
     pub fn remote(
         project_remote_id: u64,
         replica_id: ReplicaId,
         worktree: proto::WorktreeMetadata,
         client: Arc<Client>,
         cx: &mut AppContext,
-    ) -> ModelHandle<Self> {
-        cx.add_model(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.new_model(|cx: &mut ModelContext<Self>| {
             let snapshot = Snapshot {
                 id: WorktreeId(worktree.id as usize),
                 abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
@@ -436,7 +436,7 @@ impl Worktree {
             let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
             let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
 
-            cx.background()
+            cx.background_executor()
                 .spawn({
                     let background_snapshot = background_snapshot.clone();
                     async move {
@@ -452,27 +452,24 @@ impl Worktree {
                 })
                 .detach();
 
-            cx.spawn_weak(|this, mut cx| async move {
+            cx.spawn(|this, mut cx| async move {
                 while (snapshot_updated_rx.recv().await).is_some() {
-                    if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| {
-                            let this = this.as_remote_mut().unwrap();
-                            this.snapshot = this.background_snapshot.lock().clone();
-                            cx.emit(Event::UpdatedEntries(Arc::from([])));
-                            cx.notify();
-                            while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
-                                if this.observed_snapshot(*scan_id) {
-                                    let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
-                                    let _ = tx.send(());
-                                } else {
-                                    break;
-                                }
+                    this.update(&mut cx, |this, cx| {
+                        let this = this.as_remote_mut().unwrap();
+                        this.snapshot = this.background_snapshot.lock().clone();
+                        cx.emit(Event::UpdatedEntries(Arc::from([])));
+                        cx.notify();
+                        while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
+                            if this.observed_snapshot(*scan_id) {
+                                let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
+                                let _ = tx.send(());
+                            } else {
+                                break;
                             }
-                        });
-                    } else {
-                        break;
-                    }
+                        }
+                    })?;
                 }
+                anyhow::Ok(())
             })
             .detach();
 
@@ -604,9 +601,9 @@ fn start_background_scan_tasks(
     cx: &mut ModelContext<'_, Worktree>,
 ) -> Vec<Task<()>> {
     let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
-    let background_scanner = cx.background().spawn({
+    let background_scanner = cx.background_executor().spawn({
         let abs_path = abs_path.to_path_buf();
-        let background = cx.background().clone();
+        let background = cx.background_executor().clone();
         async move {
             let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
             BackgroundScanner::new(
@@ -622,8 +619,8 @@ fn start_background_scan_tasks(
             .await;
         }
     });
-    let scan_state_updater = cx.spawn_weak(|this, mut cx| async move {
-        while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade(&cx)) {
+    let scan_state_updater = cx.spawn(|this, mut cx| async move {
+        while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade()) {
             this.update(&mut cx, |this, cx| {
                 let this = this.as_local_mut().unwrap();
                 match state {
@@ -642,7 +639,8 @@ fn start_background_scan_tasks(
                     }
                 }
                 cx.notify();
-            });
+            })
+            .ok();
         }
     });
     vec![background_scanner, scan_state_updater]
@@ -674,17 +672,17 @@ impl LocalWorktree {
         id: u64,
         path: &Path,
         cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<ModelHandle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let path = Arc::from(path);
         cx.spawn(move |this, mut cx| async move {
             let (file, contents, diff_base) = this
-                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))
+                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))?
                 .await?;
             let text_buffer = cx
-                .background()
+                .background_executor()
                 .spawn(async move { text::Buffer::new(0, id, contents) })
                 .await;
-            Ok(cx.add_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file)))))
+            cx.new_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
         })
     }
 
@@ -958,24 +956,17 @@ impl LocalWorktree {
         let fs = self.fs.clone();
         let entry = self.refresh_entry(path.clone(), None, cx);
 
-        cx.spawn(|this, cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let text = fs.load(&abs_path).await?;
             let mut index_task = None;
-            let snapshot = this.read_with(&cx, |this, _| this.as_local().unwrap().snapshot());
+            let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
             if let Some(repo) = snapshot.repository_for_path(&path) {
-                if let Some(repo_path) = repo.work_directory.relativize(&snapshot, &path) {
-                    if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
-                        let repo = repo.repo_ptr.clone();
-                        index_task = Some(
-                            cx.background()
-                                .spawn(async move { repo.lock().load_index_text(&repo_path) }),
-                        );
-                    }
-                } else {
-                    log::warn!(
-                        "Skipping loading index text from path {:?} is not in repository {:?}",
-                        path,
-                        repo.work_directory,
+                let repo_path = repo.work_directory.relativize(&snapshot, &path).unwrap();
+                if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
+                    let repo = repo.repo_ptr.clone();
+                    index_task = Some(
+                        cx.background_executor()
+                            .spawn(async move { repo.lock().load_index_text(&repo_path) }),
                     );
                 }
             }
@@ -986,11 +977,14 @@ impl LocalWorktree {
                 None
             };
 
+            let worktree = this
+                .upgrade()
+                .ok_or_else(|| anyhow!("worktree was dropped"))?;
             match entry.await? {
                 Some(entry) => Ok((
                     File {
                         entry_id: Some(entry.id),
-                        worktree: this,
+                        worktree,
                         path: entry.path,
                         mtime: entry.mtime,
                         is_local: true,
@@ -1012,7 +1006,7 @@ impl LocalWorktree {
                     Ok((
                         File {
                             entry_id: None,
-                            worktree: this,
+                            worktree,
                             path,
                             mtime: metadata.mtime,
                             is_local: true,
@@ -1028,12 +1022,11 @@ impl LocalWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: ModelHandle<Buffer>,
+        buffer_handle: Model<Buffer>,
         path: Arc<Path>,
         has_changed_file: bool,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
-        let handle = cx.handle();
         let buffer = buffer_handle.read(cx);
 
         let rpc = self.client.clone();
@@ -1047,8 +1040,9 @@ impl LocalWorktree {
         let fs = Arc::clone(&self.fs);
         let abs_path = self.absolutize(&path);
 
-        cx.as_mut().spawn(|mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             let entry = save.await?;
+            let this = this.upgrade().context("worktree dropped")?;
 
             let (entry_id, mtime, path) = match entry {
                 Some(entry) => (Some(entry.id), entry.mtime, entry.path),
@@ -1071,7 +1065,7 @@ impl LocalWorktree {
             if has_changed_file {
                 let new_file = Arc::new(File {
                     entry_id,
-                    worktree: handle,
+                    worktree: this,
                     path,
                     mtime,
                     is_local: true,
@@ -1091,7 +1085,7 @@ impl LocalWorktree {
                     if has_changed_file {
                         buffer.file_updated(new_file, cx);
                     }
-                });
+                })?;
             }
 
             if let Some(project_id) = project_id {
@@ -1106,7 +1100,7 @@ impl LocalWorktree {
 
             buffer_handle.update(&mut cx, |buffer, cx| {
                 buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            });
+            })?;
 
             Ok(())
         })
@@ -1135,7 +1129,7 @@ impl LocalWorktree {
         let lowest_ancestor = self.lowest_ancestor(&path);
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
-        let write = cx.background().spawn(async move {
+        let write = cx.background_executor().spawn(async move {
             if is_dir {
                 fs.create_dir(&abs_path).await
             } else {
@@ -1165,7 +1159,7 @@ impl LocalWorktree {
                     this.as_local_mut().unwrap().refresh_entry(path, None, cx),
                     refreshes,
                 )
-            });
+            })?;
             for refresh in refreshes {
                 refresh.await.log_err();
             }
@@ -1185,14 +1179,14 @@ impl LocalWorktree {
         let abs_path = self.absolutize(&path);
         let fs = self.fs.clone();
         let write = cx
-            .background()
+            .background_executor()
             .spawn(async move { fs.save(&abs_path, &text, line_ending).await });
 
         cx.spawn(|this, mut cx| async move {
             write.await?;
             this.update(&mut cx, |this, cx| {
                 this.as_local_mut().unwrap().refresh_entry(path, None, cx)
-            })
+            })?
             .await
         })
     }
@@ -1206,7 +1200,7 @@ impl LocalWorktree {
         let abs_path = self.absolutize(&entry.path);
         let fs = self.fs.clone();
 
-        let delete = cx.background().spawn(async move {
+        let delete = cx.background_executor().spawn(async move {
             if entry.is_file() {
                 fs.remove_file(&abs_path, Default::default()).await?;
             } else {
@@ -1228,7 +1222,7 @@ impl LocalWorktree {
                 this.as_local_mut()
                     .unwrap()
                     .refresh_entries_for_paths(vec![path])
-            })
+            })?
             .recv()
             .await;
             Ok(())
@@ -1249,7 +1243,7 @@ impl LocalWorktree {
         let abs_old_path = self.absolutize(&old_path);
         let abs_new_path = self.absolutize(&new_path);
         let fs = self.fs.clone();
-        let rename = cx.background().spawn(async move {
+        let rename = cx.background_executor().spawn(async move {
             fs.rename(&abs_old_path, &abs_new_path, Default::default())
                 .await
         });
@@ -1260,7 +1254,7 @@ impl LocalWorktree {
                 this.as_local_mut()
                     .unwrap()
                     .refresh_entry(new_path.clone(), Some(old_path), cx)
-            })
+            })?
             .await
         })
     }
@@ -1279,7 +1273,7 @@ impl LocalWorktree {
         let abs_old_path = self.absolutize(&old_path);
         let abs_new_path = self.absolutize(&new_path);
         let fs = self.fs.clone();
-        let copy = cx.background().spawn(async move {
+        let copy = cx.background_executor().spawn(async move {
             copy_recursive(
                 fs.as_ref(),
                 &abs_old_path,
@@ -1295,7 +1289,7 @@ impl LocalWorktree {
                 this.as_local_mut()
                     .unwrap()
                     .refresh_entry(new_path.clone(), None, cx)
-            })
+            })?
             .await
         })
     }
@@ -1307,7 +1301,7 @@ impl LocalWorktree {
     ) -> Option<Task<Result<()>>> {
         let path = self.entry_for_id(entry_id)?.path.clone();
         let mut refresh = self.refresh_entries_for_paths(vec![path]);
-        Some(cx.background().spawn(async move {
+        Some(cx.background_executor().spawn(async move {
             refresh.next().await;
             Ok(())
         }))
@@ -1343,16 +1337,13 @@ impl LocalWorktree {
             vec![path.clone()]
         };
         let mut refresh = self.refresh_entries_for_paths(paths);
-        cx.spawn_weak(move |this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             refresh.recv().await;
-            let new_entry = this
-                .upgrade(&cx)
-                .ok_or_else(|| anyhow!("worktree was dropped"))?
-                .update(&mut cx, |this, _| {
-                    this.entry_for_path(path)
-                        .cloned()
-                        .ok_or_else(|| anyhow!("failed to read path after update"))
-                })?;
+            let new_entry = this.update(&mut cx, |this, _| {
+                this.entry_for_path(path)
+                    .cloned()
+                    .ok_or_else(|| anyhow!("failed to read path after update"))
+            })??;
             Ok(Some(new_entry))
         })
     }
@@ -1387,8 +1378,8 @@ impl LocalWorktree {
             .unbounded_send((self.snapshot(), Arc::from([]), Arc::from([])))
             .ok();
 
-        let worktree_id = cx.model_id() as u64;
-        let _maintain_remote_snapshot = cx.background().spawn(async move {
+        let worktree_id = cx.entity_id().as_u64();
+        let _maintain_remote_snapshot = cx.background_executor().spawn(async move {
             let mut is_first = true;
             while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await {
                 let update;
@@ -1435,7 +1426,7 @@ impl LocalWorktree {
             for (&server_id, summary) in summaries {
                 if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
                     project_id,
-                    worktree_id: cx.model_id() as u64,
+                    worktree_id: cx.entity_id().as_u64(),
                     summary: Some(summary.to_proto(server_id, &path)),
                 }) {
                     return Task::ready(Err(e));
@@ -1446,7 +1437,7 @@ impl LocalWorktree {
         let rx = self.observe_updates(project_id, cx, move |update| {
             client.request(update).map(|result| result.is_ok())
         });
-        cx.foreground()
+        cx.background_executor()
             .spawn(async move { rx.await.map_err(|_| anyhow!("share ended")) })
     }
 
@@ -1472,7 +1463,7 @@ impl RemoteWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: ModelHandle<Buffer>,
+        buffer_handle: Model<Buffer>,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let buffer = buffer_handle.read(cx);
@@ -1480,7 +1471,7 @@ impl RemoteWorktree {
         let version = buffer.version();
         let rpc = self.client.clone();
         let project_id = self.project_id;
-        cx.as_mut().spawn(|mut cx| async move {
+        cx.spawn(move |_, mut cx| async move {
             let response = rpc
                 .request(proto::SaveBuffer {
                     project_id,
@@ -1497,7 +1488,7 @@ impl RemoteWorktree {
 
             buffer_handle.update(&mut cx, |buffer, cx| {
                 buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            });
+            })?;
 
             Ok(())
         })
@@ -1577,7 +1568,7 @@ impl RemoteWorktree {
                 let entry = snapshot.insert_entry(entry);
                 worktree.snapshot = snapshot.clone();
                 entry
-            })
+            })?
         })
     }
 
@@ -1588,14 +1579,14 @@ impl RemoteWorktree {
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let wait_for_snapshot = self.wait_for_snapshot(scan_id);
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(move |this, mut cx| async move {
             wait_for_snapshot.await?;
             this.update(&mut cx, |worktree, _| {
                 let worktree = worktree.as_remote_mut().unwrap();
                 let mut snapshot = worktree.background_snapshot.lock();
                 snapshot.delete_entry(id);
                 worktree.snapshot = snapshot.clone();
-            });
+            })?;
             Ok(())
         })
     }
@@ -2168,16 +2159,11 @@ impl LocalSnapshot {
 
     fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
         let mut new_ignores = Vec::new();
-        for (index, ancestor) in abs_path.ancestors().enumerate() {
-            if index > 0 {
-                if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
-                    new_ignores.push((ancestor, Some(ignore.clone())));
-                } else {
-                    new_ignores.push((ancestor, None));
-                }
-            }
-            if ancestor.join(&*DOT_GIT).is_dir() {
-                break;
+        for ancestor in abs_path.ancestors().skip(1) {
+            if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
+                new_ignores.push((ancestor, Some(ignore.clone())));
+            } else {
+                new_ignores.push((ancestor, None));
             }
         }
 
@@ -2194,6 +2180,7 @@ impl LocalSnapshot {
         if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
             ignore_stack = IgnoreStack::all();
         }
+
         ignore_stack
     }
 
@@ -2471,6 +2458,7 @@ impl BackgroundScannerState {
 
     fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) {
         let scan_id = self.snapshot.scan_id;
+
         for dot_git_dir in dot_git_dirs_to_reload {
             // If there is already a repository for this .git directory, reload
             // the status for all of its files.
@@ -2732,7 +2720,7 @@ impl fmt::Debug for Snapshot {
 
 #[derive(Clone, PartialEq)]
 pub struct File {
-    pub worktree: ModelHandle<Worktree>,
+    pub worktree: Model<Worktree>,
     pub path: Arc<Path>,
     pub mtime: SystemTime,
     pub(crate) entry_id: Option<ProjectEntryId>,
@@ -2790,7 +2778,7 @@ impl language::File for File {
     }
 
     fn worktree_id(&self) -> usize {
-        self.worktree.id()
+        self.worktree.entity_id().as_u64() as usize
     }
 
     fn is_deleted(&self) -> bool {
@@ -2803,7 +2791,7 @@ impl language::File for File {
 
     fn to_proto(&self) -> rpc::proto::File {
         rpc::proto::File {
-            worktree_id: self.worktree.id() as u64,
+            worktree_id: self.worktree.entity_id().as_u64(),
             entry_id: self.entry_id.map(|id| id.to_proto()),
             path: self.path.to_string_lossy().into(),
             mtime: Some(self.mtime.into()),
@@ -2826,7 +2814,7 @@ impl language::LocalFile for File {
         let worktree = self.worktree.read(cx).as_local().unwrap();
         let abs_path = worktree.absolutize(&self.path);
         let fs = worktree.fs.clone();
-        cx.background()
+        cx.background_executor()
             .spawn(async move { fs.load(&abs_path).await })
     }
 
@@ -2857,7 +2845,7 @@ impl language::LocalFile for File {
 }
 
 impl File {
-    pub fn for_entry(entry: Entry, worktree: ModelHandle<Worktree>) -> Arc<Self> {
+    pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
         Arc::new(Self {
             worktree,
             path: entry.path.clone(),
@@ -2870,7 +2858,7 @@ impl File {
 
     pub fn from_proto(
         proto: rpc::proto::File,
-        worktree: ModelHandle<Worktree>,
+        worktree: Model<Worktree>,
         cx: &AppContext,
     ) -> Result<Self> {
         let worktree_id = worktree
@@ -3168,7 +3156,7 @@ struct BackgroundScanner {
     state: Mutex<BackgroundScannerState>,
     fs: Arc<dyn Fs>,
     status_updates_tx: UnboundedSender<ScanState>,
-    executor: Arc<executor::Background>,
+    executor: BackgroundExecutor,
     scan_requests_rx: channel::Receiver<ScanRequest>,
     path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     next_entry_id: Arc<AtomicUsize>,
@@ -3188,7 +3176,7 @@ impl BackgroundScanner {
         next_entry_id: Arc<AtomicUsize>,
         fs: Arc<dyn Fs>,
         status_updates_tx: UnboundedSender<ScanState>,
-        executor: Arc<executor::Background>,
+        executor: BackgroundExecutor,
         scan_requests_rx: channel::Receiver<ScanRequest>,
         path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
     ) -> Self {
@@ -3220,21 +3208,14 @@ impl BackgroundScanner {
 
         // Populate ignores above the root.
         let root_abs_path = self.state.lock().snapshot.abs_path.clone();
-        for (index, ancestor) in root_abs_path.ancestors().enumerate() {
-            if index != 0 {
-                if let Ok(ignore) =
-                    build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
-                {
-                    self.state
-                        .lock()
-                        .snapshot
-                        .ignores_by_parent_abs_path
-                        .insert(ancestor.into(), (ignore.into(), false));
-                }
-            }
-            if ancestor.join(&*DOT_GIT).is_dir() {
-                // Reached root of git repository.
-                break;
+        for ancestor in root_abs_path.ancestors().skip(1) {
+            if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
+            {
+                self.state
+                    .lock()
+                    .snapshot
+                    .ignores_by_parent_abs_path
+                    .insert(ancestor.into(), (ignore.into(), false));
             }
         }
 
@@ -3397,6 +3378,7 @@ impl BackgroundScanner {
                     );
                         return false;
                     };
+
                 let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
                     snapshot
                         .entry_for_path(parent)
@@ -3662,8 +3644,8 @@ impl BackgroundScanner {
             }
 
             {
-                let mut state = self.state.lock();
                 let relative_path = job.path.join(child_name);
+                let mut state = self.state.lock();
                 if state.snapshot.is_path_excluded(relative_path.clone()) {
                     log::debug!("skipping excluded child entry {relative_path:?}");
                     state.remove_path(&relative_path);
@@ -4240,11 +4222,11 @@ pub trait WorktreeModelHandle {
     #[cfg(any(test, feature = "test-support"))]
     fn flush_fs_events<'a>(
         &self,
-        cx: &'a gpui::TestAppContext,
+        cx: &'a mut gpui::TestAppContext,
     ) -> futures::future::LocalBoxFuture<'a, ()>;
 }
 
-impl WorktreeModelHandle for ModelHandle<Worktree> {
+impl WorktreeModelHandle for Model<Worktree> {
     // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
     // occurred before the worktree was constructed. These events can cause the worktree to perform
     // extra directory scans, and emit extra scan-state notifications.
@@ -4254,29 +4236,31 @@ impl WorktreeModelHandle for ModelHandle<Worktree> {
     #[cfg(any(test, feature = "test-support"))]
     fn flush_fs_events<'a>(
         &self,
-        cx: &'a gpui::TestAppContext,
+        cx: &'a mut gpui::TestAppContext,
     ) -> futures::future::LocalBoxFuture<'a, ()> {
-        let filename = "fs-event-sentinel";
+        let file_name = "fs-event-sentinel";
+
         let tree = self.clone();
-        let (fs, root_path) = self.read_with(cx, |tree, _| {
+        let (fs, root_path) = self.update(cx, |tree, _| {
             let tree = tree.as_local().unwrap();
             (tree.fs.clone(), tree.abs_path().clone())
         });
 
         async move {
-            fs.create_file(&root_path.join(filename), Default::default())
+            fs.create_file(&root_path.join(file_name), Default::default())
                 .await
                 .unwrap();
-            tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some())
+
+            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some())
                 .await;
 
-            fs.remove_file(&root_path.join(filename), Default::default())
+            fs.remove_file(&root_path.join(file_name), Default::default())
                 .await
                 .unwrap();
-            tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none())
+            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none())
                 .await;
 
-            cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+            cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
                 .await;
         }
         .boxed_local()

crates/project/src/worktree_tests.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::Result;
 use client::Client;
 use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
 use git::GITIGNORE;
-use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};
+use gpui::{ModelContext, Task, TestAppContext};
 use parking_lot::Mutex;
 use postage::stream::Stream;
 use pretty_assertions::assert_eq;
@@ -26,7 +26,7 @@ use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
 #[gpui::test]
 async fn test_traversal(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -82,7 +82,7 @@ async fn test_traversal(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_descendent_entries(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -188,9 +188,9 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
 }
 
 #[gpui::test(iterations = 10)]
-async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
+async fn test_circular_symlinks(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -247,7 +247,7 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
     )
     .await
     .unwrap();
-    executor.run_until_parked();
+    cx.executor().run_until_parked();
     tree.read_with(cx, |tree, _| {
         assert_eq!(
             tree.entries(false)
@@ -270,7 +270,7 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
 #[gpui::test]
 async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -446,7 +446,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_open_gitignored_files(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -597,7 +597,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
     fs.create_dir("/root/one/node_modules/c/lib".as_ref())
         .await
         .unwrap();
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     assert_eq!(
         fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
         0
@@ -607,7 +607,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -693,7 +693,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
     fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
         .await
         .unwrap();
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     // All of the directories that are no longer ignored are now loaded.
     tree.read_with(cx, |tree, _| {
@@ -732,13 +732,13 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
 async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
     init_test(cx);
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions = Some(Vec::new());
             });
         });
     });
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -818,7 +818,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
     .await
     .unwrap();
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     cx.read(|cx| {
         let tree = tree.read(cx);
         assert!(
@@ -844,6 +844,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_write_file(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let dir = temp_tree(json!({
         ".git": {},
         ".gitignore": "ignored-dir\n",
@@ -897,6 +898,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let dir = temp_tree(json!({
         ".gitignore": "**/target\n/node_modules\n",
         "target": {
@@ -922,7 +924,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
         ".DS_Store": "",
     }));
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions =
                     Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
@@ -959,7 +961,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
     });
 
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions =
                     Some(vec!["**/node_modules/**".to_string()]);
@@ -967,7 +969,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
         });
     });
     tree.flush_fs_events(cx).await;
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree.read_with(cx, |tree, _| {
         check_worktree_entries(
             tree,
@@ -993,6 +995,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let dir = temp_tree(json!({
         ".git": {
             "HEAD": "ref: refs/heads/main\n",
@@ -1022,7 +1025,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
         ".DS_Store": "",
     }));
     cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+        cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions = Some(vec![
                     "**/.git".to_string(),
@@ -1134,7 +1137,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
 #[gpui::test(iterations = 30)]
 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -1180,7 +1183,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_dir());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree.read_with(cx, |tree, _| {
         assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
     });
@@ -1195,9 +1198,10 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
     init_test(cx);
-    let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    cx.executor().allow_parking();
+    let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 
-    let fs_fake = FakeFs::new(cx.background());
+    let fs_fake = FakeFs::new(cx.background_executor.clone());
     fs_fake
         .insert_tree(
             "/root",
@@ -1229,14 +1233,14 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_fake.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
         assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
         assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
     });
 
-    let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
+    let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 
     let fs_real = Arc::new(RealFs);
     let temp_root = temp_tree(json!({
@@ -1265,7 +1269,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_real.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
         assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
@@ -1284,7 +1288,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_real.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("a/b/c/e.txt").unwrap().is_file());
     });
@@ -1301,7 +1305,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         .unwrap();
     assert!(entry.is_file());
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     tree_real.read_with(cx, |tree, _| {
         assert!(tree.entry_for_path("d/e/f/g.txt").unwrap().is_file());
         assert!(tree.entry_for_path("d/e/f").unwrap().is_dir());
@@ -1324,7 +1328,7 @@ async fn test_random_worktree_operations_during_initial_scan(
         .unwrap_or(20);
 
     let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
+    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
     fs.as_fake().insert_tree(root_dir, json!({})).await;
     for _ in 0..initial_entries {
         randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
@@ -1376,7 +1380,7 @@ async fn test_random_worktree_operations_during_initial_scan(
         .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
         .await;
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     let final_snapshot = worktree.read_with(cx, |tree, _| {
         let tree = tree.as_local().unwrap();
@@ -1414,7 +1418,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
         .unwrap_or(20);
 
     let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
+    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
     fs.as_fake().insert_tree(root_dir, json!({})).await;
     for _ in 0..initial_entries {
         randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
@@ -1474,7 +1478,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
             mutations_len -= 1;
         }
 
-        cx.foreground().run_until_parked();
+        cx.executor().run_until_parked();
         if rng.gen_bool(0.2) {
             log::info!("storing snapshot {}", snapshots.len());
             let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
@@ -1484,7 +1488,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
 
     log::info!("quiescing");
     fs.as_fake().flush_events(usize::MAX);
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
 
     let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
     snapshot.check_invariants(true);
@@ -1624,7 +1628,7 @@ fn randomly_mutate_worktree(
                 new_path
             );
             let task = worktree.rename_entry(entry.id, new_path, cx);
-            cx.foreground().spawn(async move {
+            cx.background_executor().spawn(async move {
                 task.await?.unwrap();
                 Ok(())
             })
@@ -1639,7 +1643,7 @@ fn randomly_mutate_worktree(
                     child_path,
                 );
                 let task = worktree.create_entry(child_path, is_dir, cx);
-                cx.foreground().spawn(async move {
+                cx.background_executor().spawn(async move {
                     task.await?;
                     Ok(())
                 })
@@ -1647,7 +1651,7 @@ fn randomly_mutate_worktree(
                 log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
                 let task =
                     worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
-                cx.foreground().spawn(async move {
+                cx.background_executor().spawn(async move {
                     task.await?;
                     Ok(())
                 })
@@ -1826,6 +1830,7 @@ fn random_filename(rng: &mut impl Rng) -> String {
 #[gpui::test]
 async fn test_rename_work_directory(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let root = temp_tree(json!({
         "projects": {
             "project1": {
@@ -1897,6 +1902,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
 #[gpui::test]
 async fn test_git_repository_for_path(cx: &mut TestAppContext) {
     init_test(cx);
+    cx.executor().allow_parking();
     let root = temp_tree(json!({
         "c.txt": "",
         "dir1": {
@@ -2016,16 +2022,9 @@ async fn test_git_repository_for_path(cx: &mut TestAppContext) {
 }
 
 #[gpui::test]
-async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+async fn test_git_status(cx: &mut TestAppContext) {
     init_test(cx);
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/.git".to_string(), "**/.gitignore".to_string()]);
-            });
-        });
-    });
+    cx.executor().allow_parking();
     const IGNORE_RULE: &'static str = "**/target";
 
     let root = temp_tree(json!({
@@ -2077,7 +2076,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     tree.flush_fs_events(cx).await;
     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
         .await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // Check that the right git state is observed on startup
     tree.read_with(cx, |tree, _cx| {
@@ -2099,7 +2098,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     // Modify a file in the working copy.
     std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // The worktree detects that the file's git status has changed.
     tree.read_with(cx, |tree, _cx| {
@@ -2115,7 +2114,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     git_add(B_TXT, &repo);
     git_commit("Committing modified and added", &repo);
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // The worktree detects that the files' git status have changed.
     tree.read_with(cx, |tree, _cx| {
@@ -2135,7 +2134,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
     std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     // Check that more complex repo changes are tracked
     tree.read_with(cx, |tree, _cx| {
@@ -2164,7 +2163,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     git_commit("Committing modified git ignore", &repo);
 
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     let mut renamed_dir_name = "first_directory/second_directory";
     const RENAMED_FILE: &'static str = "rf.txt";
@@ -2177,7 +2176,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     .unwrap();
 
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     tree.read_with(cx, |tree, _cx| {
         let snapshot = tree.snapshot();
@@ -2196,7 +2195,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
     .unwrap();
 
     tree.flush_fs_events(cx).await;
-    deterministic.run_until_parked();
+    cx.executor().run_until_parked();
 
     tree.read_with(cx, |tree, _cx| {
         let snapshot = tree.snapshot();
@@ -2215,7 +2214,7 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
 #[gpui::test]
 async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
     init_test(cx);
-    let fs = FakeFs::new(cx.background());
+    let fs = FakeFs::new(cx.background_executor.clone());
     fs.insert_tree(
         "/root",
         json!({
@@ -2266,7 +2265,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
         .await;
 
-    cx.foreground().run_until_parked();
+    cx.executor().run_until_parked();
     let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
 
     check_propagated_statuses(
@@ -2334,7 +2333,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
 
 fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
     let http_client = FakeHttpClient::with_404_response();
-    cx.read(|cx| Client::new(http_client, cx))
+    cx.update(|cx| Client::new(http_client, cx))
 }
 
 #[track_caller]
@@ -2456,7 +2455,8 @@ fn check_worktree_entries(
 
 fn init_test(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
-        cx.set_global(SettingsStore::test(cx));
+        let settings_store = SettingsStore::test(cx);
+        cx.set_global(settings_store);
         Project::init_settings(cx);
     });
 }

crates/project2/Cargo.toml 🔗

@@ -1,85 +0,0 @@
-[package]
-name = "project2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/project2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "client/test-support",
-    "db/test-support",
-    "language/test-support",
-    "settings/test-support",
-    "text/test-support",
-    "prettier/test-support",
-    "gpui/test-support",
-]
-
-[dependencies]
-text = { package = "text2", path = "../text2" }
-copilot = { path = "../copilot" }
-client = { package = "client2", path = "../client2" }
-clock = { path = "../clock" }
-collections = { path = "../collections" }
-db = { package = "db2", path = "../db2" }
-fs = { package = "fs2", path = "../fs2" }
-fsevent = { path = "../fsevent" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-git = { package = "git3", path = "../git3" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-node_runtime = { path = "../node_runtime" }
-prettier = { package = "prettier2", path = "../prettier2" }
-rpc = { package = "rpc2", path = "../rpc2" }
-settings = { package = "settings2", path = "../settings2" }
-sum_tree = { path = "../sum_tree" }
-terminal = { package = "terminal2", path = "../terminal2" }
-util = { path = "../util" }
-
-aho-corasick = "1.1"
-anyhow.workspace = true
-async-trait.workspace = true
-backtrace = "0.3"
-futures.workspace = true
-globset.workspace = true
-ignore = "0.4"
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-rand.workspace = true
-regex.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-sha2 = "0.10"
-similar = "1.3"
-smol.workspace = true
-thiserror.workspace = true
-toml.workspace = true
-itertools = "0.10"
-
-[dev-dependencies]
-ctor.workspace = true
-env_logger.workspace = true
-pretty_assertions.workspace = true
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-collections = { path = "../collections", features = ["test-support"] }
-db = { package = "db2", path = "../db2", features = ["test-support"] }
-fs = { package = "fs2", path = "../fs2",  features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-prettier = { package = "prettier2", path = "../prettier2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-git2.workspace = true
-tempdir.workspace = true
-unindent.workspace = true

crates/project2/src/ignore.rs 🔗

@@ -1,53 +0,0 @@
-use ignore::gitignore::Gitignore;
-use std::{ffi::OsStr, path::Path, sync::Arc};
-
-pub enum IgnoreStack {
-    None,
-    Some {
-        abs_base_path: Arc<Path>,
-        ignore: Arc<Gitignore>,
-        parent: Arc<IgnoreStack>,
-    },
-    All,
-}
-
-impl IgnoreStack {
-    pub fn none() -> Arc<Self> {
-        Arc::new(Self::None)
-    }
-
-    pub fn all() -> Arc<Self> {
-        Arc::new(Self::All)
-    }
-
-    pub fn append(self: Arc<Self>, abs_base_path: Arc<Path>, ignore: Arc<Gitignore>) -> Arc<Self> {
-        match self.as_ref() {
-            IgnoreStack::All => self,
-            _ => Arc::new(Self::Some {
-                abs_base_path,
-                ignore,
-                parent: self,
-            }),
-        }
-    }
-
-    pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
-        if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
-            return true;
-        }
-
-        match self {
-            Self::None => false,
-            Self::All => true,
-            Self::Some {
-                abs_base_path,
-                ignore,
-                parent: prev,
-            } => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
-                ignore::Match::None => prev.is_abs_path_ignored(abs_path, is_dir),
-                ignore::Match::Ignore(_) => true,
-                ignore::Match::Whitelist(_) => false,
-            },
-        }
-    }
-}

crates/project2/src/lsp_command.rs 🔗

@@ -1,2364 +0,0 @@
-use crate::{
-    DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
-    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
-    MarkupContent, Project, ProjectTransaction, ResolveState,
-};
-use anyhow::{anyhow, Context, Result};
-use async_trait::async_trait;
-use client::proto::{self, PeerId};
-use futures::future;
-use gpui::{AppContext, AsyncAppContext, Model};
-use language::{
-    language_settings::{language_settings, InlayHintKind},
-    point_from_lsp, point_to_lsp, prepare_completion_documentation,
-    proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
-    range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
-    CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
-    Unclipped,
-};
-use lsp::{
-    CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId,
-    OneOf, ServerCapabilities,
-};
-use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
-use text::LineEnding;
-
-pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
-    lsp::FormattingOptions {
-        tab_size,
-        insert_spaces: true,
-        insert_final_newline: Some(true),
-        ..lsp::FormattingOptions::default()
-    }
-}
-
-#[async_trait(?Send)]
-pub trait LspCommand: 'static + Sized + Send {
-    type Response: 'static + Default + Send;
-    type LspRequest: 'static + Send + lsp::request::Request;
-    type ProtoRequest: 'static + Send + proto::RequestMessage;
-
-    fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool {
-        true
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        buffer: &Buffer,
-        language_server: &Arc<LanguageServer>,
-        cx: &AppContext,
-    ) -> <Self::LspRequest as lsp::request::Request>::Params;
-
-    async fn response_from_lsp(
-        self,
-        message: <Self::LspRequest as lsp::request::Request>::Result,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
-    ) -> Result<Self::Response>;
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
-
-    async fn from_proto(
-        message: Self::ProtoRequest,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Self>;
-
-    fn response_to_proto(
-        response: Self::Response,
-        project: &mut Project,
-        peer_id: PeerId,
-        buffer_version: &clock::Global,
-        cx: &mut AppContext,
-    ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
-
-    async fn response_from_proto(
-        self,
-        message: <Self::ProtoRequest as proto::RequestMessage>::Response,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Self::Response>;
-
-    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
-}
-
-pub(crate) struct PrepareRename {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct PerformRename {
-    pub position: PointUtf16,
-    pub new_name: String,
-    pub push_to_history: bool,
-}
-
-pub(crate) struct GetDefinition {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetTypeDefinition {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetReferences {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetDocumentHighlights {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetHover {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetCompletions {
-    pub position: PointUtf16,
-}
-
-pub(crate) struct GetCodeActions {
-    pub range: Range<Anchor>,
-}
-
-pub(crate) struct OnTypeFormatting {
-    pub position: PointUtf16,
-    pub trigger: String,
-    pub options: FormattingOptions,
-    pub push_to_history: bool,
-}
-
-pub(crate) struct InlayHints {
-    pub range: Range<Anchor>,
-}
-
-pub(crate) struct FormattingOptions {
-    tab_size: u32,
-}
-
-impl From<lsp::FormattingOptions> for FormattingOptions {
-    fn from(value: lsp::FormattingOptions) -> Self {
-        Self {
-            tab_size: value.tab_size,
-        }
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for PrepareRename {
-    type Response = Option<Range<Anchor>>;
-    type LspRequest = lsp::request::PrepareRenameRequest;
-    type ProtoRequest = proto::PrepareRename;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        if let Some(lsp::OneOf::Right(rename)) = &capabilities.rename_provider {
-            rename.prepare_provider == Some(true)
-        } else {
-            false
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::TextDocumentPositionParams {
-        lsp::TextDocumentPositionParams {
-            text_document: lsp::TextDocumentIdentifier {
-                uri: lsp::Url::from_file_path(path).unwrap(),
-            },
-            position: point_to_lsp(self.position),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::PrepareRenameResponse>,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        _: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Option<Range<Anchor>>> {
-        buffer.update(&mut cx, |buffer, _| {
-            if let Some(
-                lsp::PrepareRenameResponse::Range(range)
-                | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
-            ) = message
-            {
-                let Range { start, end } = range_from_lsp(range);
-                if buffer.clip_point_utf16(start, Bias::Left) == start.0
-                    && buffer.clip_point_utf16(end, Bias::Left) == end.0
-                {
-                    return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
-                }
-            }
-            Ok(None)
-        })?
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
-        proto::PrepareRename {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::PrepareRename,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        range: Option<Range<Anchor>>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::PrepareRenameResponse {
-        proto::PrepareRenameResponse {
-            can_rename: range.is_some(),
-            start: range
-                .as_ref()
-                .map(|range| language::proto::serialize_anchor(&range.start)),
-            end: range
-                .as_ref()
-                .map(|range| language::proto::serialize_anchor(&range.end)),
-            version: serialize_version(buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::PrepareRenameResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Option<Range<Anchor>>> {
-        if message.can_rename {
-            buffer
-                .update(&mut cx, |buffer, _| {
-                    buffer.wait_for_version(deserialize_version(&message.version))
-                })?
-                .await?;
-            let start = message.start.and_then(deserialize_anchor);
-            let end = message.end.and_then(deserialize_anchor);
-            Ok(start.zip(end).map(|(start, end)| start..end))
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for PerformRename {
-    type Response = ProjectTransaction;
-    type LspRequest = lsp::request::Rename;
-    type ProtoRequest = proto::PerformRename;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::RenameParams {
-        lsp::RenameParams {
-            text_document_position: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            new_name: self.new_name.clone(),
-            work_done_progress_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::WorkspaceEdit>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        if let Some(edit) = message {
-            let (lsp_adapter, lsp_server) =
-                language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-            Project::deserialize_workspace_edit(
-                project,
-                edit,
-                self.push_to_history,
-                lsp_adapter,
-                lsp_server,
-                &mut cx,
-            )
-            .await
-        } else {
-            Ok(ProjectTransaction::default())
-        }
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
-        proto::PerformRename {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            new_name: self.new_name.clone(),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::PerformRename,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-            new_name: message.new_name,
-            push_to_history: false,
-        })
-    }
-
-    fn response_to_proto(
-        response: ProjectTransaction,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::PerformRenameResponse {
-        let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
-        proto::PerformRenameResponse {
-            transaction: Some(transaction),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::PerformRenameResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        let message = message
-            .transaction
-            .ok_or_else(|| anyhow!("missing transaction"))?;
-        project
-            .update(&mut cx, |project, cx| {
-                project.deserialize_project_transaction(message, self.push_to_history, cx)
-            })?
-            .await
-    }
-
-    fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetDefinition {
-    type Response = Vec<LocationLink>;
-    type LspRequest = lsp::request::GotoDefinition;
-    type ProtoRequest = proto::GetDefinition;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::GotoDefinitionParams {
-        lsp::GotoDefinitionParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::GotoDefinitionResponse>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_lsp(message, project, buffer, server_id, cx).await
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
-        proto::GetDefinition {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetDefinition,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<LocationLink>,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::GetDefinitionResponse {
-        let links = location_links_to_proto(response, project, peer_id, cx);
-        proto::GetDefinitionResponse { links }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetDefinitionResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_proto(message.links, project, cx).await
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetTypeDefinition {
-    type Response = Vec<LocationLink>;
-    type LspRequest = lsp::request::GotoTypeDefinition;
-    type ProtoRequest = proto::GetTypeDefinition;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        match &capabilities.type_definition_provider {
-            None => false,
-            Some(lsp::TypeDefinitionProviderCapability::Simple(false)) => false,
-            _ => true,
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::GotoTypeDefinitionParams {
-        lsp::GotoTypeDefinitionParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::GotoTypeDefinitionResponse>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_lsp(message, project, buffer, server_id, cx).await
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
-        proto::GetTypeDefinition {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetTypeDefinition,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<LocationLink>,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::GetTypeDefinitionResponse {
-        let links = location_links_to_proto(response, project, peer_id, cx);
-        proto::GetTypeDefinitionResponse { links }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetTypeDefinitionResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        cx: AsyncAppContext,
-    ) -> Result<Vec<LocationLink>> {
-        location_links_from_proto(message.links, project, cx).await
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 {
-        message.buffer_id
-    }
-}
-
-fn language_server_for_buffer(
-    project: &Model<Project>,
-    buffer: &Model<Buffer>,
-    server_id: LanguageServerId,
-    cx: &mut AsyncAppContext,
-) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
-    project
-        .update(cx, |project, cx| {
-            project
-                .language_server_for_buffer(buffer.read(cx), server_id, cx)
-                .map(|(adapter, server)| (adapter.clone(), server.clone()))
-        })?
-        .ok_or_else(|| anyhow!("no language server found for buffer"))
-}
-
-async fn location_links_from_proto(
-    proto_links: Vec<proto::LocationLink>,
-    project: Model<Project>,
-    mut cx: AsyncAppContext,
-) -> Result<Vec<LocationLink>> {
-    let mut links = Vec::new();
-
-    for link in proto_links {
-        let origin = match link.origin {
-            Some(origin) => {
-                let buffer = project
-                    .update(&mut cx, |this, cx| {
-                        this.wait_for_remote_buffer(origin.buffer_id, cx)
-                    })?
-                    .await?;
-                let start = origin
-                    .start
-                    .and_then(deserialize_anchor)
-                    .ok_or_else(|| anyhow!("missing origin start"))?;
-                let end = origin
-                    .end
-                    .and_then(deserialize_anchor)
-                    .ok_or_else(|| anyhow!("missing origin end"))?;
-                buffer
-                    .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-                    .await?;
-                Some(Location {
-                    buffer,
-                    range: start..end,
-                })
-            }
-            None => None,
-        };
-
-        let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
-        let buffer = project
-            .update(&mut cx, |this, cx| {
-                this.wait_for_remote_buffer(target.buffer_id, cx)
-            })?
-            .await?;
-        let start = target
-            .start
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("missing target start"))?;
-        let end = target
-            .end
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("missing target end"))?;
-        buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-            .await?;
-        let target = Location {
-            buffer,
-            range: start..end,
-        };
-
-        links.push(LocationLink { origin, target })
-    }
-
-    Ok(links)
-}
-
-async fn location_links_from_lsp(
-    message: Option<lsp::GotoDefinitionResponse>,
-    project: Model<Project>,
-    buffer: Model<Buffer>,
-    server_id: LanguageServerId,
-    mut cx: AsyncAppContext,
-) -> Result<Vec<LocationLink>> {
-    let message = match message {
-        Some(message) => message,
-        None => return Ok(Vec::new()),
-    };
-
-    let mut unresolved_links = Vec::new();
-    match message {
-        lsp::GotoDefinitionResponse::Scalar(loc) => {
-            unresolved_links.push((None, loc.uri, loc.range));
-        }
-
-        lsp::GotoDefinitionResponse::Array(locs) => {
-            unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
-        }
-
-        lsp::GotoDefinitionResponse::Link(links) => {
-            unresolved_links.extend(links.into_iter().map(|l| {
-                (
-                    l.origin_selection_range,
-                    l.target_uri,
-                    l.target_selection_range,
-                )
-            }));
-        }
-    }
-
-    let (lsp_adapter, language_server) =
-        language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-    let mut definitions = Vec::new();
-    for (origin_range, target_uri, target_range) in unresolved_links {
-        let target_buffer_handle = project
-            .update(&mut cx, |this, cx| {
-                this.open_local_buffer_via_lsp(
-                    target_uri,
-                    language_server.server_id(),
-                    lsp_adapter.name.clone(),
-                    cx,
-                )
-            })?
-            .await?;
-
-        cx.update(|cx| {
-            let origin_location = origin_range.map(|origin_range| {
-                let origin_buffer = buffer.read(cx);
-                let origin_start =
-                    origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
-                let origin_end =
-                    origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
-                Location {
-                    buffer: buffer.clone(),
-                    range: origin_buffer.anchor_after(origin_start)
-                        ..origin_buffer.anchor_before(origin_end),
-                }
-            });
-
-            let target_buffer = target_buffer_handle.read(cx);
-            let target_start =
-                target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
-            let target_end =
-                target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
-            let target_location = Location {
-                buffer: target_buffer_handle,
-                range: target_buffer.anchor_after(target_start)
-                    ..target_buffer.anchor_before(target_end),
-            };
-
-            definitions.push(LocationLink {
-                origin: origin_location,
-                target: target_location,
-            })
-        })?;
-    }
-    Ok(definitions)
-}
-
-fn location_links_to_proto(
-    links: Vec<LocationLink>,
-    project: &mut Project,
-    peer_id: PeerId,
-    cx: &mut AppContext,
-) -> Vec<proto::LocationLink> {
-    links
-        .into_iter()
-        .map(|definition| {
-            let origin = definition.origin.map(|origin| {
-                let buffer_id = project.create_buffer_for_peer(&origin.buffer, peer_id, cx);
-                proto::Location {
-                    start: Some(serialize_anchor(&origin.range.start)),
-                    end: Some(serialize_anchor(&origin.range.end)),
-                    buffer_id,
-                }
-            });
-
-            let buffer_id = project.create_buffer_for_peer(&definition.target.buffer, peer_id, cx);
-            let target = proto::Location {
-                start: Some(serialize_anchor(&definition.target.range.start)),
-                end: Some(serialize_anchor(&definition.target.range.end)),
-                buffer_id,
-            };
-
-            proto::LocationLink {
-                origin,
-                target: Some(target),
-            }
-        })
-        .collect()
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetReferences {
-    type Response = Vec<Location>;
-    type LspRequest = lsp::request::References;
-    type ProtoRequest = proto::GetReferences;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::ReferenceParams {
-        lsp::ReferenceParams {
-            text_document_position: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-            context: lsp::ReferenceContext {
-                include_declaration: true,
-            },
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        locations: Option<Vec<lsp::Location>>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Location>> {
-        let mut references = Vec::new();
-        let (lsp_adapter, language_server) =
-            language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-
-        if let Some(locations) = locations {
-            for lsp_location in locations {
-                let target_buffer_handle = project
-                    .update(&mut cx, |this, cx| {
-                        this.open_local_buffer_via_lsp(
-                            lsp_location.uri,
-                            language_server.server_id(),
-                            lsp_adapter.name.clone(),
-                            cx,
-                        )
-                    })?
-                    .await?;
-
-                target_buffer_handle
-                    .clone()
-                    .update(&mut cx, |target_buffer, _| {
-                        let target_start = target_buffer
-                            .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
-                        let target_end = target_buffer
-                            .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
-                        references.push(Location {
-                            buffer: target_buffer_handle,
-                            range: target_buffer.anchor_after(target_start)
-                                ..target_buffer.anchor_before(target_end),
-                        });
-                    })?;
-            }
-        }
-
-        Ok(references)
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
-        proto::GetReferences {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetReferences,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<Location>,
-        project: &mut Project,
-        peer_id: PeerId,
-        _: &clock::Global,
-        cx: &mut AppContext,
-    ) -> proto::GetReferencesResponse {
-        let locations = response
-            .into_iter()
-            .map(|definition| {
-                let buffer_id = project.create_buffer_for_peer(&definition.buffer, peer_id, cx);
-                proto::Location {
-                    start: Some(serialize_anchor(&definition.range.start)),
-                    end: Some(serialize_anchor(&definition.range.end)),
-                    buffer_id,
-                }
-            })
-            .collect();
-        proto::GetReferencesResponse { locations }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetReferencesResponse,
-        project: Model<Project>,
-        _: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Location>> {
-        let mut locations = Vec::new();
-        for location in message.locations {
-            let target_buffer = project
-                .update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(location.buffer_id, cx)
-                })?
-                .await?;
-            let start = location
-                .start
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target start"))?;
-            let end = location
-                .end
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target end"))?;
-            target_buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-                .await?;
-            locations.push(Location {
-                buffer: target_buffer,
-                range: start..end,
-            })
-        }
-        Ok(locations)
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetDocumentHighlights {
-    type Response = Vec<DocumentHighlight>;
-    type LspRequest = lsp::request::DocumentHighlightRequest;
-    type ProtoRequest = proto::GetDocumentHighlights;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        capabilities.document_highlight_provider.is_some()
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::DocumentHighlightParams {
-        lsp::DocumentHighlightParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        _: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<DocumentHighlight>> {
-        buffer.update(&mut cx, |buffer, _| {
-            let mut lsp_highlights = lsp_highlights.unwrap_or_default();
-            lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end)));
-            lsp_highlights
-                .into_iter()
-                .map(|lsp_highlight| {
-                    let start = buffer
-                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.start), Bias::Left);
-                    let end = buffer
-                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left);
-                    DocumentHighlight {
-                        range: buffer.anchor_after(start)..buffer.anchor_before(end),
-                        kind: lsp_highlight
-                            .kind
-                            .unwrap_or(lsp::DocumentHighlightKind::READ),
-                    }
-                })
-                .collect()
-        })
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentHighlights {
-        proto::GetDocumentHighlights {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetDocumentHighlights,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Vec<DocumentHighlight>,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetDocumentHighlightsResponse {
-        let highlights = response
-            .into_iter()
-            .map(|highlight| proto::DocumentHighlight {
-                start: Some(serialize_anchor(&highlight.range.start)),
-                end: Some(serialize_anchor(&highlight.range.end)),
-                kind: match highlight.kind {
-                    DocumentHighlightKind::TEXT => proto::document_highlight::Kind::Text.into(),
-                    DocumentHighlightKind::WRITE => proto::document_highlight::Kind::Write.into(),
-                    DocumentHighlightKind::READ => proto::document_highlight::Kind::Read.into(),
-                    _ => proto::document_highlight::Kind::Text.into(),
-                },
-            })
-            .collect();
-        proto::GetDocumentHighlightsResponse { highlights }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetDocumentHighlightsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<DocumentHighlight>> {
-        let mut highlights = Vec::new();
-        for highlight in message.highlights {
-            let start = highlight
-                .start
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target start"))?;
-            let end = highlight
-                .end
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("missing target end"))?;
-            buffer
-                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
-                .await?;
-            let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
-                Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
-                Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ,
-                Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE,
-                None => DocumentHighlightKind::TEXT,
-            };
-            highlights.push(DocumentHighlight {
-                range: start..end,
-                kind,
-            });
-        }
-        Ok(highlights)
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetHover {
-    type Response = Option<Hover>;
-    type LspRequest = lsp::request::HoverRequest;
-    type ProtoRequest = proto::GetHover;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::HoverParams {
-        lsp::HoverParams {
-            text_document_position_params: lsp::TextDocumentPositionParams {
-                text_document: lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(path).unwrap(),
-                },
-                position: point_to_lsp(self.position),
-            },
-            work_done_progress_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<lsp::Hover>,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        _: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self::Response> {
-        let Some(hover) = message else {
-            return Ok(None);
-        };
-
-        let (language, range) = buffer.update(&mut cx, |buffer, _| {
-            (
-                buffer.language().cloned(),
-                hover.range.map(|range| {
-                    let token_start =
-                        buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
-                    let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
-                    buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
-                }),
-            )
-        })?;
-
-        fn hover_blocks_from_marked_string(marked_string: lsp::MarkedString) -> Option<HoverBlock> {
-            let block = match marked_string {
-                lsp::MarkedString::String(content) => HoverBlock {
-                    text: content,
-                    kind: HoverBlockKind::Markdown,
-                },
-                lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
-                    HoverBlock {
-                        text: value,
-                        kind: HoverBlockKind::Code { language },
-                    }
-                }
-            };
-            if block.text.is_empty() {
-                None
-            } else {
-                Some(block)
-            }
-        }
-
-        let contents = match hover.contents {
-            lsp::HoverContents::Scalar(marked_string) => {
-                hover_blocks_from_marked_string(marked_string)
-                    .into_iter()
-                    .collect()
-            }
-            lsp::HoverContents::Array(marked_strings) => marked_strings
-                .into_iter()
-                .filter_map(hover_blocks_from_marked_string)
-                .collect(),
-            lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
-                text: markup_content.value,
-                kind: if markup_content.kind == lsp::MarkupKind::Markdown {
-                    HoverBlockKind::Markdown
-                } else {
-                    HoverBlockKind::PlainText
-                },
-            }],
-        };
-
-        Ok(Some(Hover {
-            contents,
-            range,
-            language,
-        }))
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
-        proto::GetHover {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            version: serialize_version(&buffer.version),
-        }
-    }
-
-    async fn from_proto(
-        message: Self::ProtoRequest,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: Self::Response,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetHoverResponse {
-        if let Some(response) = response {
-            let (start, end) = if let Some(range) = response.range {
-                (
-                    Some(language::proto::serialize_anchor(&range.start)),
-                    Some(language::proto::serialize_anchor(&range.end)),
-                )
-            } else {
-                (None, None)
-            };
-
-            let contents = response
-                .contents
-                .into_iter()
-                .map(|block| proto::HoverBlock {
-                    text: block.text,
-                    is_markdown: block.kind == HoverBlockKind::Markdown,
-                    language: if let HoverBlockKind::Code { language } = block.kind {
-                        Some(language)
-                    } else {
-                        None
-                    },
-                })
-                .collect();
-
-            proto::GetHoverResponse {
-                start,
-                end,
-                contents,
-            }
-        } else {
-            proto::GetHoverResponse {
-                start: None,
-                end: None,
-                contents: Vec::new(),
-            }
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetHoverResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self::Response> {
-        let contents: Vec<_> = message
-            .contents
-            .into_iter()
-            .map(|block| HoverBlock {
-                text: block.text,
-                kind: if let Some(language) = block.language {
-                    HoverBlockKind::Code { language }
-                } else if block.is_markdown {
-                    HoverBlockKind::Markdown
-                } else {
-                    HoverBlockKind::PlainText
-                },
-            })
-            .collect();
-        if contents.is_empty() {
-            return Ok(None);
-        }
-
-        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
-        let range = if let (Some(start), Some(end)) = (message.start, message.end) {
-            language::proto::deserialize_anchor(start)
-                .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
-        } else {
-            None
-        };
-
-        Ok(Some(Hover {
-            contents,
-            range,
-            language,
-        }))
-    }
-
-    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetCompletions {
-    type Response = Vec<Completion>;
-    type LspRequest = lsp::request::Completion;
-    type ProtoRequest = proto::GetCompletions;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::CompletionParams {
-        lsp::CompletionParams {
-            text_document_position: lsp::TextDocumentPositionParams::new(
-                lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
-                point_to_lsp(self.position),
-            ),
-            context: Default::default(),
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        completions: Option<lsp::CompletionResponse>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Completion>> {
-        let mut response_list = None;
-        let completions = if let Some(completions) = completions {
-            match completions {
-                lsp::CompletionResponse::Array(completions) => completions,
-
-                lsp::CompletionResponse::List(mut list) => {
-                    let items = std::mem::take(&mut list.items);
-                    response_list = Some(list);
-                    items
-                }
-            }
-        } else {
-            Default::default()
-        };
-
-        let completions = buffer.update(&mut cx, |buffer, cx| {
-            let language_registry = project.read(cx).languages().clone();
-            let language = buffer.language().cloned();
-            let snapshot = buffer.snapshot();
-            let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
-
-            let mut range_for_token = None;
-            completions
-                .into_iter()
-                .filter_map(move |mut lsp_completion| {
-                    let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
-                        // If the language server provides a range to overwrite, then
-                        // check that the range is valid.
-                        Some(lsp::CompletionTextEdit::Edit(edit)) => {
-                            let range = range_from_lsp(edit.range);
-                            let start = snapshot.clip_point_utf16(range.start, Bias::Left);
-                            let end = snapshot.clip_point_utf16(range.end, Bias::Left);
-                            if start != range.start.0 || end != range.end.0 {
-                                log::info!("completion out of expected range");
-                                return None;
-                            }
-                            (
-                                snapshot.anchor_before(start)..snapshot.anchor_after(end),
-                                edit.new_text.clone(),
-                            )
-                        }
-
-                        // If the language server does not provide a range, then infer
-                        // the range based on the syntax tree.
-                        None => {
-                            if self.position != clipped_position {
-                                log::info!("completion out of expected range");
-                                return None;
-                            }
-
-                            let default_edit_range = response_list
-                                .as_ref()
-                                .and_then(|list| list.item_defaults.as_ref())
-                                .and_then(|defaults| defaults.edit_range.as_ref())
-                                .and_then(|range| match range {
-                                    CompletionListItemDefaultsEditRange::Range(r) => Some(r),
-                                    _ => None,
-                                });
-
-                            let range = if let Some(range) = default_edit_range {
-                                let range = range_from_lsp(range.clone());
-                                let start = snapshot.clip_point_utf16(range.start, Bias::Left);
-                                let end = snapshot.clip_point_utf16(range.end, Bias::Left);
-                                if start != range.start.0 || end != range.end.0 {
-                                    log::info!("completion out of expected range");
-                                    return None;
-                                }
-
-                                snapshot.anchor_before(start)..snapshot.anchor_after(end)
-                            } else {
-                                range_for_token
-                                    .get_or_insert_with(|| {
-                                        let offset = self.position.to_offset(&snapshot);
-                                        let (range, kind) = snapshot.surrounding_word(offset);
-                                        let range = if kind == Some(CharKind::Word) {
-                                            range
-                                        } else {
-                                            offset..offset
-                                        };
-
-                                        snapshot.anchor_before(range.start)
-                                            ..snapshot.anchor_after(range.end)
-                                    })
-                                    .clone()
-                            };
-
-                            let text = lsp_completion
-                                .insert_text
-                                .as_ref()
-                                .unwrap_or(&lsp_completion.label)
-                                .clone();
-                            (range, text)
-                        }
-
-                        Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
-                            log::info!("unsupported insert/replace completion");
-                            return None;
-                        }
-                    };
-
-                    let language_registry = language_registry.clone();
-                    let language = language.clone();
-                    LineEnding::normalize(&mut new_text);
-                    Some(async move {
-                        let mut label = None;
-                        if let Some(language) = language.as_ref() {
-                            language.process_completion(&mut lsp_completion).await;
-                            label = language.label_for_completion(&lsp_completion).await;
-                        }
-
-                        let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
-                            Some(
-                                prepare_completion_documentation(
-                                    lsp_docs,
-                                    &language_registry,
-                                    language.clone(),
-                                )
-                                .await,
-                            )
-                        } else {
-                            None
-                        };
-
-                        Completion {
-                            old_range,
-                            new_text,
-                            label: label.unwrap_or_else(|| {
-                                language::CodeLabel::plain(
-                                    lsp_completion.label.clone(),
-                                    lsp_completion.filter_text.as_deref(),
-                                )
-                            }),
-                            documentation,
-                            server_id,
-                            lsp_completion,
-                        }
-                    })
-                })
-        })?;
-
-        Ok(future::join_all(completions).await)
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCompletions {
-        let anchor = buffer.anchor_after(self.position);
-        proto::GetCompletions {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(&anchor)),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetCompletions,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let version = deserialize_version(&message.version);
-        buffer
-            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))?
-            .await?;
-        let position = message
-            .position
-            .and_then(language::proto::deserialize_anchor)
-            .map(|p| {
-                buffer.update(&mut cx, |buffer, _| {
-                    buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
-                })
-            })
-            .ok_or_else(|| anyhow!("invalid position"))??;
-        Ok(Self { position })
-    }
-
-    fn response_to_proto(
-        completions: Vec<Completion>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetCompletionsResponse {
-        proto::GetCompletionsResponse {
-            completions: completions
-                .iter()
-                .map(language::proto::serialize_completion)
-                .collect(),
-            version: serialize_version(&buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetCompletionsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<Completion>> {
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
-        let completions = message.completions.into_iter().map(|completion| {
-            language::proto::deserialize_completion(completion, language.clone())
-        });
-        future::try_join_all(completions).await
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetCompletions) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for GetCodeActions {
-    type Response = Vec<CodeAction>;
-    type LspRequest = lsp::request::CodeActionRequest;
-    type ProtoRequest = proto::GetCodeActions;
-
-    fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        match &capabilities.code_action_provider {
-            None => false,
-            Some(lsp::CodeActionProviderCapability::Simple(false)) => false,
-            _ => true,
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        buffer: &Buffer,
-        language_server: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::CodeActionParams {
-        let relevant_diagnostics = buffer
-            .snapshot()
-            .diagnostics_in_range::<_, usize>(self.range.clone(), false)
-            .map(|entry| entry.to_lsp_diagnostic_stub())
-            .collect();
-        lsp::CodeActionParams {
-            text_document: lsp::TextDocumentIdentifier::new(
-                lsp::Url::from_file_path(path).unwrap(),
-            ),
-            range: range_to_lsp(self.range.to_point_utf16(buffer)),
-            work_done_progress_params: Default::default(),
-            partial_result_params: Default::default(),
-            context: lsp::CodeActionContext {
-                diagnostics: relevant_diagnostics,
-                only: language_server.code_action_kinds(),
-                ..lsp::CodeActionContext::default()
-            },
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        actions: Option<lsp::CodeActionResponse>,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        server_id: LanguageServerId,
-        _: AsyncAppContext,
-    ) -> Result<Vec<CodeAction>> {
-        Ok(actions
-            .unwrap_or_default()
-            .into_iter()
-            .filter_map(|entry| {
-                if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
-                    Some(CodeAction {
-                        server_id,
-                        range: self.range.clone(),
-                        lsp_action,
-                    })
-                } else {
-                    None
-                }
-            })
-            .collect())
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeActions {
-        proto::GetCodeActions {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            start: Some(language::proto::serialize_anchor(&self.range.start)),
-            end: Some(language::proto::serialize_anchor(&self.range.end)),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::GetCodeActions,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let start = message
-            .start
-            .and_then(language::proto::deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid start"))?;
-        let end = message
-            .end
-            .and_then(language::proto::deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid end"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        Ok(Self { range: start..end })
-    }
-
-    fn response_to_proto(
-        code_actions: Vec<CodeAction>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::GetCodeActionsResponse {
-        proto::GetCodeActionsResponse {
-            actions: code_actions
-                .iter()
-                .map(language::proto::serialize_code_action)
-                .collect(),
-            version: serialize_version(&buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::GetCodeActionsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Vec<CodeAction>> {
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-        message
-            .actions
-            .into_iter()
-            .map(language::proto::deserialize_code_action)
-            .collect()
-    }
-
-    fn buffer_id_from_proto(message: &proto::GetCodeActions) -> u64 {
-        message.buffer_id
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for OnTypeFormatting {
-    type Response = Option<Transaction>;
-    type LspRequest = lsp::request::OnTypeFormatting;
-    type ProtoRequest = proto::OnTypeFormatting;
-
-    fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
-        let Some(on_type_formatting_options) =
-            &server_capabilities.document_on_type_formatting_provider
-        else {
-            return false;
-        };
-        on_type_formatting_options
-            .first_trigger_character
-            .contains(&self.trigger)
-            || on_type_formatting_options
-                .more_trigger_character
-                .iter()
-                .flatten()
-                .any(|chars| chars.contains(&self.trigger))
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::DocumentOnTypeFormattingParams {
-        lsp::DocumentOnTypeFormattingParams {
-            text_document_position: lsp::TextDocumentPositionParams::new(
-                lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
-                point_to_lsp(self.position),
-            ),
-            ch: self.trigger.clone(),
-            options: lsp_formatting_options(self.options.tab_size),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<Vec<lsp::TextEdit>>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> Result<Option<Transaction>> {
-        if let Some(edits) = message {
-            let (lsp_adapter, lsp_server) =
-                language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-            Project::deserialize_edits(
-                project,
-                buffer,
-                edits,
-                self.push_to_history,
-                lsp_adapter,
-                lsp_server,
-                &mut cx,
-            )
-            .await
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
-        proto::OnTypeFormatting {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-            trigger: self.trigger.clone(),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::OnTypeFormatting,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .ok_or_else(|| anyhow!("invalid position"))?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        let tab_size = buffer.update(&mut cx, |buffer, cx| {
-            language_settings(buffer.language(), buffer.file(), cx).tab_size
-        })?;
-
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-            trigger: message.trigger.clone(),
-            options: lsp_formatting_options(tab_size.get()).into(),
-            push_to_history: false,
-        })
-    }
-
-    fn response_to_proto(
-        response: Option<Transaction>,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::OnTypeFormattingResponse {
-        proto::OnTypeFormattingResponse {
-            transaction: response
-                .map(|transaction| language::proto::serialize_transaction(&transaction)),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::OnTypeFormattingResponse,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        _: AsyncAppContext,
-    ) -> Result<Option<Transaction>> {
-        let Some(transaction) = message.transaction else {
-            return Ok(None);
-        };
-        Ok(Some(language::proto::deserialize_transaction(transaction)?))
-    }
-
-    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
-        message.buffer_id
-    }
-}
-
-impl InlayHints {
-    pub async fn lsp_to_project_hint(
-        lsp_hint: lsp::InlayHint,
-        buffer_handle: &Model<Buffer>,
-        server_id: LanguageServerId,
-        resolve_state: ResolveState,
-        force_no_type_left_padding: bool,
-        cx: &mut AsyncAppContext,
-    ) -> anyhow::Result<InlayHint> {
-        let kind = lsp_hint.kind.and_then(|kind| match kind {
-            lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
-            lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
-            _ => None,
-        });
-
-        let position = buffer_handle.update(cx, |buffer, _| {
-            let position = buffer.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
-            if kind == Some(InlayHintKind::Parameter) {
-                buffer.anchor_before(position)
-            } else {
-                buffer.anchor_after(position)
-            }
-        })?;
-        let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id)
-            .await
-            .context("lsp to project inlay hint conversion")?;
-        let padding_left = if force_no_type_left_padding && kind == Some(InlayHintKind::Type) {
-            false
-        } else {
-            lsp_hint.padding_left.unwrap_or(false)
-        };
-
-        Ok(InlayHint {
-            position,
-            padding_left,
-            padding_right: lsp_hint.padding_right.unwrap_or(false),
-            label,
-            kind,
-            tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip {
-                lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s),
-                lsp::InlayHintTooltip::MarkupContent(markup_content) => {
-                    InlayHintTooltip::MarkupContent(MarkupContent {
-                        kind: match markup_content.kind {
-                            lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
-                            lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
-                        },
-                        value: markup_content.value,
-                    })
-                }
-            }),
-            resolve_state,
-        })
-    }
-
-    async fn lsp_inlay_label_to_project(
-        lsp_label: lsp::InlayHintLabel,
-        server_id: LanguageServerId,
-    ) -> anyhow::Result<InlayHintLabel> {
-        let label = match lsp_label {
-            lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s),
-            lsp::InlayHintLabel::LabelParts(lsp_parts) => {
-                let mut parts = Vec::with_capacity(lsp_parts.len());
-                for lsp_part in lsp_parts {
-                    parts.push(InlayHintLabelPart {
-                        value: lsp_part.value,
-                        tooltip: lsp_part.tooltip.map(|tooltip| match tooltip {
-                            lsp::InlayHintLabelPartTooltip::String(s) => {
-                                InlayHintLabelPartTooltip::String(s)
-                            }
-                            lsp::InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
-                                InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
-                                    kind: match markup_content.kind {
-                                        lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
-                                        lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
-                                    },
-                                    value: markup_content.value,
-                                })
-                            }
-                        }),
-                        location: Some(server_id).zip(lsp_part.location),
-                    });
-                }
-                InlayHintLabel::LabelParts(parts)
-            }
-        };
-
-        Ok(label)
-    }
-
-    pub fn project_to_proto_hint(response_hint: InlayHint) -> proto::InlayHint {
-        let (state, lsp_resolve_state) = match response_hint.resolve_state {
-            ResolveState::Resolved => (0, None),
-            ResolveState::CanResolve(server_id, resolve_data) => (
-                1,
-                resolve_data
-                    .map(|json_data| {
-                        serde_json::to_string(&json_data)
-                            .expect("failed to serialize resolve json data")
-                    })
-                    .map(|value| proto::resolve_state::LspResolveState {
-                        server_id: server_id.0 as u64,
-                        value,
-                    }),
-            ),
-            ResolveState::Resolving => (2, None),
-        };
-        let resolve_state = Some(proto::ResolveState {
-            state,
-            lsp_resolve_state,
-        });
-        proto::InlayHint {
-            position: Some(language::proto::serialize_anchor(&response_hint.position)),
-            padding_left: response_hint.padding_left,
-            padding_right: response_hint.padding_right,
-            label: Some(proto::InlayHintLabel {
-                label: Some(match response_hint.label {
-                    InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
-                    InlayHintLabel::LabelParts(label_parts) => {
-                        proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
-                            parts: label_parts.into_iter().map(|label_part| {
-                                let location_url = label_part.location.as_ref().map(|(_, location)| location.uri.to_string());
-                                let location_range_start = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.start).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
-                                let location_range_end = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.end).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
-                                proto::InlayHintLabelPart {
-                                value: label_part.value,
-                                tooltip: label_part.tooltip.map(|tooltip| {
-                                    let proto_tooltip = match tooltip {
-                                        InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
-                                        InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
-                                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
-                                            value: markup_content.value,
-                                        }),
-                                    };
-                                    proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
-                                }),
-                                location_url,
-                                location_range_start,
-                                location_range_end,
-                                language_server_id: label_part.location.as_ref().map(|(server_id, _)| server_id.0 as u64),
-                            }}).collect()
-                        })
-                    }
-                }),
-            }),
-            kind: response_hint.kind.map(|kind| kind.name().to_string()),
-            tooltip: response_hint.tooltip.map(|response_tooltip| {
-                let proto_tooltip = match response_tooltip {
-                    InlayHintTooltip::String(s) => proto::inlay_hint_tooltip::Content::Value(s),
-                    InlayHintTooltip::MarkupContent(markup_content) => {
-                        proto::inlay_hint_tooltip::Content::MarkupContent(proto::MarkupContent {
-                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
-                            value: markup_content.value,
-                        })
-                    }
-                };
-                proto::InlayHintTooltip {
-                    content: Some(proto_tooltip),
-                }
-            }),
-            resolve_state,
-        }
-    }
-
-    pub fn proto_to_project_hint(message_hint: proto::InlayHint) -> anyhow::Result<InlayHint> {
-        let resolve_state = message_hint.resolve_state.as_ref().unwrap_or_else(|| {
-            panic!("incorrect proto inlay hint message: no resolve state in hint {message_hint:?}",)
-        });
-        let resolve_state_data = resolve_state
-            .lsp_resolve_state.as_ref()
-            .map(|lsp_resolve_state| {
-                serde_json::from_str::<Option<lsp::LSPAny>>(&lsp_resolve_state.value)
-                    .with_context(|| format!("incorrect proto inlay hint message: non-json resolve state {lsp_resolve_state:?}"))
-                    .map(|state| (LanguageServerId(lsp_resolve_state.server_id as usize), state))
-            })
-            .transpose()?;
-        let resolve_state = match resolve_state.state {
-            0 => ResolveState::Resolved,
-            1 => {
-                let (server_id, lsp_resolve_state) = resolve_state_data.with_context(|| {
-                    format!(
-                        "No lsp resolve data for the hint that can be resolved: {message_hint:?}"
-                    )
-                })?;
-                ResolveState::CanResolve(server_id, lsp_resolve_state)
-            }
-            2 => ResolveState::Resolving,
-            invalid => {
-                anyhow::bail!("Unexpected resolve state {invalid} for hint {message_hint:?}")
-            }
-        };
-        Ok(InlayHint {
-            position: message_hint
-                .position
-                .and_then(language::proto::deserialize_anchor)
-                .context("invalid position")?,
-            label: match message_hint
-                .label
-                .and_then(|label| label.label)
-                .context("missing label")?
-            {
-                proto::inlay_hint_label::Label::Value(s) => InlayHintLabel::String(s),
-                proto::inlay_hint_label::Label::LabelParts(parts) => {
-                    let mut label_parts = Vec::new();
-                    for part in parts.parts {
-                        label_parts.push(InlayHintLabelPart {
-                            value: part.value,
-                            tooltip: part.tooltip.map(|tooltip| match tooltip.content {
-                                Some(proto::inlay_hint_label_part_tooltip::Content::Value(s)) => {
-                                    InlayHintLabelPartTooltip::String(s)
-                                }
-                                Some(
-                                    proto::inlay_hint_label_part_tooltip::Content::MarkupContent(
-                                        markup_content,
-                                    ),
-                                ) => InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
-                                    kind: if markup_content.is_markdown {
-                                        HoverBlockKind::Markdown
-                                    } else {
-                                        HoverBlockKind::PlainText
-                                    },
-                                    value: markup_content.value,
-                                }),
-                                None => InlayHintLabelPartTooltip::String(String::new()),
-                            }),
-                            location: {
-                                match part
-                                    .location_url
-                                    .zip(
-                                        part.location_range_start.and_then(|start| {
-                                            Some(start..part.location_range_end?)
-                                        }),
-                                    )
-                                    .zip(part.language_server_id)
-                                {
-                                    Some(((uri, range), server_id)) => Some((
-                                        LanguageServerId(server_id as usize),
-                                        lsp::Location {
-                                            uri: lsp::Url::parse(&uri)
-                                                .context("invalid uri in hint part {part:?}")?,
-                                            range: lsp::Range::new(
-                                                point_to_lsp(PointUtf16::new(
-                                                    range.start.row,
-                                                    range.start.column,
-                                                )),
-                                                point_to_lsp(PointUtf16::new(
-                                                    range.end.row,
-                                                    range.end.column,
-                                                )),
-                                            ),
-                                        },
-                                    )),
-                                    None => None,
-                                }
-                            },
-                        });
-                    }
-
-                    InlayHintLabel::LabelParts(label_parts)
-                }
-            },
-            padding_left: message_hint.padding_left,
-            padding_right: message_hint.padding_right,
-            kind: message_hint
-                .kind
-                .as_deref()
-                .and_then(InlayHintKind::from_name),
-            tooltip: message_hint.tooltip.and_then(|tooltip| {
-                Some(match tooltip.content? {
-                    proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s),
-                    proto::inlay_hint_tooltip::Content::MarkupContent(markup_content) => {
-                        InlayHintTooltip::MarkupContent(MarkupContent {
-                            kind: if markup_content.is_markdown {
-                                HoverBlockKind::Markdown
-                            } else {
-                                HoverBlockKind::PlainText
-                            },
-                            value: markup_content.value,
-                        })
-                    }
-                })
-            }),
-            resolve_state,
-        })
-    }
-
-    pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp::InlayHint {
-        lsp::InlayHint {
-            position: point_to_lsp(hint.position.to_point_utf16(snapshot)),
-            kind: hint.kind.map(|kind| match kind {
-                InlayHintKind::Type => lsp::InlayHintKind::TYPE,
-                InlayHintKind::Parameter => lsp::InlayHintKind::PARAMETER,
-            }),
-            text_edits: None,
-            tooltip: hint.tooltip.and_then(|tooltip| {
-                Some(match tooltip {
-                    InlayHintTooltip::String(s) => lsp::InlayHintTooltip::String(s),
-                    InlayHintTooltip::MarkupContent(markup_content) => {
-                        lsp::InlayHintTooltip::MarkupContent(lsp::MarkupContent {
-                            kind: match markup_content.kind {
-                                HoverBlockKind::PlainText => lsp::MarkupKind::PlainText,
-                                HoverBlockKind::Markdown => lsp::MarkupKind::Markdown,
-                                HoverBlockKind::Code { .. } => return None,
-                            },
-                            value: markup_content.value,
-                        })
-                    }
-                })
-            }),
-            label: match hint.label {
-                InlayHintLabel::String(s) => lsp::InlayHintLabel::String(s),
-                InlayHintLabel::LabelParts(label_parts) => lsp::InlayHintLabel::LabelParts(
-                    label_parts
-                        .into_iter()
-                        .map(|part| lsp::InlayHintLabelPart {
-                            value: part.value,
-                            tooltip: part.tooltip.and_then(|tooltip| {
-                                Some(match tooltip {
-                                    InlayHintLabelPartTooltip::String(s) => {
-                                        lsp::InlayHintLabelPartTooltip::String(s)
-                                    }
-                                    InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
-                                        lsp::InlayHintLabelPartTooltip::MarkupContent(
-                                            lsp::MarkupContent {
-                                                kind: match markup_content.kind {
-                                                    HoverBlockKind::PlainText => {
-                                                        lsp::MarkupKind::PlainText
-                                                    }
-                                                    HoverBlockKind::Markdown => {
-                                                        lsp::MarkupKind::Markdown
-                                                    }
-                                                    HoverBlockKind::Code { .. } => return None,
-                                                },
-                                                value: markup_content.value,
-                                            },
-                                        )
-                                    }
-                                })
-                            }),
-                            location: part.location.map(|(_, location)| location),
-                            command: None,
-                        })
-                        .collect(),
-                ),
-            },
-            padding_left: Some(hint.padding_left),
-            padding_right: Some(hint.padding_right),
-            data: match hint.resolve_state {
-                ResolveState::CanResolve(_, data) => data,
-                ResolveState::Resolving | ResolveState::Resolved => None,
-            },
-        }
-    }
-
-    pub fn can_resolve_inlays(capabilities: &ServerCapabilities) -> bool {
-        capabilities
-            .inlay_hint_provider
-            .as_ref()
-            .and_then(|options| match options {
-                OneOf::Left(_is_supported) => None,
-                OneOf::Right(capabilities) => match capabilities {
-                    lsp::InlayHintServerCapabilities::Options(o) => o.resolve_provider,
-                    lsp::InlayHintServerCapabilities::RegistrationOptions(o) => {
-                        o.inlay_hint_options.resolve_provider
-                    }
-                },
-            })
-            .unwrap_or(false)
-    }
-}
-
-#[async_trait(?Send)]
-impl LspCommand for InlayHints {
-    type Response = Vec<InlayHint>;
-    type LspRequest = lsp::InlayHintRequest;
-    type ProtoRequest = proto::InlayHints;
-
-    fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
-        let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else {
-            return false;
-        };
-        match inlay_hint_provider {
-            lsp::OneOf::Left(enabled) => *enabled,
-            lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities {
-                lsp::InlayHintServerCapabilities::Options(_) => true,
-                lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false,
-            },
-        }
-    }
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        buffer: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> lsp::InlayHintParams {
-        lsp::InlayHintParams {
-            text_document: lsp::TextDocumentIdentifier {
-                uri: lsp::Url::from_file_path(path).unwrap(),
-            },
-            range: range_to_lsp(self.range.to_point_utf16(buffer)),
-            work_done_progress_params: Default::default(),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<Vec<lsp::InlayHint>>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        mut cx: AsyncAppContext,
-    ) -> anyhow::Result<Vec<InlayHint>> {
-        let (lsp_adapter, lsp_server) =
-            language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
-        // `typescript-language-server` adds padding to the left for type hints, turning
-        // `const foo: boolean` into `const foo : boolean` which looks odd.
-        // `rust-analyzer` does not have the padding for this case, and we have to accomodate both.
-        //
-        // We could trim the whole string, but being pessimistic on par with the situation above,
-        // there might be a hint with multiple whitespaces at the end(s) which we need to display properly.
-        // Hence let's use a heuristic first to handle the most awkward case and look for more.
-        let force_no_type_left_padding =
-            lsp_adapter.name.0.as_ref() == "typescript-language-server";
-
-        let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
-            let resolve_state = if InlayHints::can_resolve_inlays(lsp_server.capabilities()) {
-                ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
-            } else {
-                ResolveState::Resolved
-            };
-
-            let buffer = buffer.clone();
-            cx.spawn(move |mut cx| async move {
-                InlayHints::lsp_to_project_hint(
-                    lsp_hint,
-                    &buffer,
-                    server_id,
-                    resolve_state,
-                    force_no_type_left_padding,
-                    &mut cx,
-                )
-                .await
-            })
-        });
-        future::join_all(hints)
-            .await
-            .into_iter()
-            .collect::<anyhow::Result<_>>()
-            .context("lsp to project inlay hints conversion")
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints {
-        proto::InlayHints {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            start: Some(language::proto::serialize_anchor(&self.range.start)),
-            end: Some(language::proto::serialize_anchor(&self.range.end)),
-            version: serialize_version(&buffer.version()),
-        }
-    }
-
-    async fn from_proto(
-        message: proto::InlayHints,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Self> {
-        let start = message
-            .start
-            .and_then(language::proto::deserialize_anchor)
-            .context("invalid start")?;
-        let end = message
-            .end
-            .and_then(language::proto::deserialize_anchor)
-            .context("invalid end")?;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        Ok(Self { range: start..end })
-    }
-
-    fn response_to_proto(
-        response: Vec<InlayHint>,
-        _: &mut Project,
-        _: PeerId,
-        buffer_version: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::InlayHintsResponse {
-        proto::InlayHintsResponse {
-            hints: response
-                .into_iter()
-                .map(|response_hint| InlayHints::project_to_proto_hint(response_hint))
-                .collect(),
-            version: serialize_version(buffer_version),
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::InlayHintsResponse,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> anyhow::Result<Vec<InlayHint>> {
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&message.version))
-            })?
-            .await?;
-
-        let mut hints = Vec::new();
-        for message_hint in message.hints {
-            hints.push(InlayHints::proto_to_project_hint(message_hint)?);
-        }
-
-        Ok(hints)
-    }
-
-    fn buffer_id_from_proto(message: &proto::InlayHints) -> u64 {
-        message.buffer_id
-    }
-}

crates/project2/src/lsp_ext_command.rs 🔗

@@ -1,137 +0,0 @@
-use std::{path::Path, sync::Arc};
-
-use anyhow::Context;
-use async_trait::async_trait;
-use gpui::{AppContext, AsyncAppContext, Model};
-use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
-use lsp::{LanguageServer, LanguageServerId};
-use rpc::proto::{self, PeerId};
-use serde::{Deserialize, Serialize};
-use text::{PointUtf16, ToPointUtf16};
-
-use crate::{lsp_command::LspCommand, Project};
-
-pub enum LspExpandMacro {}
-
-impl lsp::request::Request for LspExpandMacro {
-    type Params = ExpandMacroParams;
-    type Result = Option<ExpandedMacro>;
-    const METHOD: &'static str = "rust-analyzer/expandMacro";
-}
-
-#[derive(Deserialize, Serialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub struct ExpandMacroParams {
-    pub text_document: lsp::TextDocumentIdentifier,
-    pub position: lsp::Position,
-}
-
-#[derive(Default, Deserialize, Serialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub struct ExpandedMacro {
-    pub name: String,
-    pub expansion: String,
-}
-
-impl ExpandedMacro {
-    pub fn is_empty(&self) -> bool {
-        self.name.is_empty() && self.expansion.is_empty()
-    }
-}
-
-pub struct ExpandMacro {
-    pub position: PointUtf16,
-}
-
-#[async_trait(?Send)]
-impl LspCommand for ExpandMacro {
-    type Response = ExpandedMacro;
-    type LspRequest = LspExpandMacro;
-    type ProtoRequest = proto::LspExtExpandMacro;
-
-    fn to_lsp(
-        &self,
-        path: &Path,
-        _: &Buffer,
-        _: &Arc<LanguageServer>,
-        _: &AppContext,
-    ) -> ExpandMacroParams {
-        ExpandMacroParams {
-            text_document: lsp::TextDocumentIdentifier {
-                uri: lsp::Url::from_file_path(path).unwrap(),
-            },
-            position: point_to_lsp(self.position),
-        }
-    }
-
-    async fn response_from_lsp(
-        self,
-        message: Option<ExpandedMacro>,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        _: LanguageServerId,
-        _: AsyncAppContext,
-    ) -> anyhow::Result<ExpandedMacro> {
-        Ok(message
-            .map(|message| ExpandedMacro {
-                name: message.name,
-                expansion: message.expansion,
-            })
-            .unwrap_or_default())
-    }
-
-    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
-        proto::LspExtExpandMacro {
-            project_id,
-            buffer_id: buffer.remote_id(),
-            position: Some(language::proto::serialize_anchor(
-                &buffer.anchor_before(self.position),
-            )),
-        }
-    }
-
-    async fn from_proto(
-        message: Self::ProtoRequest,
-        _: Model<Project>,
-        buffer: Model<Buffer>,
-        mut cx: AsyncAppContext,
-    ) -> anyhow::Result<Self> {
-        let position = message
-            .position
-            .and_then(deserialize_anchor)
-            .context("invalid position")?;
-        Ok(Self {
-            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
-        })
-    }
-
-    fn response_to_proto(
-        response: ExpandedMacro,
-        _: &mut Project,
-        _: PeerId,
-        _: &clock::Global,
-        _: &mut AppContext,
-    ) -> proto::LspExtExpandMacroResponse {
-        proto::LspExtExpandMacroResponse {
-            name: response.name,
-            expansion: response.expansion,
-        }
-    }
-
-    async fn response_from_proto(
-        self,
-        message: proto::LspExtExpandMacroResponse,
-        _: Model<Project>,
-        _: Model<Buffer>,
-        _: AsyncAppContext,
-    ) -> anyhow::Result<ExpandedMacro> {
-        Ok(ExpandedMacro {
-            name: message.name,
-            expansion: message.expansion,
-        })
-    }
-
-    fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
-        message.buffer_id
-    }
-}

crates/project2/src/prettier_support.rs 🔗

@@ -1,772 +0,0 @@
-use std::{
-    ops::ControlFlow,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-
-use anyhow::Context;
-use collections::HashSet;
-use fs::Fs;
-use futures::{
-    future::{self, Shared},
-    FutureExt,
-};
-use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
-use language::{
-    language_settings::{Formatter, LanguageSettings},
-    Buffer, Language, LanguageServerName, LocalFile,
-};
-use lsp::LanguageServerId;
-use node_runtime::NodeRuntime;
-use prettier::Prettier;
-use util::{paths::DEFAULT_PRETTIER_DIR, ResultExt, TryFutureExt};
-
-use crate::{
-    Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,
-};
-
-pub fn prettier_plugins_for_language(
-    language: &Language,
-    language_settings: &LanguageSettings,
-) -> Option<HashSet<&'static str>> {
-    match &language_settings.formatter {
-        Formatter::Prettier { .. } | Formatter::Auto => {}
-        Formatter::LanguageServer | Formatter::External { .. } => return None,
-    };
-    let mut prettier_plugins = None;
-    if language.prettier_parser_name().is_some() {
-        prettier_plugins
-            .get_or_insert_with(|| HashSet::default())
-            .extend(
-                language
-                    .lsp_adapters()
-                    .iter()
-                    .flat_map(|adapter| adapter.prettier_plugins()),
-            )
-    }
-
-    prettier_plugins
-}
-
-pub(super) async fn format_with_prettier(
-    project: &WeakModel<Project>,
-    buffer: &Model<Buffer>,
-    cx: &mut AsyncAppContext,
-) -> Option<FormatOperation> {
-    if let Some((prettier_path, prettier_task)) = project
-        .update(cx, |project, cx| {
-            project.prettier_instance_for_buffer(buffer, cx)
-        })
-        .ok()?
-        .await
-    {
-        match prettier_task.await {
-            Ok(prettier) => {
-                let buffer_path = buffer
-                    .update(cx, |buffer, cx| {
-                        File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
-                    })
-                    .ok()?;
-                match prettier.format(buffer, buffer_path, cx).await {
-                    Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
-                    Err(e) => {
-                        log::error!(
-                            "Prettier instance from {prettier_path:?} failed to format a buffer: {e:#}"
-                        );
-                    }
-                }
-            }
-            Err(e) => project
-                .update(cx, |project, _| {
-                    let instance_to_update = match prettier_path {
-                        Some(prettier_path) => {
-                            log::error!(
-                            "Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
-                        );
-                            project.prettier_instances.get_mut(&prettier_path)
-                        }
-                        None => {
-                            log::error!("Default prettier instance failed to spawn: {e:#}");
-                            match &mut project.default_prettier.prettier {
-                                PrettierInstallation::NotInstalled { .. } => None,
-                                PrettierInstallation::Installed(instance) => Some(instance),
-                            }
-                        }
-                    };
-
-                    if let Some(instance) = instance_to_update {
-                        instance.attempt += 1;
-                        instance.prettier = None;
-                    }
-                })
-                .ok()?,
-        }
-    }
-
-    None
-}
-
-pub struct DefaultPrettier {
-    prettier: PrettierInstallation,
-    installed_plugins: HashSet<&'static str>,
-}
-
-pub enum PrettierInstallation {
-    NotInstalled {
-        attempts: usize,
-        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
-        not_installed_plugins: HashSet<&'static str>,
-    },
-    Installed(PrettierInstance),
-}
-
-pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
-
-#[derive(Clone)]
-pub struct PrettierInstance {
-    attempt: usize,
-    prettier: Option<PrettierTask>,
-}
-
-impl Default for DefaultPrettier {
-    fn default() -> Self {
-        Self {
-            prettier: PrettierInstallation::NotInstalled {
-                attempts: 0,
-                installation_task: None,
-                not_installed_plugins: HashSet::default(),
-            },
-            installed_plugins: HashSet::default(),
-        }
-    }
-}
-
-impl DefaultPrettier {
-    pub fn instance(&self) -> Option<&PrettierInstance> {
-        if let PrettierInstallation::Installed(instance) = &self.prettier {
-            Some(instance)
-        } else {
-            None
-        }
-    }
-
-    pub fn prettier_task(
-        &mut self,
-        node: &Arc<dyn NodeRuntime>,
-        worktree_id: Option<WorktreeId>,
-        cx: &mut ModelContext<'_, Project>,
-    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
-        match &mut self.prettier {
-            PrettierInstallation::NotInstalled { .. } => {
-                Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
-            }
-            PrettierInstallation::Installed(existing_instance) => {
-                existing_instance.prettier_task(node, None, worktree_id, cx)
-            }
-        }
-    }
-}
-
-impl PrettierInstance {
-    pub fn prettier_task(
-        &mut self,
-        node: &Arc<dyn NodeRuntime>,
-        prettier_dir: Option<&Path>,
-        worktree_id: Option<WorktreeId>,
-        cx: &mut ModelContext<'_, Project>,
-    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
-        if self.attempt > prettier::FAIL_THRESHOLD {
-            match prettier_dir {
-                Some(prettier_dir) => log::warn!(
-                    "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
-                ),
-                None => log::warn!("Default prettier exceeded launch threshold, not starting"),
-            }
-            return None;
-        }
-        Some(match &self.prettier {
-            Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
-            None => match prettier_dir {
-                Some(prettier_dir) => {
-                    let new_task = start_prettier(
-                        Arc::clone(node),
-                        prettier_dir.to_path_buf(),
-                        worktree_id,
-                        cx,
-                    );
-                    self.attempt += 1;
-                    self.prettier = Some(new_task.clone());
-                    Task::ready(Ok(new_task))
-                }
-                None => {
-                    self.attempt += 1;
-                    let node = Arc::clone(node);
-                    cx.spawn(|project, mut cx| async move {
-                        project
-                            .update(&mut cx, |_, cx| {
-                                start_default_prettier(node, worktree_id, cx)
-                            })?
-                            .await
-                    })
-                }
-            },
-        })
-    }
-}
-
-fn start_default_prettier(
-    node: Arc<dyn NodeRuntime>,
-    worktree_id: Option<WorktreeId>,
-    cx: &mut ModelContext<'_, Project>,
-) -> Task<anyhow::Result<PrettierTask>> {
-    cx.spawn(|project, mut cx| async move {
-        loop {
-            let installation_task = project.update(&mut cx, |project, _| {
-                match &project.default_prettier.prettier {
-                    PrettierInstallation::NotInstalled {
-                        installation_task, ..
-                    } => ControlFlow::Continue(installation_task.clone()),
-                    PrettierInstallation::Installed(default_prettier) => {
-                        ControlFlow::Break(default_prettier.clone())
-                    }
-                }
-            })?;
-            match installation_task {
-                ControlFlow::Continue(None) => {
-                    anyhow::bail!("Default prettier is not installed and cannot be started")
-                }
-                ControlFlow::Continue(Some(installation_task)) => {
-                    log::info!("Waiting for default prettier to install");
-                    if let Err(e) = installation_task.await {
-                        project.update(&mut cx, |project, _| {
-                            if let PrettierInstallation::NotInstalled {
-                                installation_task,
-                                attempts,
-                                ..
-                            } = &mut project.default_prettier.prettier
-                            {
-                                *installation_task = None;
-                                *attempts += 1;
-                            }
-                        })?;
-                        anyhow::bail!(
-                            "Cannot start default prettier due to its installation failure: {e:#}"
-                        );
-                    }
-                    let new_default_prettier = project.update(&mut cx, |project, cx| {
-                        let new_default_prettier =
-                            start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
-                        project.default_prettier.prettier =
-                            PrettierInstallation::Installed(PrettierInstance {
-                                attempt: 0,
-                                prettier: Some(new_default_prettier.clone()),
-                            });
-                        new_default_prettier
-                    })?;
-                    return Ok(new_default_prettier);
-                }
-                ControlFlow::Break(instance) => match instance.prettier {
-                    Some(instance) => return Ok(instance),
-                    None => {
-                        let new_default_prettier = project.update(&mut cx, |project, cx| {
-                            let new_default_prettier =
-                                start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
-                            project.default_prettier.prettier =
-                                PrettierInstallation::Installed(PrettierInstance {
-                                    attempt: instance.attempt + 1,
-                                    prettier: Some(new_default_prettier.clone()),
-                                });
-                            new_default_prettier
-                        })?;
-                        return Ok(new_default_prettier);
-                    }
-                },
-            }
-        }
-    })
-}
-
-fn start_prettier(
-    node: Arc<dyn NodeRuntime>,
-    prettier_dir: PathBuf,
-    worktree_id: Option<WorktreeId>,
-    cx: &mut ModelContext<'_, Project>,
-) -> PrettierTask {
-    cx.spawn(|project, mut cx| async move {
-        log::info!("Starting prettier at path {prettier_dir:?}");
-        let new_server_id = project.update(&mut cx, |project, _| {
-            project.languages.next_language_server_id()
-        })?;
-
-        let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
-            .await
-            .context("default prettier spawn")
-            .map(Arc::new)
-            .map_err(Arc::new)?;
-        register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
-        Ok(new_prettier)
-    })
-    .shared()
-}
-
-fn register_new_prettier(
-    project: &WeakModel<Project>,
-    prettier: &Prettier,
-    worktree_id: Option<WorktreeId>,
-    new_server_id: LanguageServerId,
-    cx: &mut AsyncAppContext,
-) {
-    let prettier_dir = prettier.prettier_dir();
-    let is_default = prettier.is_default();
-    if is_default {
-        log::info!("Started default prettier in {prettier_dir:?}");
-    } else {
-        log::info!("Started prettier in {prettier_dir:?}");
-    }
-    if let Some(prettier_server) = prettier.server() {
-        project
-            .update(cx, |project, cx| {
-                let name = if is_default {
-                    LanguageServerName(Arc::from("prettier (default)"))
-                } else {
-                    let worktree_path = worktree_id
-                        .and_then(|id| project.worktree_for_id(id, cx))
-                        .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
-                    let name = match worktree_path {
-                        Some(worktree_path) => {
-                            if prettier_dir == worktree_path.as_ref() {
-                                let name = prettier_dir
-                                    .file_name()
-                                    .and_then(|name| name.to_str())
-                                    .unwrap_or_default();
-                                format!("prettier ({name})")
-                            } else {
-                                let dir_to_display = prettier_dir
-                                    .strip_prefix(worktree_path.as_ref())
-                                    .ok()
-                                    .unwrap_or(prettier_dir);
-                                format!("prettier ({})", dir_to_display.display())
-                            }
-                        }
-                        None => format!("prettier ({})", prettier_dir.display()),
-                    };
-                    LanguageServerName(Arc::from(name))
-                };
-                project
-                    .supplementary_language_servers
-                    .insert(new_server_id, (name, Arc::clone(prettier_server)));
-                cx.emit(Event::LanguageServerAdded(new_server_id));
-            })
-            .ok();
-    }
-}
-
-async fn install_prettier_packages(
-    plugins_to_install: HashSet<&'static str>,
-    node: Arc<dyn NodeRuntime>,
-) -> anyhow::Result<()> {
-    let packages_to_versions =
-        future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
-            |package_name| async {
-                let returned_package_name = package_name.to_string();
-                let latest_version = node
-                    .npm_package_latest_version(package_name)
-                    .await
-                    .with_context(|| {
-                        format!("fetching latest npm version for package {returned_package_name}")
-                    })?;
-                anyhow::Ok((returned_package_name, latest_version))
-            },
-        ))
-        .await
-        .context("fetching latest npm versions")?;
-
-    log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
-    let borrowed_packages = packages_to_versions
-        .iter()
-        .map(|(package, version)| (package.as_str(), version.as_str()))
-        .collect::<Vec<_>>();
-    node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages)
-        .await
-        .context("fetching formatter packages")?;
-    anyhow::Ok(())
-}
-
-async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
-    let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
-    fs.save(
-        &prettier_wrapper_path,
-        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
-        text::LineEnding::Unix,
-    )
-    .await
-    .with_context(|| {
-        format!(
-            "writing {} file at {prettier_wrapper_path:?}",
-            prettier::PRETTIER_SERVER_FILE
-        )
-    })?;
-    Ok(())
-}
-
-impl Project {
-    pub fn update_prettier_settings(
-        &self,
-        worktree: &Model<Worktree>,
-        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
-        cx: &mut ModelContext<'_, Project>,
-    ) {
-        let prettier_config_files = Prettier::CONFIG_FILE_NAMES
-            .iter()
-            .map(Path::new)
-            .collect::<HashSet<_>>();
-
-        let prettier_config_file_changed = changes
-            .iter()
-            .filter(|(_, _, change)| !matches!(change, PathChange::Loaded))
-            .filter(|(path, _, _)| {
-                !path
-                    .components()
-                    .any(|component| component.as_os_str().to_string_lossy() == "node_modules")
-            })
-            .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));
-        let current_worktree_id = worktree.read(cx).id();
-        if let Some((config_path, _, _)) = prettier_config_file_changed {
-            log::info!(
-                "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
-            );
-            let prettiers_to_reload =
-                self.prettiers_per_worktree
-                    .get(&current_worktree_id)
-                    .iter()
-                    .flat_map(|prettier_paths| prettier_paths.iter())
-                    .flatten()
-                    .filter_map(|prettier_path| {
-                        Some((
-                            current_worktree_id,
-                            Some(prettier_path.clone()),
-                            self.prettier_instances.get(prettier_path)?.clone(),
-                        ))
-                    })
-                    .chain(self.default_prettier.instance().map(|default_prettier| {
-                        (current_worktree_id, None, default_prettier.clone())
-                    }))
-                    .collect::<Vec<_>>();
-
-            cx.background_executor()
-                .spawn(async move {
-                    let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
-                        async move {
-                            if let Some(instance) = prettier_instance.prettier {
-                                match instance.await {
-                                    Ok(prettier) => {
-                                        prettier.clear_cache().log_err().await;
-                                    },
-                                    Err(e) => {
-                                        match prettier_path {
-                                            Some(prettier_path) => log::error!(
-                                                "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
-                                            ),
-                                            None => log::error!(
-                                                "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
-                                            ),
-                                        }
-                                    },
-                                }
-                            }
-                        }
-                    }))
-                    .await;
-                })
-                .detach();
-        }
-    }
-
-    fn prettier_instance_for_buffer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
-        let buffer = buffer.read(cx);
-        let buffer_file = buffer.file();
-        let Some(buffer_language) = buffer.language() else {
-            return Task::ready(None);
-        };
-        if buffer_language.prettier_parser_name().is_none() {
-            return Task::ready(None);
-        }
-
-        if self.is_local() {
-            let Some(node) = self.node.as_ref().map(Arc::clone) else {
-                return Task::ready(None);
-            };
-            match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx)))
-            {
-                Some((worktree_id, buffer_path)) => {
-                    let fs = Arc::clone(&self.fs);
-                    let installed_prettiers = self.prettier_instances.keys().cloned().collect();
-                    return cx.spawn(|project, mut cx| async move {
-                        match cx
-                            .background_executor()
-                            .spawn(async move {
-                                Prettier::locate_prettier_installation(
-                                    fs.as_ref(),
-                                    &installed_prettiers,
-                                    &buffer_path,
-                                )
-                                .await
-                            })
-                            .await
-                        {
-                            Ok(ControlFlow::Break(())) => {
-                                return None;
-                            }
-                            Ok(ControlFlow::Continue(None)) => {
-                                let default_instance = project
-                                    .update(&mut cx, |project, cx| {
-                                        project
-                                            .prettiers_per_worktree
-                                            .entry(worktree_id)
-                                            .or_default()
-                                            .insert(None);
-                                        project.default_prettier.prettier_task(
-                                            &node,
-                                            Some(worktree_id),
-                                            cx,
-                                        )
-                                    })
-                                    .ok()?;
-                                Some((None, default_instance?.log_err().await?))
-                            }
-                            Ok(ControlFlow::Continue(Some(prettier_dir))) => {
-                                project
-                                    .update(&mut cx, |project, _| {
-                                        project
-                                            .prettiers_per_worktree
-                                            .entry(worktree_id)
-                                            .or_default()
-                                            .insert(Some(prettier_dir.clone()))
-                                    })
-                                    .ok()?;
-                                if let Some(prettier_task) = project
-                                    .update(&mut cx, |project, cx| {
-                                        project.prettier_instances.get_mut(&prettier_dir).map(
-                                            |existing_instance| {
-                                                existing_instance.prettier_task(
-                                                    &node,
-                                                    Some(&prettier_dir),
-                                                    Some(worktree_id),
-                                                    cx,
-                                                )
-                                            },
-                                        )
-                                    })
-                                    .ok()?
-                                {
-                                    log::debug!(
-                                        "Found already started prettier in {prettier_dir:?}"
-                                    );
-                                    return Some((
-                                        Some(prettier_dir),
-                                        prettier_task?.await.log_err()?,
-                                    ));
-                                }
-
-                                log::info!("Found prettier in {prettier_dir:?}, starting.");
-                                let new_prettier_task = project
-                                    .update(&mut cx, |project, cx| {
-                                        let new_prettier_task = start_prettier(
-                                            node,
-                                            prettier_dir.clone(),
-                                            Some(worktree_id),
-                                            cx,
-                                        );
-                                        project.prettier_instances.insert(
-                                            prettier_dir.clone(),
-                                            PrettierInstance {
-                                                attempt: 0,
-                                                prettier: Some(new_prettier_task.clone()),
-                                            },
-                                        );
-                                        new_prettier_task
-                                    })
-                                    .ok()?;
-                                Some((Some(prettier_dir), new_prettier_task))
-                            }
-                            Err(e) => {
-                                log::error!("Failed to determine prettier path for buffer: {e:#}");
-                                return None;
-                            }
-                        }
-                    });
-                }
-                None => {
-                    let new_task = self.default_prettier.prettier_task(&node, None, cx);
-                    return cx
-                        .spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) });
-                }
-            }
-        } else {
-            return Task::ready(None);
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn install_default_prettier(
-        &mut self,
-        _worktree: Option<WorktreeId>,
-        plugins: HashSet<&'static str>,
-        _cx: &mut ModelContext<Self>,
-    ) {
-        // suppress unused code warnings
-        let _ = install_prettier_packages;
-        let _ = save_prettier_server_file;
-
-        self.default_prettier.installed_plugins.extend(plugins);
-        self.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance {
-            attempt: 0,
-            prettier: None,
-        });
-    }
-
-    #[cfg(not(any(test, feature = "test-support")))]
-    pub fn install_default_prettier(
-        &mut self,
-        worktree: Option<WorktreeId>,
-        mut new_plugins: HashSet<&'static str>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let Some(node) = self.node.as_ref().cloned() else {
-            return;
-        };
-        log::info!("Initializing default prettier with plugins {new_plugins:?}");
-        let fs = Arc::clone(&self.fs);
-        let locate_prettier_installation = match worktree.and_then(|worktree_id| {
-            self.worktree_for_id(worktree_id, cx)
-                .map(|worktree| worktree.read(cx).abs_path())
-        }) {
-            Some(locate_from) => {
-                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
-                cx.background_executor().spawn(async move {
-                    Prettier::locate_prettier_installation(
-                        fs.as_ref(),
-                        &installed_prettiers,
-                        locate_from.as_ref(),
-                    )
-                    .await
-                })
-            }
-            None => Task::ready(Ok(ControlFlow::Continue(None))),
-        };
-        new_plugins.retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
-        let mut installation_attempt = 0;
-        let previous_installation_task = match &mut self.default_prettier.prettier {
-            PrettierInstallation::NotInstalled {
-                installation_task,
-                attempts,
-                not_installed_plugins,
-            } => {
-                installation_attempt = *attempts;
-                if installation_attempt > prettier::FAIL_THRESHOLD {
-                    *installation_task = None;
-                    log::warn!(
-                        "Default prettier installation had failed {installation_attempt} times, not attempting again",
-                    );
-                    return;
-                }
-                new_plugins.extend(not_installed_plugins.iter());
-                installation_task.clone()
-            }
-            PrettierInstallation::Installed { .. } => {
-                if new_plugins.is_empty() {
-                    return;
-                }
-                None
-            }
-        };
-
-        let plugins_to_install = new_plugins.clone();
-        let fs = Arc::clone(&self.fs);
-        let new_installation_task = cx
-            .spawn(|project, mut cx| async move {
-                match locate_prettier_installation
-                    .await
-                    .context("locate prettier installation")
-                    .map_err(Arc::new)?
-                {
-                    ControlFlow::Break(()) => return Ok(()),
-                    ControlFlow::Continue(prettier_path) => {
-                        if prettier_path.is_some() {
-                            new_plugins.clear();
-                        }
-                        let mut needs_install = false;
-                        if let Some(previous_installation_task) = previous_installation_task {
-                            if let Err(e) = previous_installation_task.await {
-                                log::error!("Failed to install default prettier: {e:#}");
-                                project.update(&mut cx, |project, _| {
-                                    if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut project.default_prettier.prettier {
-                                        *attempts += 1;
-                                        new_plugins.extend(not_installed_plugins.iter());
-                                        installation_attempt = *attempts;
-                                        needs_install = true;
-                                    };
-                                })?;
-                            }
-                        };
-                        if installation_attempt > prettier::FAIL_THRESHOLD {
-                            project.update(&mut cx, |project, _| {
-                                if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut project.default_prettier.prettier {
-                                    *installation_task = None;
-                                };
-                            })?;
-                            log::warn!(
-                                "Default prettier installation had failed {installation_attempt} times, not attempting again",
-                            );
-                            return Ok(());
-                        }
-                        project.update(&mut cx, |project, _| {
-                            new_plugins.retain(|plugin| {
-                                !project.default_prettier.installed_plugins.contains(plugin)
-                            });
-                            if let PrettierInstallation::NotInstalled { not_installed_plugins, .. } = &mut project.default_prettier.prettier {
-                                not_installed_plugins.retain(|plugin| {
-                                    !project.default_prettier.installed_plugins.contains(plugin)
-                                });
-                                not_installed_plugins.extend(new_plugins.iter());
-                            }
-                            needs_install |= !new_plugins.is_empty();
-                        })?;
-                        if needs_install {
-                            let installed_plugins = new_plugins.clone();
-                            cx.background_executor()
-                                .spawn(async move {
-                                    save_prettier_server_file(fs.as_ref()).await?;
-                                    install_prettier_packages(new_plugins, node).await
-                                })
-                                .await
-                                .context("prettier & plugins install")
-                                .map_err(Arc::new)?;
-                            log::info!("Initialized prettier with plugins: {installed_plugins:?}");
-                            project.update(&mut cx, |project, _| {
-                                project.default_prettier.prettier =
-                                    PrettierInstallation::Installed(PrettierInstance {
-                                        attempt: 0,
-                                        prettier: None,
-                                    });
-                                project.default_prettier
-                                    .installed_plugins
-                                    .extend(installed_plugins);
-                            })?;
-                        }
-                    }
-                }
-                Ok(())
-            })
-            .shared();
-        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
-            attempts: installation_attempt,
-            installation_task: Some(new_installation_task),
-            not_installed_plugins: plugins_to_install,
-        };
-    }
-}

crates/project2/src/project2.rs 🔗

@@ -1,8737 +0,0 @@
-mod ignore;
-pub mod lsp_command;
-pub mod lsp_ext_command;
-mod prettier_support;
-pub mod project_settings;
-pub mod search;
-pub mod terminals;
-pub mod worktree;
-
-#[cfg(test)]
-mod project_tests;
-#[cfg(test)]
-mod worktree_tests;
-
-use anyhow::{anyhow, Context as _, Result};
-use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
-use clock::ReplicaId;
-use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
-use copilot::Copilot;
-use futures::{
-    channel::{
-        mpsc::{self, UnboundedReceiver},
-        oneshot,
-    },
-    future::{try_join_all, Shared},
-    stream::FuturesUnordered,
-    AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
-};
-use globset::{Glob, GlobSet, GlobSetBuilder};
-use gpui::{
-    AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
-    Model, ModelContext, Task, WeakModel,
-};
-use itertools::Itertools;
-use language::{
-    language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
-    point_to_lsp,
-    proto::{
-        deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
-        serialize_anchor, serialize_version, split_operations,
-    },
-    range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
-    CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
-    File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
-    OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
-    ToOffset, ToPointUtf16, Transaction, Unclipped,
-};
-use log::error;
-use lsp::{
-    DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
-    DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf,
-};
-use lsp_command::*;
-use node_runtime::NodeRuntime;
-use parking_lot::Mutex;
-use postage::watch;
-use prettier_support::{DefaultPrettier, PrettierInstance};
-use project_settings::{LspSettings, ProjectSettings};
-use rand::prelude::*;
-use search::SearchQuery;
-use serde::Serialize;
-use settings::{Settings, SettingsStore};
-use sha2::{Digest, Sha256};
-use similar::{ChangeTag, TextDiff};
-use smol::channel::{Receiver, Sender};
-use smol::lock::Semaphore;
-use std::{
-    cmp::{self, Ordering},
-    convert::TryInto,
-    hash::Hash,
-    mem,
-    num::NonZeroU32,
-    ops::Range,
-    path::{self, Component, Path, PathBuf},
-    process::Stdio,
-    str,
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-    time::{Duration, Instant},
-};
-use terminals::Terminals;
-use text::Anchor;
-use util::{
-    debug_panic, defer, http::HttpClient, merge_json_value_into,
-    paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _,
-};
-
-pub use fs::*;
-#[cfg(any(test, feature = "test-support"))]
-pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
-pub use worktree::*;
-
-const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
-
-pub trait Item {
-    fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
-}
-
-// Language server state is stored across 3 collections:
-//     language_servers =>
-//         a mapping from unique server id to LanguageServerState which can either be a task for a
-//         server in the process of starting, or a running server with adapter and language server arcs
-//     language_server_ids => a mapping from worktreeId and server name to the unique server id
-//     language_server_statuses => a mapping from unique server id to the current server status
-//
-// Multiple worktrees can map to the same language server for example when you jump to the definition
-// of a file in the standard library. So language_server_ids is used to look up which server is active
-// for a given worktree and language server name
-//
-// When starting a language server, first the id map is checked to make sure a server isn't already available
-// for that worktree. If there is one, it finishes early. Otherwise, a new id is allocated and and
-// the Starting variant of LanguageServerState is stored in the language_servers map.
-pub struct Project {
-    worktrees: Vec<WorktreeHandle>,
-    active_entry: Option<ProjectEntryId>,
-    buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
-    languages: Arc<LanguageRegistry>,
-    supplementary_language_servers:
-        HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
-    language_servers: HashMap<LanguageServerId, LanguageServerState>,
-    language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
-    language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
-    last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
-    client: Arc<client::Client>,
-    next_entry_id: Arc<AtomicUsize>,
-    join_project_response_message_id: u32,
-    next_diagnostic_group_id: usize,
-    user_store: Model<UserStore>,
-    fs: Arc<dyn Fs>,
-    client_state: Option<ProjectClientState>,
-    collaborators: HashMap<proto::PeerId, Collaborator>,
-    client_subscriptions: Vec<client::Subscription>,
-    _subscriptions: Vec<gpui::Subscription>,
-    next_buffer_id: u64,
-    opened_buffer: (watch::Sender<()>, watch::Receiver<()>),
-    shared_buffers: HashMap<proto::PeerId, HashSet<u64>>,
-    #[allow(clippy::type_complexity)]
-    loading_buffers_by_path: HashMap<
-        ProjectPath,
-        postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
-    >,
-    #[allow(clippy::type_complexity)]
-    loading_local_worktrees:
-        HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
-    opened_buffers: HashMap<u64, OpenBuffer>,
-    local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
-    local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
-    /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
-    /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
-    buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
-    buffers_being_formatted: HashSet<u64>,
-    buffers_needing_diff: HashSet<WeakModel<Buffer>>,
-    git_diff_debouncer: DelayedDebounced,
-    nonce: u128,
-    _maintain_buffer_languages: Task<()>,
-    _maintain_workspace_config: Task<Result<()>>,
-    terminals: Terminals,
-    copilot_lsp_subscription: Option<gpui::Subscription>,
-    copilot_log_subscription: Option<lsp::Subscription>,
-    current_lsp_settings: HashMap<Arc<str>, LspSettings>,
-    node: Option<Arc<dyn NodeRuntime>>,
-    default_prettier: DefaultPrettier,
-    prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
-    prettier_instances: HashMap<PathBuf, PrettierInstance>,
-}
-
-struct DelayedDebounced {
-    task: Option<Task<()>>,
-    cancel_channel: Option<oneshot::Sender<()>>,
-}
-
-pub enum LanguageServerToQuery {
-    Primary,
-    Other(LanguageServerId),
-}
-
-impl DelayedDebounced {
-    fn new() -> DelayedDebounced {
-        DelayedDebounced {
-            task: None,
-            cancel_channel: None,
-        }
-    }
-
-    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
-    where
-        F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
-    {
-        if let Some(channel) = self.cancel_channel.take() {
-            _ = channel.send(());
-        }
-
-        let (sender, mut receiver) = oneshot::channel::<()>();
-        self.cancel_channel = Some(sender);
-
-        let previous_task = self.task.take();
-        self.task = Some(cx.spawn(move |project, mut cx| async move {
-            let mut timer = cx.background_executor().timer(delay).fuse();
-            if let Some(previous_task) = previous_task {
-                previous_task.await;
-            }
-
-            futures::select_biased! {
-                _ = receiver => return,
-                    _ = timer => {}
-            }
-
-            if let Ok(task) = project.update(&mut cx, |project, cx| (func)(project, cx)) {
-                task.await;
-            }
-        }));
-    }
-}
-
-struct LspBufferSnapshot {
-    version: i32,
-    snapshot: TextBufferSnapshot,
-}
-
-/// Message ordered with respect to buffer operations
-enum BufferOrderedMessage {
-    Operation {
-        buffer_id: u64,
-        operation: proto::Operation,
-    },
-    LanguageServerUpdate {
-        language_server_id: LanguageServerId,
-        message: proto::update_language_server::Variant,
-    },
-    Resync,
-}
-
-enum LocalProjectUpdate {
-    WorktreesChanged,
-    CreateBufferForPeer {
-        peer_id: proto::PeerId,
-        buffer_id: u64,
-    },
-}
-
-enum OpenBuffer {
-    Strong(Model<Buffer>),
-    Weak(WeakModel<Buffer>),
-    Operations(Vec<Operation>),
-}
-
-#[derive(Clone)]
-enum WorktreeHandle {
-    Strong(Model<Worktree>),
-    Weak(WeakModel<Worktree>),
-}
-
-enum ProjectClientState {
-    Local {
-        remote_id: u64,
-        updates_tx: mpsc::UnboundedSender<LocalProjectUpdate>,
-        _send_updates: Task<Result<()>>,
-    },
-    Remote {
-        sharing_has_stopped: bool,
-        remote_id: u64,
-        replica_id: ReplicaId,
-    },
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum Event {
-    LanguageServerAdded(LanguageServerId),
-    LanguageServerRemoved(LanguageServerId),
-    LanguageServerLog(LanguageServerId, String),
-    Notification(String),
-    ActiveEntryChanged(Option<ProjectEntryId>),
-    ActivateProjectPanel,
-    WorktreeAdded,
-    WorktreeRemoved(WorktreeId),
-    WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
-    DiskBasedDiagnosticsStarted {
-        language_server_id: LanguageServerId,
-    },
-    DiskBasedDiagnosticsFinished {
-        language_server_id: LanguageServerId,
-    },
-    DiagnosticsUpdated {
-        path: ProjectPath,
-        language_server_id: LanguageServerId,
-    },
-    RemoteIdChanged(Option<u64>),
-    DisconnectedFromHost,
-    Closed,
-    DeletedEntry(ProjectEntryId),
-    CollaboratorUpdated {
-        old_peer_id: proto::PeerId,
-        new_peer_id: proto::PeerId,
-    },
-    CollaboratorJoined(proto::PeerId),
-    CollaboratorLeft(proto::PeerId),
-    RefreshInlayHints,
-    RevealInProjectPanel(ProjectEntryId),
-}
-
-pub enum LanguageServerState {
-    Starting(Task<Option<Arc<LanguageServer>>>),
-
-    Running {
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        server: Arc<LanguageServer>,
-        watched_paths: HashMap<WorktreeId, GlobSet>,
-        simulate_disk_based_diagnostics_completion: Option<Task<()>>,
-    },
-}
-
-#[derive(Serialize)]
-pub struct LanguageServerStatus {
-    pub name: String,
-    pub pending_work: BTreeMap<String, LanguageServerProgress>,
-    pub has_pending_diagnostic_updates: bool,
-    progress_tokens: HashSet<String>,
-}
-
-#[derive(Clone, Debug, Serialize)]
-pub struct LanguageServerProgress {
-    pub message: Option<String>,
-    pub percentage: Option<usize>,
-    #[serde(skip_serializing)]
-    pub last_update_at: Instant,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
-pub struct ProjectPath {
-    pub worktree_id: WorktreeId,
-    pub path: Arc<Path>,
-}
-
-#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
-pub struct DiagnosticSummary {
-    pub error_count: usize,
-    pub warning_count: usize,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Location {
-    pub buffer: Model<Buffer>,
-    pub range: Range<language::Anchor>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InlayHint {
-    pub position: language::Anchor,
-    pub label: InlayHintLabel,
-    pub kind: Option<InlayHintKind>,
-    pub padding_left: bool,
-    pub padding_right: bool,
-    pub tooltip: Option<InlayHintTooltip>,
-    pub resolve_state: ResolveState,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ResolveState {
-    Resolved,
-    CanResolve(LanguageServerId, Option<lsp::LSPAny>),
-    Resolving,
-}
-
-impl InlayHint {
-    pub fn text(&self) -> String {
-        match &self.label {
-            InlayHintLabel::String(s) => s.to_owned(),
-            InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
-        }
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum InlayHintLabel {
-    String(String),
-    LabelParts(Vec<InlayHintLabelPart>),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InlayHintLabelPart {
-    pub value: String,
-    pub tooltip: Option<InlayHintLabelPartTooltip>,
-    pub location: Option<(LanguageServerId, lsp::Location)>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum InlayHintTooltip {
-    String(String),
-    MarkupContent(MarkupContent),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum InlayHintLabelPartTooltip {
-    String(String),
-    MarkupContent(MarkupContent),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct MarkupContent {
-    pub kind: HoverBlockKind,
-    pub value: String,
-}
-
-#[derive(Debug, Clone)]
-pub struct LocationLink {
-    pub origin: Option<Location>,
-    pub target: Location,
-}
-
-#[derive(Debug)]
-pub struct DocumentHighlight {
-    pub range: Range<language::Anchor>,
-    pub kind: DocumentHighlightKind,
-}
-
-#[derive(Clone, Debug)]
-pub struct Symbol {
-    pub language_server_name: LanguageServerName,
-    pub source_worktree_id: WorktreeId,
-    pub path: ProjectPath,
-    pub label: CodeLabel,
-    pub name: String,
-    pub kind: lsp::SymbolKind,
-    pub range: Range<Unclipped<PointUtf16>>,
-    pub signature: [u8; 32],
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct HoverBlock {
-    pub text: String,
-    pub kind: HoverBlockKind,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum HoverBlockKind {
-    PlainText,
-    Markdown,
-    Code { language: String },
-}
-
-#[derive(Debug)]
-pub struct Hover {
-    pub contents: Vec<HoverBlock>,
-    pub range: Option<Range<language::Anchor>>,
-    pub language: Option<Arc<Language>>,
-}
-
-impl Hover {
-    pub fn is_empty(&self) -> bool {
-        self.contents.iter().all(|block| block.text.is_empty())
-    }
-}
-
-#[derive(Default)]
-pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
-
-impl DiagnosticSummary {
-    fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
-        let mut this = Self {
-            error_count: 0,
-            warning_count: 0,
-        };
-
-        for entry in diagnostics {
-            if entry.diagnostic.is_primary {
-                match entry.diagnostic.severity {
-                    DiagnosticSeverity::ERROR => this.error_count += 1,
-                    DiagnosticSeverity::WARNING => this.warning_count += 1,
-                    _ => {}
-                }
-            }
-        }
-
-        this
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.error_count == 0 && self.warning_count == 0
-    }
-
-    pub fn to_proto(
-        &self,
-        language_server_id: LanguageServerId,
-        path: &Path,
-    ) -> proto::DiagnosticSummary {
-        proto::DiagnosticSummary {
-            path: path.to_string_lossy().to_string(),
-            language_server_id: language_server_id.0 as u64,
-            error_count: self.error_count as u32,
-            warning_count: self.warning_count as u32,
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ProjectEntryId(usize);
-
-impl ProjectEntryId {
-    pub const MAX: Self = Self(usize::MAX);
-
-    pub fn new(counter: &AtomicUsize) -> Self {
-        Self(counter.fetch_add(1, SeqCst))
-    }
-
-    pub fn from_proto(id: u64) -> Self {
-        Self(id as usize)
-    }
-
-    pub fn to_proto(&self) -> u64 {
-        self.0 as u64
-    }
-
-    pub fn to_usize(&self) -> usize {
-        self.0
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum FormatTrigger {
-    Save,
-    Manual,
-}
-
-struct ProjectLspAdapterDelegate {
-    project: Model<Project>,
-    http_client: Arc<dyn HttpClient>,
-}
-
-// Currently, formatting operations are represented differently depending on
-// whether they come from a language server or an external command.
-enum FormatOperation {
-    Lsp(Vec<(Range<Anchor>, String)>),
-    External(Diff),
-    Prettier(Diff),
-}
-
-impl FormatTrigger {
-    fn from_proto(value: i32) -> FormatTrigger {
-        match value {
-            0 => FormatTrigger::Save,
-            1 => FormatTrigger::Manual,
-            _ => FormatTrigger::Save,
-        }
-    }
-}
-#[derive(Clone, Debug, PartialEq)]
-enum SearchMatchCandidate {
-    OpenBuffer {
-        buffer: Model<Buffer>,
-        // This might be an unnamed file without representation on filesystem
-        path: Option<Arc<Path>>,
-    },
-    Path {
-        worktree_id: WorktreeId,
-        is_ignored: bool,
-        path: Arc<Path>,
-    },
-}
-
-type SearchMatchCandidateIndex = usize;
-impl SearchMatchCandidate {
-    fn path(&self) -> Option<Arc<Path>> {
-        match self {
-            SearchMatchCandidate::OpenBuffer { path, .. } => path.clone(),
-            SearchMatchCandidate::Path { path, .. } => Some(path.clone()),
-        }
-    }
-}
-
-impl Project {
-    pub fn init_settings(cx: &mut AppContext) {
-        ProjectSettings::register(cx);
-    }
-
-    pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
-        Self::init_settings(cx);
-
-        client.add_model_message_handler(Self::handle_add_collaborator);
-        client.add_model_message_handler(Self::handle_update_project_collaborator);
-        client.add_model_message_handler(Self::handle_remove_collaborator);
-        client.add_model_message_handler(Self::handle_buffer_reloaded);
-        client.add_model_message_handler(Self::handle_buffer_saved);
-        client.add_model_message_handler(Self::handle_start_language_server);
-        client.add_model_message_handler(Self::handle_update_language_server);
-        client.add_model_message_handler(Self::handle_update_project);
-        client.add_model_message_handler(Self::handle_unshare_project);
-        client.add_model_message_handler(Self::handle_create_buffer_for_peer);
-        client.add_model_message_handler(Self::handle_update_buffer_file);
-        client.add_model_request_handler(Self::handle_update_buffer);
-        client.add_model_message_handler(Self::handle_update_diagnostic_summary);
-        client.add_model_message_handler(Self::handle_update_worktree);
-        client.add_model_message_handler(Self::handle_update_worktree_settings);
-        client.add_model_request_handler(Self::handle_create_project_entry);
-        client.add_model_request_handler(Self::handle_rename_project_entry);
-        client.add_model_request_handler(Self::handle_copy_project_entry);
-        client.add_model_request_handler(Self::handle_delete_project_entry);
-        client.add_model_request_handler(Self::handle_expand_project_entry);
-        client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
-        client.add_model_request_handler(Self::handle_apply_code_action);
-        client.add_model_request_handler(Self::handle_on_type_formatting);
-        client.add_model_request_handler(Self::handle_inlay_hints);
-        client.add_model_request_handler(Self::handle_resolve_inlay_hint);
-        client.add_model_request_handler(Self::handle_refresh_inlay_hints);
-        client.add_model_request_handler(Self::handle_reload_buffers);
-        client.add_model_request_handler(Self::handle_synchronize_buffers);
-        client.add_model_request_handler(Self::handle_format_buffers);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetCodeActions>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetCompletions>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetHover>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetDefinition>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetTypeDefinition>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
-        client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
-        client.add_model_request_handler(Self::handle_lsp_command::<PrepareRename>);
-        client.add_model_request_handler(Self::handle_lsp_command::<PerformRename>);
-        client.add_model_request_handler(Self::handle_search_project);
-        client.add_model_request_handler(Self::handle_get_project_symbols);
-        client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
-        client.add_model_request_handler(Self::handle_open_buffer_by_id);
-        client.add_model_request_handler(Self::handle_open_buffer_by_path);
-        client.add_model_request_handler(Self::handle_save_buffer);
-        client.add_model_message_handler(Self::handle_update_diff_base);
-        client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
-    }
-
-    pub fn local(
-        client: Arc<Client>,
-        node: Arc<dyn NodeRuntime>,
-        user_store: Model<UserStore>,
-        languages: Arc<LanguageRegistry>,
-        fs: Arc<dyn Fs>,
-        cx: &mut AppContext,
-    ) -> Model<Self> {
-        cx.new_model(|cx: &mut ModelContext<Self>| {
-            let (tx, rx) = mpsc::unbounded();
-            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
-                .detach();
-            let copilot_lsp_subscription =
-                Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
-            Self {
-                worktrees: Default::default(),
-                buffer_ordered_messages_tx: tx,
-                collaborators: Default::default(),
-                next_buffer_id: 0,
-                opened_buffers: Default::default(),
-                shared_buffers: Default::default(),
-                incomplete_remote_buffers: Default::default(),
-                loading_buffers_by_path: Default::default(),
-                loading_local_worktrees: Default::default(),
-                local_buffer_ids_by_path: Default::default(),
-                local_buffer_ids_by_entry_id: Default::default(),
-                buffer_snapshots: Default::default(),
-                join_project_response_message_id: 0,
-                client_state: None,
-                opened_buffer: watch::channel(),
-                client_subscriptions: Vec::new(),
-                _subscriptions: vec![
-                    cx.observe_global::<SettingsStore>(Self::on_settings_changed),
-                    cx.on_release(Self::release),
-                    cx.on_app_quit(Self::shutdown_language_servers),
-                ],
-                _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
-                _maintain_workspace_config: Self::maintain_workspace_config(cx),
-                active_entry: None,
-                languages,
-                client,
-                user_store,
-                fs,
-                next_entry_id: Default::default(),
-                next_diagnostic_group_id: Default::default(),
-                supplementary_language_servers: HashMap::default(),
-                language_servers: Default::default(),
-                language_server_ids: Default::default(),
-                language_server_statuses: Default::default(),
-                last_workspace_edits_by_language_server: Default::default(),
-                buffers_being_formatted: Default::default(),
-                buffers_needing_diff: Default::default(),
-                git_diff_debouncer: DelayedDebounced::new(),
-                nonce: StdRng::from_entropy().gen(),
-                terminals: Terminals {
-                    local_handles: Vec::new(),
-                },
-                copilot_lsp_subscription,
-                copilot_log_subscription: None,
-                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
-                node: Some(node),
-                default_prettier: DefaultPrettier::default(),
-                prettiers_per_worktree: HashMap::default(),
-                prettier_instances: HashMap::default(),
-            }
-        })
-    }
-
-    pub async fn remote(
-        remote_id: u64,
-        client: Arc<Client>,
-        user_store: Model<UserStore>,
-        languages: Arc<LanguageRegistry>,
-        fs: Arc<dyn Fs>,
-        mut cx: AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        client.authenticate_and_connect(true, &cx).await?;
-
-        let subscription = client.subscribe_to_entity(remote_id)?;
-        let response = client
-            .request_envelope(proto::JoinProject {
-                project_id: remote_id,
-            })
-            .await?;
-        let this = cx.new_model(|cx| {
-            let replica_id = response.payload.replica_id as ReplicaId;
-
-            let mut worktrees = Vec::new();
-            for worktree in response.payload.worktrees {
-                let worktree =
-                    Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx);
-                worktrees.push(worktree);
-            }
-
-            let (tx, rx) = mpsc::unbounded();
-            cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
-                .detach();
-            let copilot_lsp_subscription =
-                Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
-            let mut this = Self {
-                worktrees: Vec::new(),
-                buffer_ordered_messages_tx: tx,
-                loading_buffers_by_path: Default::default(),
-                next_buffer_id: 0,
-                opened_buffer: watch::channel(),
-                shared_buffers: Default::default(),
-                incomplete_remote_buffers: Default::default(),
-                loading_local_worktrees: Default::default(),
-                local_buffer_ids_by_path: Default::default(),
-                local_buffer_ids_by_entry_id: Default::default(),
-                active_entry: None,
-                collaborators: Default::default(),
-                join_project_response_message_id: response.message_id,
-                _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
-                _maintain_workspace_config: Self::maintain_workspace_config(cx),
-                languages,
-                user_store: user_store.clone(),
-                fs,
-                next_entry_id: Default::default(),
-                next_diagnostic_group_id: Default::default(),
-                client_subscriptions: Default::default(),
-                _subscriptions: vec![
-                    cx.on_release(Self::release),
-                    cx.on_app_quit(Self::shutdown_language_servers),
-                ],
-                client: client.clone(),
-                client_state: Some(ProjectClientState::Remote {
-                    sharing_has_stopped: false,
-                    remote_id,
-                    replica_id,
-                }),
-                supplementary_language_servers: HashMap::default(),
-                language_servers: Default::default(),
-                language_server_ids: Default::default(),
-                language_server_statuses: response
-                    .payload
-                    .language_servers
-                    .into_iter()
-                    .map(|server| {
-                        (
-                            LanguageServerId(server.id as usize),
-                            LanguageServerStatus {
-                                name: server.name,
-                                pending_work: Default::default(),
-                                has_pending_diagnostic_updates: false,
-                                progress_tokens: Default::default(),
-                            },
-                        )
-                    })
-                    .collect(),
-                last_workspace_edits_by_language_server: Default::default(),
-                opened_buffers: Default::default(),
-                buffers_being_formatted: Default::default(),
-                buffers_needing_diff: Default::default(),
-                git_diff_debouncer: DelayedDebounced::new(),
-                buffer_snapshots: Default::default(),
-                nonce: StdRng::from_entropy().gen(),
-                terminals: Terminals {
-                    local_handles: Vec::new(),
-                },
-                copilot_lsp_subscription,
-                copilot_log_subscription: None,
-                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
-                node: None,
-                default_prettier: DefaultPrettier::default(),
-                prettiers_per_worktree: HashMap::default(),
-                prettier_instances: HashMap::default(),
-            };
-            for worktree in worktrees {
-                let _ = this.add_worktree(&worktree, cx);
-            }
-            this
-        })?;
-        let subscription = subscription.set_model(&this, &mut cx);
-
-        let user_ids = response
-            .payload
-            .collaborators
-            .iter()
-            .map(|peer| peer.user_id)
-            .collect();
-        user_store
-            .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
-            .await?;
-
-        this.update(&mut cx, |this, cx| {
-            this.set_collaborators_from_proto(response.payload.collaborators, cx)?;
-            this.client_subscriptions.push(subscription);
-            anyhow::Ok(())
-        })??;
-
-        Ok(this)
-    }
-
-    fn release(&mut self, cx: &mut AppContext) {
-        match &self.client_state {
-            Some(ProjectClientState::Local { .. }) => {
-                let _ = self.unshare_internal(cx);
-            }
-            Some(ProjectClientState::Remote { remote_id, .. }) => {
-                let _ = self.client.send(proto::LeaveProject {
-                    project_id: *remote_id,
-                });
-                self.disconnected_from_host_internal(cx);
-            }
-            _ => {}
-        }
-    }
-
-    fn shutdown_language_servers(
-        &mut self,
-        _cx: &mut ModelContext<Self>,
-    ) -> impl Future<Output = ()> {
-        let shutdown_futures = self
-            .language_servers
-            .drain()
-            .map(|(_, server_state)| async {
-                use LanguageServerState::*;
-                match server_state {
-                    Running { server, .. } => server.shutdown()?.await,
-                    Starting(task) => task.await?.shutdown()?.await,
-                }
-            })
-            .collect::<Vec<_>>();
-
-        async move {
-            futures::future::join_all(shutdown_futures).await;
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub async fn test(
-        fs: Arc<dyn Fs>,
-        root_paths: impl IntoIterator<Item = &Path>,
-        cx: &mut gpui::TestAppContext,
-    ) -> Model<Project> {
-        let mut languages = LanguageRegistry::test();
-        languages.set_executor(cx.executor());
-        let http_client = util::http::FakeHttpClient::with_404_response();
-        let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
-        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
-        let project = cx.update(|cx| {
-            Project::local(
-                client,
-                node_runtime::FakeNodeRuntime::new(),
-                user_store,
-                Arc::new(languages),
-                fs,
-                cx,
-            )
-        });
-        for path in root_paths {
-            let (tree, _) = project
-                .update(cx, |project, cx| {
-                    project.find_or_create_local_worktree(path, true, cx)
-                })
-                .await
-                .unwrap();
-            tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
-                .await;
-        }
-        project
-    }
-
-    fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
-        let mut language_servers_to_start = Vec::new();
-        let mut language_formatters_to_check = Vec::new();
-        for buffer in self.opened_buffers.values() {
-            if let Some(buffer) = buffer.upgrade() {
-                let buffer = buffer.read(cx);
-                let buffer_file = File::from_dyn(buffer.file());
-                let buffer_language = buffer.language();
-                let settings = language_settings(buffer_language, buffer.file(), cx);
-                if let Some(language) = buffer_language {
-                    if settings.enable_language_server {
-                        if let Some(file) = buffer_file {
-                            language_servers_to_start
-                                .push((file.worktree.clone(), Arc::clone(language)));
-                        }
-                    }
-                    language_formatters_to_check.push((
-                        buffer_file.map(|f| f.worktree_id(cx)),
-                        Arc::clone(language),
-                        settings.clone(),
-                    ));
-                }
-            }
-        }
-
-        let mut language_servers_to_stop = Vec::new();
-        let mut language_servers_to_restart = Vec::new();
-        let languages = self.languages.to_vec();
-
-        let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
-        let current_lsp_settings = &self.current_lsp_settings;
-        for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
-            let language = languages.iter().find_map(|l| {
-                let adapter = l
-                    .lsp_adapters()
-                    .iter()
-                    .find(|adapter| &adapter.name == started_lsp_name)?;
-                Some((l, adapter))
-            });
-            if let Some((language, adapter)) = language {
-                let worktree = self.worktree_for_id(*worktree_id, cx);
-                let file = worktree.as_ref().and_then(|tree| {
-                    tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
-                });
-                if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
-                    language_servers_to_stop.push((*worktree_id, started_lsp_name.clone()));
-                } else if let Some(worktree) = worktree {
-                    let server_name = &adapter.name.0;
-                    match (
-                        current_lsp_settings.get(server_name),
-                        new_lsp_settings.get(server_name),
-                    ) {
-                        (None, None) => {}
-                        (Some(_), None) | (None, Some(_)) => {
-                            language_servers_to_restart.push((worktree, Arc::clone(language)));
-                        }
-                        (Some(current_lsp_settings), Some(new_lsp_settings)) => {
-                            if current_lsp_settings != new_lsp_settings {
-                                language_servers_to_restart.push((worktree, Arc::clone(language)));
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        self.current_lsp_settings = new_lsp_settings;
-
-        // Stop all newly-disabled language servers.
-        for (worktree_id, adapter_name) in language_servers_to_stop {
-            self.stop_language_server(worktree_id, adapter_name, cx)
-                .detach();
-        }
-
-        let mut prettier_plugins_by_worktree = HashMap::default();
-        for (worktree, language, settings) in language_formatters_to_check {
-            if let Some(plugins) =
-                prettier_support::prettier_plugins_for_language(&language, &settings)
-            {
-                prettier_plugins_by_worktree
-                    .entry(worktree)
-                    .or_insert_with(|| HashSet::default())
-                    .extend(plugins);
-            }
-        }
-        for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
-            self.install_default_prettier(worktree, prettier_plugins, cx);
-        }
-
-        // Start all the newly-enabled language servers.
-        for (worktree, language) in language_servers_to_start {
-            let worktree_path = worktree.read(cx).abs_path();
-            self.start_language_servers(&worktree, worktree_path, language, cx);
-        }
-
-        // Restart all language servers with changed initialization options.
-        for (worktree, language) in language_servers_to_restart {
-            self.restart_language_servers(worktree, language, cx);
-        }
-
-        if self.copilot_lsp_subscription.is_none() {
-            if let Some(copilot) = Copilot::global(cx) {
-                for buffer in self.opened_buffers.values() {
-                    if let Some(buffer) = buffer.upgrade() {
-                        self.register_buffer_with_copilot(&buffer, cx);
-                    }
-                }
-                self.copilot_lsp_subscription = Some(subscribe_for_copilot_events(&copilot, cx));
-            }
-        }
-
-        cx.notify();
-    }
-
-    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
-        self.opened_buffers
-            .get(&remote_id)
-            .and_then(|buffer| buffer.upgrade())
-    }
-
-    pub fn languages(&self) -> &Arc<LanguageRegistry> {
-        &self.languages
-    }
-
-    pub fn client(&self) -> Arc<Client> {
-        self.client.clone()
-    }
-
-    pub fn user_store(&self) -> Model<UserStore> {
-        self.user_store.clone()
-    }
-
-    pub fn opened_buffers(&self) -> Vec<Model<Buffer>> {
-        self.opened_buffers
-            .values()
-            .filter_map(|b| b.upgrade())
-            .collect()
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
-        let path = path.into();
-        if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
-            self.opened_buffers.iter().any(|(_, buffer)| {
-                if let Some(buffer) = buffer.upgrade() {
-                    if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
-                        if file.worktree == worktree && file.path() == &path.path {
-                            return true;
-                        }
-                    }
-                }
-                false
-            })
-        } else {
-            false
-        }
-    }
-
-    pub fn fs(&self) -> &Arc<dyn Fs> {
-        &self.fs
-    }
-
-    pub fn remote_id(&self) -> Option<u64> {
-        match self.client_state.as_ref()? {
-            ProjectClientState::Local { remote_id, .. }
-            | ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
-        }
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        match &self.client_state {
-            Some(ProjectClientState::Remote { replica_id, .. }) => *replica_id,
-            _ => 0,
-        }
-    }
-
-    fn metadata_changed(&mut self, cx: &mut ModelContext<Self>) {
-        if let Some(ProjectClientState::Local { updates_tx, .. }) = &mut self.client_state {
-            updates_tx
-                .unbounded_send(LocalProjectUpdate::WorktreesChanged)
-                .ok();
-        }
-        cx.notify();
-    }
-
-    pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
-        &self.collaborators
-    }
-
-    pub fn host(&self) -> Option<&Collaborator> {
-        self.collaborators.values().find(|c| c.replica_id == 0)
-    }
-
-    /// Collect all worktrees, including ones that don't appear in the project panel
-    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
-        self.worktrees
-            .iter()
-            .filter_map(move |worktree| worktree.upgrade())
-    }
-
-    /// Collect all user-visible worktrees, the ones that appear in the project panel
-    pub fn visible_worktrees<'a>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
-        self.worktrees.iter().filter_map(|worktree| {
-            worktree.upgrade().and_then(|worktree| {
-                if worktree.read(cx).is_visible() {
-                    Some(worktree)
-                } else {
-                    None
-                }
-            })
-        })
-    }
-
-    pub fn worktree_root_names<'a>(&'a self, cx: &'a AppContext) -> impl Iterator<Item = &'a str> {
-        self.visible_worktrees(cx)
-            .map(|tree| tree.read(cx).root_name())
-    }
-
-    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
-        self.worktrees()
-            .find(|worktree| worktree.read(cx).id() == id)
-    }
-
-    pub fn worktree_for_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &AppContext,
-    ) -> Option<Model<Worktree>> {
-        self.worktrees()
-            .find(|worktree| worktree.read(cx).contains_entry(entry_id))
-    }
-
-    pub fn worktree_id_for_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &AppContext,
-    ) -> Option<WorktreeId> {
-        self.worktree_for_entry(entry_id, cx)
-            .map(|worktree| worktree.read(cx).id())
-    }
-
-    pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
-        paths.iter().all(|path| self.contains_path(path, cx))
-    }
-
-    pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
-        for worktree in self.worktrees() {
-            let worktree = worktree.read(cx).as_local();
-            if worktree.map_or(false, |w| w.contains_abs_path(path)) {
-                return true;
-            }
-        }
-        false
-    }
-
-    pub fn create_entry(
-        &mut self,
-        project_path: impl Into<ProjectPath>,
-        is_directory: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Entry>>> {
-        let project_path = project_path.into();
-        let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else {
-            return Task::ready(Ok(None));
-        };
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .create_entry(project_path.path, is_directory, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::CreateProjectEntry {
-                        worktree_id: project_path.worktree_id.to_proto(),
-                        project_id,
-                        path: project_path.path.to_string_lossy().into(),
-                        is_directory,
-                    })
-                    .await?;
-                match response.entry {
-                    Some(entry) => worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.as_remote_mut().unwrap().insert_entry(
-                                entry,
-                                response.worktree_scan_id as usize,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .map(Some),
-                    None => Ok(None),
-                }
-            })
-        }
-    }
-
-    pub fn copy_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Entry>>> {
-        let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
-            return Task::ready(Ok(None));
-        };
-        let new_path = new_path.into();
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .copy_entry(entry_id, new_path, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::CopyProjectEntry {
-                        project_id,
-                        entry_id: entry_id.to_proto(),
-                        new_path: new_path.to_string_lossy().into(),
-                    })
-                    .await?;
-                match response.entry {
-                    Some(entry) => worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.as_remote_mut().unwrap().insert_entry(
-                                entry,
-                                response.worktree_scan_id as usize,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .map(Some),
-                    None => Ok(None),
-                }
-            })
-        }
-    }
-
-    pub fn rename_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Entry>>> {
-        let Some(worktree) = self.worktree_for_entry(entry_id, cx) else {
-            return Task::ready(Ok(None));
-        };
-        let new_path = new_path.into();
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .rename_entry(entry_id, new_path, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::RenameProjectEntry {
-                        project_id,
-                        entry_id: entry_id.to_proto(),
-                        new_path: new_path.to_string_lossy().into(),
-                    })
-                    .await?;
-                match response.entry {
-                    Some(entry) => worktree
-                        .update(&mut cx, |worktree, cx| {
-                            worktree.as_remote_mut().unwrap().insert_entry(
-                                entry,
-                                response.worktree_scan_id as usize,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .map(Some),
-                    None => Ok(None),
-                }
-            })
-        }
-    }
-
-    pub fn delete_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let worktree = self.worktree_for_entry(entry_id, cx)?;
-
-        cx.emit(Event::DeletedEntry(entry_id));
-
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree.as_local_mut().unwrap().delete_entry(entry_id, cx)
-            })
-        } else {
-            let client = self.client.clone();
-            let project_id = self.remote_id().unwrap();
-            Some(cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::DeleteProjectEntry {
-                        project_id,
-                        entry_id: entry_id.to_proto(),
-                    })
-                    .await?;
-                worktree
-                    .update(&mut cx, move |worktree, cx| {
-                        worktree.as_remote_mut().unwrap().delete_entry(
-                            entry_id,
-                            response.worktree_scan_id as usize,
-                            cx,
-                        )
-                    })?
-                    .await
-            }))
-        }
-    }
-
-    pub fn expand_entry(
-        &mut self,
-        worktree_id: WorktreeId,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let worktree = self.worktree_for_id(worktree_id, cx)?;
-        if self.is_local() {
-            worktree.update(cx, |worktree, cx| {
-                worktree.as_local_mut().unwrap().expand_entry(entry_id, cx)
-            })
-        } else {
-            let worktree = worktree.downgrade();
-            let request = self.client.request(proto::ExpandProjectEntry {
-                project_id: self.remote_id().unwrap(),
-                entry_id: entry_id.to_proto(),
-            });
-            Some(cx.spawn(move |_, mut cx| async move {
-                let response = request.await?;
-                if let Some(worktree) = worktree.upgrade() {
-                    worktree
-                        .update(&mut cx, |worktree, _| {
-                            worktree
-                                .as_remote_mut()
-                                .unwrap()
-                                .wait_for_snapshot(response.worktree_scan_id as usize)
-                        })?
-                        .await?;
-                }
-                Ok(())
-            }))
-        }
-    }
-
-    pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> {
-        if self.client_state.is_some() {
-            return Err(anyhow!("project was already shared"));
-        }
-        self.client_subscriptions.push(
-            self.client
-                .subscribe_to_entity(project_id)?
-                .set_model(&cx.handle(), &mut cx.to_async()),
-        );
-
-        for open_buffer in self.opened_buffers.values_mut() {
-            match open_buffer {
-                OpenBuffer::Strong(_) => {}
-                OpenBuffer::Weak(buffer) => {
-                    if let Some(buffer) = buffer.upgrade() {
-                        *open_buffer = OpenBuffer::Strong(buffer);
-                    }
-                }
-                OpenBuffer::Operations(_) => unreachable!(),
-            }
-        }
-
-        for worktree_handle in self.worktrees.iter_mut() {
-            match worktree_handle {
-                WorktreeHandle::Strong(_) => {}
-                WorktreeHandle::Weak(worktree) => {
-                    if let Some(worktree) = worktree.upgrade() {
-                        *worktree_handle = WorktreeHandle::Strong(worktree);
-                    }
-                }
-            }
-        }
-
-        for (server_id, status) in &self.language_server_statuses {
-            self.client
-                .send(proto::StartLanguageServer {
-                    project_id,
-                    server: Some(proto::LanguageServer {
-                        id: server_id.0 as u64,
-                        name: status.name.clone(),
-                    }),
-                })
-                .log_err();
-        }
-
-        let store = cx.global::<SettingsStore>();
-        for worktree in self.worktrees() {
-            let worktree_id = worktree.read(cx).id().to_proto();
-            for (path, content) in store.local_settings(worktree.entity_id().as_u64() as usize) {
-                self.client
-                    .send(proto::UpdateWorktreeSettings {
-                        project_id,
-                        worktree_id,
-                        path: path.to_string_lossy().into(),
-                        content: Some(content),
-                    })
-                    .log_err();
-            }
-        }
-
-        let (updates_tx, mut updates_rx) = mpsc::unbounded();
-        let client = self.client.clone();
-        self.client_state = Some(ProjectClientState::Local {
-            remote_id: project_id,
-            updates_tx,
-            _send_updates: cx.spawn(move |this, mut cx| async move {
-                while let Some(update) = updates_rx.next().await {
-                    match update {
-                        LocalProjectUpdate::WorktreesChanged => {
-                            let worktrees = this.update(&mut cx, |this, _cx| {
-                                this.worktrees().collect::<Vec<_>>()
-                            })?;
-                            let update_project = this
-                                .update(&mut cx, |this, cx| {
-                                    this.client.request(proto::UpdateProject {
-                                        project_id,
-                                        worktrees: this.worktree_metadata_protos(cx),
-                                    })
-                                })?
-                                .await;
-                            if update_project.is_ok() {
-                                for worktree in worktrees {
-                                    worktree.update(&mut cx, |worktree, cx| {
-                                        let worktree = worktree.as_local_mut().unwrap();
-                                        worktree.share(project_id, cx).detach_and_log_err(cx)
-                                    })?;
-                                }
-                            }
-                        }
-                        LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id } => {
-                            let buffer = this.update(&mut cx, |this, _| {
-                                let buffer = this.opened_buffers.get(&buffer_id).unwrap();
-                                let shared_buffers =
-                                    this.shared_buffers.entry(peer_id).or_default();
-                                if shared_buffers.insert(buffer_id) {
-                                    if let OpenBuffer::Strong(buffer) = buffer {
-                                        Some(buffer.clone())
-                                    } else {
-                                        None
-                                    }
-                                } else {
-                                    None
-                                }
-                            })?;
-
-                            let Some(buffer) = buffer else { continue };
-                            let operations =
-                                buffer.update(&mut cx, |b, cx| b.serialize_ops(None, cx))?;
-                            let operations = operations.await;
-                            let state = buffer.update(&mut cx, |buffer, _| buffer.to_proto())?;
-
-                            let initial_state = proto::CreateBufferForPeer {
-                                project_id,
-                                peer_id: Some(peer_id),
-                                variant: Some(proto::create_buffer_for_peer::Variant::State(state)),
-                            };
-                            if client.send(initial_state).log_err().is_some() {
-                                let client = client.clone();
-                                cx.background_executor()
-                                    .spawn(async move {
-                                        let mut chunks = split_operations(operations).peekable();
-                                        while let Some(chunk) = chunks.next() {
-                                            let is_last = chunks.peek().is_none();
-                                            client.send(proto::CreateBufferForPeer {
-                                                project_id,
-                                                peer_id: Some(peer_id),
-                                                variant: Some(
-                                                    proto::create_buffer_for_peer::Variant::Chunk(
-                                                        proto::BufferChunk {
-                                                            buffer_id,
-                                                            operations: chunk,
-                                                            is_last,
-                                                        },
-                                                    ),
-                                                ),
-                                            })?;
-                                        }
-                                        anyhow::Ok(())
-                                    })
-                                    .await
-                                    .log_err();
-                            }
-                        }
-                    }
-                }
-                Ok(())
-            }),
-        });
-
-        self.metadata_changed(cx);
-        cx.emit(Event::RemoteIdChanged(Some(project_id)));
-        cx.notify();
-        Ok(())
-    }
-
-    pub fn reshared(
-        &mut self,
-        message: proto::ResharedProject,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        self.shared_buffers.clear();
-        self.set_collaborators_from_proto(message.collaborators, cx)?;
-        self.metadata_changed(cx);
-        Ok(())
-    }
-
-    pub fn rejoined(
-        &mut self,
-        message: proto::RejoinedProject,
-        message_id: u32,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            for worktree in &self.worktrees {
-                store
-                    .clear_local_settings(worktree.handle_id(), cx)
-                    .log_err();
-            }
-        });
-
-        self.join_project_response_message_id = message_id;
-        self.set_worktrees_from_proto(message.worktrees, cx)?;
-        self.set_collaborators_from_proto(message.collaborators, cx)?;
-        self.language_server_statuses = message
-            .language_servers
-            .into_iter()
-            .map(|server| {
-                (
-                    LanguageServerId(server.id as usize),
-                    LanguageServerStatus {
-                        name: server.name,
-                        pending_work: Default::default(),
-                        has_pending_diagnostic_updates: false,
-                        progress_tokens: Default::default(),
-                    },
-                )
-            })
-            .collect();
-        self.buffer_ordered_messages_tx
-            .unbounded_send(BufferOrderedMessage::Resync)
-            .unwrap();
-        cx.notify();
-        Ok(())
-    }
-
-    pub fn unshare(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
-        self.unshare_internal(cx)?;
-        self.metadata_changed(cx);
-        cx.notify();
-        Ok(())
-    }
-
-    fn unshare_internal(&mut self, cx: &mut AppContext) -> Result<()> {
-        if self.is_remote() {
-            return Err(anyhow!("attempted to unshare a remote project"));
-        }
-
-        if let Some(ProjectClientState::Local { remote_id, .. }) = self.client_state.take() {
-            self.collaborators.clear();
-            self.shared_buffers.clear();
-            self.client_subscriptions.clear();
-
-            for worktree_handle in self.worktrees.iter_mut() {
-                if let WorktreeHandle::Strong(worktree) = worktree_handle {
-                    let is_visible = worktree.update(cx, |worktree, _| {
-                        worktree.as_local_mut().unwrap().unshare();
-                        worktree.is_visible()
-                    });
-                    if !is_visible {
-                        *worktree_handle = WorktreeHandle::Weak(worktree.downgrade());
-                    }
-                }
-            }
-
-            for open_buffer in self.opened_buffers.values_mut() {
-                // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade() {
-                    buffer.update(cx, |buffer, _| buffer.give_up_waiting());
-                }
-
-                if let OpenBuffer::Strong(buffer) = open_buffer {
-                    *open_buffer = OpenBuffer::Weak(buffer.downgrade());
-                }
-            }
-
-            self.client.send(proto::UnshareProject {
-                project_id: remote_id,
-            })?;
-
-            Ok(())
-        } else {
-            Err(anyhow!("attempted to unshare an unshared project"))
-        }
-    }
-
-    pub fn disconnected_from_host(&mut self, cx: &mut ModelContext<Self>) {
-        self.disconnected_from_host_internal(cx);
-        cx.emit(Event::DisconnectedFromHost);
-        cx.notify();
-    }
-
-    fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) {
-        if let Some(ProjectClientState::Remote {
-            sharing_has_stopped,
-            ..
-        }) = &mut self.client_state
-        {
-            *sharing_has_stopped = true;
-
-            self.collaborators.clear();
-
-            for worktree in &self.worktrees {
-                if let Some(worktree) = worktree.upgrade() {
-                    worktree.update(cx, |worktree, _| {
-                        if let Some(worktree) = worktree.as_remote_mut() {
-                            worktree.disconnected_from_host();
-                        }
-                    });
-                }
-            }
-
-            for open_buffer in self.opened_buffers.values_mut() {
-                // Wake up any tasks waiting for peers' edits to this buffer.
-                if let Some(buffer) = open_buffer.upgrade() {
-                    buffer.update(cx, |buffer, _| buffer.give_up_waiting());
-                }
-
-                if let OpenBuffer::Strong(buffer) = open_buffer {
-                    *open_buffer = OpenBuffer::Weak(buffer.downgrade());
-                }
-            }
-
-            // Wake up all futures currently waiting on a buffer to get opened,
-            // to give them a chance to fail now that we've disconnected.
-            *self.opened_buffer.0.borrow_mut() = ();
-        }
-    }
-
-    pub fn close(&mut self, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Closed);
-    }
-
-    pub fn is_read_only(&self) -> bool {
-        match &self.client_state {
-            Some(ProjectClientState::Remote {
-                sharing_has_stopped,
-                ..
-            }) => *sharing_has_stopped,
-            _ => false,
-        }
-    }
-
-    pub fn is_local(&self) -> bool {
-        match &self.client_state {
-            Some(ProjectClientState::Remote { .. }) => false,
-            _ => true,
-        }
-    }
-
-    pub fn is_remote(&self) -> bool {
-        !self.is_local()
-    }
-
-    pub fn create_buffer(
-        &mut self,
-        text: &str,
-        language: Option<Arc<Language>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<Model<Buffer>> {
-        if self.is_remote() {
-            return Err(anyhow!("creating buffers as a guest is not supported yet"));
-        }
-        let id = post_inc(&mut self.next_buffer_id);
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(self.replica_id(), id, text)
-                .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
-        });
-        self.register_buffer(&buffer, cx)?;
-        Ok(buffer)
-    }
-
-    pub fn open_path(
-        &mut self,
-        path: ProjectPath,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Option<ProjectEntryId>, AnyModel)>> {
-        let task = self.open_buffer(path.clone(), cx);
-        cx.spawn(move |_, cx| async move {
-            let buffer = task.await?;
-            let project_entry_id = buffer.read_with(&cx, |buffer, cx| {
-                File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
-            })?;
-
-            let buffer: &AnyModel = &buffer;
-            Ok((project_entry_id, buffer.clone()))
-        })
-    }
-
-    pub fn open_local_buffer(
-        &mut self,
-        abs_path: impl AsRef<Path>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
-            self.open_buffer((worktree.read(cx).id(), relative_path), cx)
-        } else {
-            Task::ready(Err(anyhow!("no such path")))
-        }
-    }
-
-    pub fn open_buffer(
-        &mut self,
-        path: impl Into<ProjectPath>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let project_path = path.into();
-        let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
-            worktree
-        } else {
-            return Task::ready(Err(anyhow!("no such worktree")));
-        };
-
-        // If there is already a buffer for the given path, then return it.
-        let existing_buffer = self.get_open_buffer(&project_path, cx);
-        if let Some(existing_buffer) = existing_buffer {
-            return Task::ready(Ok(existing_buffer));
-        }
-
-        let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
-            // If the given path is already being loaded, then wait for that existing
-            // task to complete and return the same buffer.
-            hash_map::Entry::Occupied(e) => e.get().clone(),
-
-            // Otherwise, record the fact that this path is now being loaded.
-            hash_map::Entry::Vacant(entry) => {
-                let (mut tx, rx) = postage::watch::channel();
-                entry.insert(rx.clone());
-
-                let load_buffer = if worktree.read(cx).is_local() {
-                    self.open_local_buffer_internal(&project_path.path, &worktree, cx)
-                } else {
-                    self.open_remote_buffer_internal(&project_path.path, &worktree, cx)
-                };
-
-                let project_path = project_path.clone();
-                cx.spawn(move |this, mut cx| async move {
-                    let load_result = load_buffer.await;
-                    *tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
-                        // Record the fact that the buffer is no longer loading.
-                        this.loading_buffers_by_path.remove(&project_path);
-                        let buffer = load_result.map_err(Arc::new)?;
-                        Ok(buffer)
-                    })?);
-                    anyhow::Ok(())
-                })
-                .detach();
-                rx
-            }
-        };
-
-        cx.background_executor().spawn(async move {
-            wait_for_loading_buffer(loading_watch)
-                .await
-                .map_err(|error| anyhow!("{project_path:?} opening failure: {error:#}"))
-        })
-    }
-
-    fn open_local_buffer_internal(
-        &mut self,
-        path: &Arc<Path>,
-        worktree: &Model<Worktree>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let buffer_id = post_inc(&mut self.next_buffer_id);
-        let load_buffer = worktree.update(cx, |worktree, cx| {
-            let worktree = worktree.as_local_mut().unwrap();
-            worktree.load_buffer(buffer_id, path, cx)
-        });
-        cx.spawn(move |this, mut cx| async move {
-            let buffer = load_buffer.await?;
-            this.update(&mut cx, |this, cx| this.register_buffer(&buffer, cx))??;
-            Ok(buffer)
-        })
-    }
-
-    fn open_remote_buffer_internal(
-        &mut self,
-        path: &Arc<Path>,
-        worktree: &Model<Worktree>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let rpc = self.client.clone();
-        let project_id = self.remote_id().unwrap();
-        let remote_worktree_id = worktree.read(cx).id();
-        let path = path.clone();
-        let path_string = path.to_string_lossy().to_string();
-        cx.spawn(move |this, mut cx| async move {
-            let response = rpc
-                .request(proto::OpenBufferByPath {
-                    project_id,
-                    worktree_id: remote_worktree_id.to_proto(),
-                    path: path_string,
-                })
-                .await?;
-            this.update(&mut cx, |this, cx| {
-                this.wait_for_remote_buffer(response.buffer_id, cx)
-            })?
-            .await
-        })
-    }
-
-    /// LanguageServerName is owned, because it is inserted into a map
-    pub fn open_local_buffer_via_lsp(
-        &mut self,
-        abs_path: lsp::Url,
-        language_server_id: LanguageServerId,
-        language_server_name: LanguageServerName,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        cx.spawn(move |this, mut cx| async move {
-            let abs_path = abs_path
-                .to_file_path()
-                .map_err(|_| anyhow!("can't convert URI to path"))?;
-            let (worktree, relative_path) = if let Some(result) =
-                this.update(&mut cx, |this, cx| this.find_local_worktree(&abs_path, cx))?
-            {
-                result
-            } else {
-                let worktree = this
-                    .update(&mut cx, |this, cx| {
-                        this.create_local_worktree(&abs_path, false, cx)
-                    })?
-                    .await?;
-                this.update(&mut cx, |this, cx| {
-                    this.language_server_ids.insert(
-                        (worktree.read(cx).id(), language_server_name),
-                        language_server_id,
-                    );
-                })
-                .ok();
-                (worktree, PathBuf::new())
-            };
-
-            let project_path = ProjectPath {
-                worktree_id: worktree.update(&mut cx, |worktree, _| worktree.id())?,
-                path: relative_path.into(),
-            };
-            this.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))?
-                .await
-        })
-    }
-
-    pub fn open_buffer_by_id(
-        &mut self,
-        id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        if let Some(buffer) = self.buffer_for_id(id) {
-            Task::ready(Ok(buffer))
-        } else if self.is_local() {
-            Task::ready(Err(anyhow!("buffer {} does not exist", id)))
-        } else if let Some(project_id) = self.remote_id() {
-            let request = self
-                .client
-                .request(proto::OpenBufferById { project_id, id });
-            cx.spawn(move |this, mut cx| async move {
-                let buffer_id = request.await?.buffer_id;
-                this.update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(buffer_id, cx)
-                })?
-                .await
-            })
-        } else {
-            Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
-        }
-    }
-
-    pub fn save_buffers(
-        &self,
-        buffers: HashSet<Model<Buffer>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        cx.spawn(move |this, mut cx| async move {
-            let save_tasks = buffers.into_iter().filter_map(|buffer| {
-                this.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
-                    .ok()
-            });
-            try_join_all(save_tasks).await?;
-            Ok(())
-        })
-    }
-
-    pub fn save_buffer(
-        &self,
-        buffer: Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
-            return Task::ready(Err(anyhow!("buffer doesn't have a file")));
-        };
-        let worktree = file.worktree.clone();
-        let path = file.path.clone();
-        worktree.update(cx, |worktree, cx| match worktree {
-            Worktree::Local(worktree) => worktree.save_buffer(buffer, path, false, cx),
-            Worktree::Remote(worktree) => worktree.save_buffer(buffer, cx),
-        })
-    }
-
-    pub fn save_buffer_as(
-        &mut self,
-        buffer: Model<Buffer>,
-        abs_path: PathBuf,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<()>> {
-        let worktree_task = self.find_or_create_local_worktree(&abs_path, true, cx);
-        let old_file = File::from_dyn(buffer.read(cx).file())
-            .filter(|f| f.is_local())
-            .cloned();
-        cx.spawn(move |this, mut cx| async move {
-            if let Some(old_file) = &old_file {
-                this.update(&mut cx, |this, cx| {
-                    this.unregister_buffer_from_language_servers(&buffer, old_file, cx);
-                })?;
-            }
-            let (worktree, path) = worktree_task.await?;
-            worktree
-                .update(&mut cx, |worktree, cx| match worktree {
-                    Worktree::Local(worktree) => {
-                        worktree.save_buffer(buffer.clone(), path.into(), true, cx)
-                    }
-                    Worktree::Remote(_) => panic!("cannot remote buffers as new files"),
-                })?
-                .await?;
-
-            this.update(&mut cx, |this, cx| {
-                this.detect_language_for_buffer(&buffer, cx);
-                this.register_buffer_with_language_servers(&buffer, cx);
-            })?;
-            Ok(())
-        })
-    }
-
-    pub fn get_open_buffer(
-        &mut self,
-        path: &ProjectPath,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Model<Buffer>> {
-        let worktree = self.worktree_for_id(path.worktree_id, cx)?;
-        self.opened_buffers.values().find_map(|buffer| {
-            let buffer = buffer.upgrade()?;
-            let file = File::from_dyn(buffer.read(cx).file())?;
-            if file.worktree == worktree && file.path() == &path.path {
-                Some(buffer)
-            } else {
-                None
-            }
-        })
-    }
-
-    fn register_buffer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        self.request_buffer_diff_recalculation(buffer, cx);
-        buffer.update(cx, |buffer, _| {
-            buffer.set_language_registry(self.languages.clone())
-        });
-
-        let remote_id = buffer.read(cx).remote_id();
-        let is_remote = self.is_remote();
-        let open_buffer = if is_remote || self.is_shared() {
-            OpenBuffer::Strong(buffer.clone())
-        } else {
-            OpenBuffer::Weak(buffer.downgrade())
-        };
-
-        match self.opened_buffers.entry(remote_id) {
-            hash_map::Entry::Vacant(entry) => {
-                entry.insert(open_buffer);
-            }
-            hash_map::Entry::Occupied(mut entry) => {
-                if let OpenBuffer::Operations(operations) = entry.get_mut() {
-                    buffer.update(cx, |b, cx| b.apply_ops(operations.drain(..), cx))?;
-                } else if entry.get().upgrade().is_some() {
-                    if is_remote {
-                        return Ok(());
-                    } else {
-                        debug_panic!("buffer {} was already registered", remote_id);
-                        Err(anyhow!("buffer {} was already registered", remote_id))?;
-                    }
-                }
-                entry.insert(open_buffer);
-            }
-        }
-        cx.subscribe(buffer, |this, buffer, event, cx| {
-            this.on_buffer_event(buffer, event, cx);
-        })
-        .detach();
-
-        if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
-            if file.is_local {
-                self.local_buffer_ids_by_path.insert(
-                    ProjectPath {
-                        worktree_id: file.worktree_id(cx),
-                        path: file.path.clone(),
-                    },
-                    remote_id,
-                );
-
-                if let Some(entry_id) = file.entry_id {
-                    self.local_buffer_ids_by_entry_id
-                        .insert(entry_id, remote_id);
-                }
-            }
-        }
-
-        self.detect_language_for_buffer(buffer, cx);
-        self.register_buffer_with_language_servers(buffer, cx);
-        self.register_buffer_with_copilot(buffer, cx);
-        cx.observe_release(buffer, |this, buffer, cx| {
-            if let Some(file) = File::from_dyn(buffer.file()) {
-                if file.is_local() {
-                    let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                    for server in this.language_servers_for_buffer(buffer, cx) {
-                        server
-                            .1
-                            .notify::<lsp::notification::DidCloseTextDocument>(
-                                lsp::DidCloseTextDocumentParams {
-                                    text_document: lsp::TextDocumentIdentifier::new(uri.clone()),
-                                },
-                            )
-                            .log_err();
-                    }
-                }
-            }
-        })
-        .detach();
-
-        *self.opened_buffer.0.borrow_mut() = ();
-        Ok(())
-    }
-
-    fn register_buffer_with_language_servers(
-        &mut self,
-        buffer_handle: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
-
-        if let Some(file) = File::from_dyn(buffer.file()) {
-            if !file.is_local() {
-                return;
-            }
-
-            let abs_path = file.abs_path(cx);
-            let uri = lsp::Url::from_file_path(&abs_path)
-                .unwrap_or_else(|()| panic!("Failed to register file {abs_path:?}"));
-            let initial_snapshot = buffer.text_snapshot();
-            let language = buffer.language().cloned();
-            let worktree_id = file.worktree_id(cx);
-
-            if let Some(local_worktree) = file.worktree.read(cx).as_local() {
-                for (server_id, diagnostics) in local_worktree.diagnostics_for_path(file.path()) {
-                    self.update_buffer_diagnostics(buffer_handle, server_id, None, diagnostics, cx)
-                        .log_err();
-                }
-            }
-
-            if let Some(language) = language {
-                for adapter in language.lsp_adapters() {
-                    let language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
-                    let server = self
-                        .language_server_ids
-                        .get(&(worktree_id, adapter.name.clone()))
-                        .and_then(|id| self.language_servers.get(id))
-                        .and_then(|server_state| {
-                            if let LanguageServerState::Running { server, .. } = server_state {
-                                Some(server.clone())
-                            } else {
-                                None
-                            }
-                        });
-                    let server = match server {
-                        Some(server) => server,
-                        None => continue,
-                    };
-
-                    server
-                        .notify::<lsp::notification::DidOpenTextDocument>(
-                            lsp::DidOpenTextDocumentParams {
-                                text_document: lsp::TextDocumentItem::new(
-                                    uri.clone(),
-                                    language_id.unwrap_or_default(),
-                                    0,
-                                    initial_snapshot.text(),
-                                ),
-                            },
-                        )
-                        .log_err();
-
-                    buffer_handle.update(cx, |buffer, cx| {
-                        buffer.set_completion_triggers(
-                            server
-                                .capabilities()
-                                .completion_provider
-                                .as_ref()
-                                .and_then(|provider| provider.trigger_characters.clone())
-                                .unwrap_or_default(),
-                            cx,
-                        );
-                    });
-
-                    let snapshot = LspBufferSnapshot {
-                        version: 0,
-                        snapshot: initial_snapshot.clone(),
-                    };
-                    self.buffer_snapshots
-                        .entry(buffer_id)
-                        .or_default()
-                        .insert(server.server_id(), vec![snapshot]);
-                }
-            }
-        }
-    }
-
-    fn unregister_buffer_from_language_servers(
-        &mut self,
-        buffer: &Model<Buffer>,
-        old_file: &File,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let old_path = match old_file.as_local() {
-            Some(local) => local.abs_path(cx),
-            None => return,
-        };
-
-        buffer.update(cx, |buffer, cx| {
-            let worktree_id = old_file.worktree_id(cx);
-            let ids = &self.language_server_ids;
-
-            let language = buffer.language().cloned();
-            let adapters = language.iter().flat_map(|language| language.lsp_adapters());
-            for &server_id in adapters.flat_map(|a| ids.get(&(worktree_id, a.name.clone()))) {
-                buffer.update_diagnostics(server_id, Default::default(), cx);
-            }
-
-            self.buffer_snapshots.remove(&buffer.remote_id());
-            let file_url = lsp::Url::from_file_path(old_path).unwrap();
-            for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
-                language_server
-                    .notify::<lsp::notification::DidCloseTextDocument>(
-                        lsp::DidCloseTextDocumentParams {
-                            text_document: lsp::TextDocumentIdentifier::new(file_url.clone()),
-                        },
-                    )
-                    .log_err();
-            }
-        });
-    }
-
-    fn register_buffer_with_copilot(
-        &self,
-        buffer_handle: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(copilot) = Copilot::global(cx) {
-            copilot.update(cx, |copilot, cx| copilot.register_buffer(buffer_handle, cx));
-        }
-    }
-
-    async fn send_buffer_ordered_messages(
-        this: WeakModel<Self>,
-        rx: UnboundedReceiver<BufferOrderedMessage>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        const MAX_BATCH_SIZE: usize = 128;
-
-        let mut operations_by_buffer_id = HashMap::default();
-        async fn flush_operations(
-            this: &WeakModel<Project>,
-            operations_by_buffer_id: &mut HashMap<u64, Vec<proto::Operation>>,
-            needs_resync_with_host: &mut bool,
-            is_local: bool,
-            cx: &mut AsyncAppContext,
-        ) -> Result<()> {
-            for (buffer_id, operations) in operations_by_buffer_id.drain() {
-                let request = this.update(cx, |this, _| {
-                    let project_id = this.remote_id()?;
-                    Some(this.client.request(proto::UpdateBuffer {
-                        buffer_id,
-                        project_id,
-                        operations,
-                    }))
-                })?;
-                if let Some(request) = request {
-                    if request.await.is_err() && !is_local {
-                        *needs_resync_with_host = true;
-                        break;
-                    }
-                }
-            }
-            Ok(())
-        }
-
-        let mut needs_resync_with_host = false;
-        let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
-
-        while let Some(changes) = changes.next().await {
-            let is_local = this.update(&mut cx, |this, _| this.is_local())?;
-
-            for change in changes {
-                match change {
-                    BufferOrderedMessage::Operation {
-                        buffer_id,
-                        operation,
-                    } => {
-                        if needs_resync_with_host {
-                            continue;
-                        }
-
-                        operations_by_buffer_id
-                            .entry(buffer_id)
-                            .or_insert(Vec::new())
-                            .push(operation);
-                    }
-
-                    BufferOrderedMessage::Resync => {
-                        operations_by_buffer_id.clear();
-                        if this
-                            .update(&mut cx, |this, cx| this.synchronize_remote_buffers(cx))?
-                            .await
-                            .is_ok()
-                        {
-                            needs_resync_with_host = false;
-                        }
-                    }
-
-                    BufferOrderedMessage::LanguageServerUpdate {
-                        language_server_id,
-                        message,
-                    } => {
-                        flush_operations(
-                            &this,
-                            &mut operations_by_buffer_id,
-                            &mut needs_resync_with_host,
-                            is_local,
-                            &mut cx,
-                        )
-                        .await?;
-
-                        this.update(&mut cx, |this, _| {
-                            if let Some(project_id) = this.remote_id() {
-                                this.client
-                                    .send(proto::UpdateLanguageServer {
-                                        project_id,
-                                        language_server_id: language_server_id.0 as u64,
-                                        variant: Some(message),
-                                    })
-                                    .log_err();
-                            }
-                        })?;
-                    }
-                }
-            }
-
-            flush_operations(
-                &this,
-                &mut operations_by_buffer_id,
-                &mut needs_resync_with_host,
-                is_local,
-                &mut cx,
-            )
-            .await?;
-        }
-
-        Ok(())
-    }
-
-    fn on_buffer_event(
-        &mut self,
-        buffer: Model<Buffer>,
-        event: &BufferEvent,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        if matches!(
-            event,
-            BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged
-        ) {
-            self.request_buffer_diff_recalculation(&buffer, cx);
-        }
-
-        match event {
-            BufferEvent::Operation(operation) => {
-                self.buffer_ordered_messages_tx
-                    .unbounded_send(BufferOrderedMessage::Operation {
-                        buffer_id: buffer.read(cx).remote_id(),
-                        operation: language::proto::serialize_operation(operation),
-                    })
-                    .ok();
-            }
-
-            BufferEvent::Edited { .. } => {
-                let buffer = buffer.read(cx);
-                let file = File::from_dyn(buffer.file())?;
-                let abs_path = file.as_local()?.abs_path(cx);
-                let uri = lsp::Url::from_file_path(abs_path).unwrap();
-                let next_snapshot = buffer.text_snapshot();
-
-                let language_servers: Vec<_> = self
-                    .language_servers_for_buffer(buffer, cx)
-                    .map(|i| i.1.clone())
-                    .collect();
-
-                for language_server in language_servers {
-                    let language_server = language_server.clone();
-
-                    let buffer_snapshots = self
-                        .buffer_snapshots
-                        .get_mut(&buffer.remote_id())
-                        .and_then(|m| m.get_mut(&language_server.server_id()))?;
-                    let previous_snapshot = buffer_snapshots.last()?;
-
-                    let build_incremental_change = || {
-                        buffer
-                            .edits_since::<(PointUtf16, usize)>(
-                                previous_snapshot.snapshot.version(),
-                            )
-                            .map(|edit| {
-                                let edit_start = edit.new.start.0;
-                                let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
-                                let new_text = next_snapshot
-                                    .text_for_range(edit.new.start.1..edit.new.end.1)
-                                    .collect();
-                                lsp::TextDocumentContentChangeEvent {
-                                    range: Some(lsp::Range::new(
-                                        point_to_lsp(edit_start),
-                                        point_to_lsp(edit_end),
-                                    )),
-                                    range_length: None,
-                                    text: new_text,
-                                }
-                            })
-                            .collect()
-                    };
-
-                    let document_sync_kind = language_server
-                        .capabilities()
-                        .text_document_sync
-                        .as_ref()
-                        .and_then(|sync| match sync {
-                            lsp::TextDocumentSyncCapability::Kind(kind) => Some(*kind),
-                            lsp::TextDocumentSyncCapability::Options(options) => options.change,
-                        });
-
-                    let content_changes: Vec<_> = match document_sync_kind {
-                        Some(lsp::TextDocumentSyncKind::FULL) => {
-                            vec![lsp::TextDocumentContentChangeEvent {
-                                range: None,
-                                range_length: None,
-                                text: next_snapshot.text(),
-                            }]
-                        }
-                        Some(lsp::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(),
-                        _ => {
-                            #[cfg(any(test, feature = "test-support"))]
-                            {
-                                build_incremental_change()
-                            }
-
-                            #[cfg(not(any(test, feature = "test-support")))]
-                            {
-                                continue;
-                            }
-                        }
-                    };
-
-                    let next_version = previous_snapshot.version + 1;
-
-                    buffer_snapshots.push(LspBufferSnapshot {
-                        version: next_version,
-                        snapshot: next_snapshot.clone(),
-                    });
-
-                    language_server
-                        .notify::<lsp::notification::DidChangeTextDocument>(
-                            lsp::DidChangeTextDocumentParams {
-                                text_document: lsp::VersionedTextDocumentIdentifier::new(
-                                    uri.clone(),
-                                    next_version,
-                                ),
-                                content_changes,
-                            },
-                        )
-                        .log_err();
-                }
-            }
-
-            BufferEvent::Saved => {
-                let file = File::from_dyn(buffer.read(cx).file())?;
-                let worktree_id = file.worktree_id(cx);
-                let abs_path = file.as_local()?.abs_path(cx);
-                let text_document = lsp::TextDocumentIdentifier {
-                    uri: lsp::Url::from_file_path(abs_path).unwrap(),
-                };
-
-                for (_, _, server) in self.language_servers_for_worktree(worktree_id) {
-                    let text = include_text(server.as_ref()).then(|| buffer.read(cx).text());
-
-                    server
-                        .notify::<lsp::notification::DidSaveTextDocument>(
-                            lsp::DidSaveTextDocumentParams {
-                                text_document: text_document.clone(),
-                                text,
-                            },
-                        )
-                        .log_err();
-                }
-
-                let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx);
-                for language_server_id in language_server_ids {
-                    if let Some(LanguageServerState::Running {
-                        adapter,
-                        simulate_disk_based_diagnostics_completion,
-                        ..
-                    }) = self.language_servers.get_mut(&language_server_id)
-                    {
-                        // After saving a buffer using a language server that doesn't provide
-                        // a disk-based progress token, kick off a timer that will reset every
-                        // time the buffer is saved. If the timer eventually fires, simulate
-                        // disk-based diagnostics being finished so that other pieces of UI
-                        // (e.g., project diagnostics view, diagnostic status bar) can update.
-                        // We don't emit an event right away because the language server might take
-                        // some time to publish diagnostics.
-                        if adapter.disk_based_diagnostics_progress_token.is_none() {
-                            const DISK_BASED_DIAGNOSTICS_DEBOUNCE: Duration =
-                                Duration::from_secs(1);
-
-                            let task = cx.spawn(move |this, mut cx| async move {
-                                cx.background_executor().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await;
-                                if let Some(this) = this.upgrade() {
-                                    this.update(&mut cx, |this, cx| {
-                                        this.disk_based_diagnostics_finished(
-                                            language_server_id,
-                                            cx,
-                                        );
-                                        this.buffer_ordered_messages_tx
-                                            .unbounded_send(
-                                                BufferOrderedMessage::LanguageServerUpdate {
-                                                    language_server_id,
-                                                    message:proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(Default::default())
-                                                },
-                                            )
-                                            .ok();
-                                    }).ok();
-                                }
-                            });
-                            *simulate_disk_based_diagnostics_completion = Some(task);
-                        }
-                    }
-                }
-            }
-            BufferEvent::FileHandleChanged => {
-                let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
-                    return None;
-                };
-
-                let remote_id = buffer.read(cx).remote_id();
-                if let Some(entry_id) = file.entry_id {
-                    match self.local_buffer_ids_by_entry_id.get(&entry_id) {
-                        Some(_) => {
-                            return None;
-                        }
-                        None => {
-                            self.local_buffer_ids_by_entry_id
-                                .insert(entry_id, remote_id);
-                        }
-                    }
-                };
-                self.local_buffer_ids_by_path.insert(
-                    ProjectPath {
-                        worktree_id: file.worktree_id(cx),
-                        path: file.path.clone(),
-                    },
-                    remote_id,
-                );
-            }
-            _ => {}
-        }
-
-        None
-    }
-
-    fn request_buffer_diff_recalculation(
-        &mut self,
-        buffer: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.buffers_needing_diff.insert(buffer.downgrade());
-        let first_insertion = self.buffers_needing_diff.len() == 1;
-
-        let settings = ProjectSettings::get_global(cx);
-        let delay = if let Some(delay) = settings.git.gutter_debounce {
-            delay
-        } else {
-            if first_insertion {
-                let this = cx.weak_model();
-                cx.defer(move |cx| {
-                    if let Some(this) = this.upgrade() {
-                        this.update(cx, |this, cx| {
-                            this.recalculate_buffer_diffs(cx).detach();
-                        });
-                    }
-                });
-            }
-            return;
-        };
-
-        const MIN_DELAY: u64 = 50;
-        let delay = delay.max(MIN_DELAY);
-        let duration = Duration::from_millis(delay);
-
-        self.git_diff_debouncer
-            .fire_new(duration, cx, move |this, cx| {
-                this.recalculate_buffer_diffs(cx)
-            });
-    }
-
-    fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
-        let buffers = self.buffers_needing_diff.drain().collect::<Vec<_>>();
-        cx.spawn(move |this, mut cx| async move {
-            let tasks: Vec<_> = buffers
-                .iter()
-                .filter_map(|buffer| {
-                    let buffer = buffer.upgrade()?;
-                    buffer
-                        .update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx))
-                        .ok()
-                        .flatten()
-                })
-                .collect();
-
-            futures::future::join_all(tasks).await;
-
-            this.update(&mut cx, |this, cx| {
-                if !this.buffers_needing_diff.is_empty() {
-                    this.recalculate_buffer_diffs(cx).detach();
-                } else {
-                    // TODO: Would a `ModelContext<Project>.notify()` suffice here?
-                    for buffer in buffers {
-                        if let Some(buffer) = buffer.upgrade() {
-                            buffer.update(cx, |_, cx| cx.notify());
-                        }
-                    }
-                }
-            })
-            .ok();
-        })
-    }
-
-    fn language_servers_for_worktree(
-        &self,
-        worktree_id: WorktreeId,
-    ) -> impl Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<Language>, &Arc<LanguageServer>)> {
-        self.language_server_ids
-            .iter()
-            .filter_map(move |((language_server_worktree_id, _), id)| {
-                if *language_server_worktree_id == worktree_id {
-                    if let Some(LanguageServerState::Running {
-                        adapter,
-                        language,
-                        server,
-                        ..
-                    }) = self.language_servers.get(id)
-                    {
-                        return Some((adapter, language, server));
-                    }
-                }
-                None
-            })
-    }
-
-    fn maintain_buffer_languages(
-        languages: Arc<LanguageRegistry>,
-        cx: &mut ModelContext<Project>,
-    ) -> Task<()> {
-        let mut subscription = languages.subscribe();
-        let mut prev_reload_count = languages.reload_count();
-        cx.spawn(move |project, mut cx| async move {
-            while let Some(()) = subscription.next().await {
-                if let Some(project) = project.upgrade() {
-                    // If the language registry has been reloaded, then remove and
-                    // re-assign the languages on all open buffers.
-                    let reload_count = languages.reload_count();
-                    if reload_count > prev_reload_count {
-                        prev_reload_count = reload_count;
-                        project
-                            .update(&mut cx, |this, cx| {
-                                let buffers = this
-                                    .opened_buffers
-                                    .values()
-                                    .filter_map(|b| b.upgrade())
-                                    .collect::<Vec<_>>();
-                                for buffer in buffers {
-                                    if let Some(f) = File::from_dyn(buffer.read(cx).file()).cloned()
-                                    {
-                                        this.unregister_buffer_from_language_servers(
-                                            &buffer, &f, cx,
-                                        );
-                                        buffer
-                                            .update(cx, |buffer, cx| buffer.set_language(None, cx));
-                                    }
-                                }
-                            })
-                            .ok();
-                    }
-
-                    project
-                        .update(&mut cx, |project, cx| {
-                            let mut plain_text_buffers = Vec::new();
-                            let mut buffers_with_unknown_injections = Vec::new();
-                            for buffer in project.opened_buffers.values() {
-                                if let Some(handle) = buffer.upgrade() {
-                                    let buffer = &handle.read(cx);
-                                    if buffer.language().is_none()
-                                        || buffer.language() == Some(&*language::PLAIN_TEXT)
-                                    {
-                                        plain_text_buffers.push(handle);
-                                    } else if buffer.contains_unknown_injections() {
-                                        buffers_with_unknown_injections.push(handle);
-                                    }
-                                }
-                            }
-
-                            for buffer in plain_text_buffers {
-                                project.detect_language_for_buffer(&buffer, cx);
-                                project.register_buffer_with_language_servers(&buffer, cx);
-                            }
-
-                            for buffer in buffers_with_unknown_injections {
-                                buffer.update(cx, |buffer, cx| buffer.reparse(cx));
-                            }
-                        })
-                        .ok();
-                }
-            }
-        })
-    }
-
-    fn maintain_workspace_config(cx: &mut ModelContext<Project>) -> Task<Result<()>> {
-        let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
-        let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
-
-        let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
-            *settings_changed_tx.borrow_mut() = ();
-        });
-
-        cx.spawn(move |this, mut cx| async move {
-            while let Some(_) = settings_changed_rx.next().await {
-                let servers: Vec<_> = this.update(&mut cx, |this, _| {
-                    this.language_servers
-                        .values()
-                        .filter_map(|state| match state {
-                            LanguageServerState::Starting(_) => None,
-                            LanguageServerState::Running {
-                                adapter, server, ..
-                            } => Some((adapter.clone(), server.clone())),
-                        })
-                        .collect()
-                })?;
-
-                for (adapter, server) in servers {
-                    let workspace_config = cx
-                        .update(|cx| adapter.workspace_configuration(server.root_path(), cx))?
-                        .await;
-                    server
-                        .notify::<lsp::notification::DidChangeConfiguration>(
-                            lsp::DidChangeConfigurationParams {
-                                settings: workspace_config.clone(),
-                            },
-                        )
-                        .ok();
-                }
-            }
-
-            drop(settings_observation);
-            anyhow::Ok(())
-        })
-    }
-
-    fn detect_language_for_buffer(
-        &mut self,
-        buffer_handle: &Model<Buffer>,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        // If the buffer has a language, set it and start the language server if we haven't already.
-        let buffer = buffer_handle.read(cx);
-        let full_path = buffer.file()?.full_path(cx);
-        let content = buffer.as_rope();
-        let new_language = self
-            .languages
-            .language_for_file(&full_path, Some(content))
-            .now_or_never()?
-            .ok()?;
-        self.set_language_for_buffer(buffer_handle, new_language, cx);
-        None
-    }
-
-    pub fn set_language_for_buffer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        new_language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        buffer.update(cx, |buffer, cx| {
-            if buffer.language().map_or(true, |old_language| {
-                !Arc::ptr_eq(old_language, &new_language)
-            }) {
-                buffer.set_language(Some(new_language.clone()), cx);
-            }
-        });
-
-        let buffer_file = buffer.read(cx).file().cloned();
-        let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
-        let buffer_file = File::from_dyn(buffer_file.as_ref());
-        let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
-        if let Some(prettier_plugins) =
-            prettier_support::prettier_plugins_for_language(&new_language, &settings)
-        {
-            self.install_default_prettier(worktree, prettier_plugins, cx);
-        };
-        if let Some(file) = buffer_file {
-            let worktree = file.worktree.clone();
-            if let Some(tree) = worktree.read(cx).as_local() {
-                self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx);
-            }
-        }
-    }
-
-    fn start_language_servers(
-        &mut self,
-        worktree: &Model<Worktree>,
-        worktree_path: Arc<Path>,
-        language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx));
-        let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx);
-        if !settings.enable_language_server {
-            return;
-        }
-
-        let worktree_id = worktree.read(cx).id();
-        for adapter in language.lsp_adapters() {
-            self.start_language_server(
-                worktree_id,
-                worktree_path.clone(),
-                adapter.clone(),
-                language.clone(),
-                cx,
-            );
-        }
-    }
-
-    fn start_language_server(
-        &mut self,
-        worktree_id: WorktreeId,
-        worktree_path: Arc<Path>,
-        adapter: Arc<CachedLspAdapter>,
-        language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if adapter.reinstall_attempt_count.load(SeqCst) > MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
-            return;
-        }
-
-        let key = (worktree_id, adapter.name.clone());
-        if self.language_server_ids.contains_key(&key) {
-            return;
-        }
-
-        let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
-        let pending_server = match self.languages.create_pending_language_server(
-            stderr_capture.clone(),
-            language.clone(),
-            adapter.clone(),
-            Arc::clone(&worktree_path),
-            ProjectLspAdapterDelegate::new(self, cx),
-            cx,
-        ) {
-            Some(pending_server) => pending_server,
-            None => return,
-        };
-
-        let project_settings = ProjectSettings::get_global(cx);
-        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({
-            let adapter = adapter.clone();
-            let server_name = adapter.name.0.clone();
-            let language = language.clone();
-            let key = key.clone();
-
-            cx.spawn(move |this, mut cx| async move {
-                let result = Self::setup_and_insert_language_server(
-                    this.clone(),
-                    &worktree_path,
-                    initialization_options,
-                    pending_server,
-                    adapter.clone(),
-                    language.clone(),
-                    server_id,
-                    key,
-                    &mut cx,
-                )
-                .await;
-
-                match result {
-                    Ok(server) => {
-                        stderr_capture.lock().take();
-                        server
-                    }
-
-                    Err(err) => {
-                        log::error!("failed to start language server {server_name:?}: {err}");
-                        log::error!("server stderr: {:?}", stderr_capture.lock().take());
-
-                        let this = this.upgrade()?;
-                        let container_dir = container_dir?;
-
-                        let attempt_count = adapter.reinstall_attempt_count.fetch_add(1, SeqCst);
-                        if attempt_count >= MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
-                            let max = MAX_SERVER_REINSTALL_ATTEMPT_COUNT;
-                            log::error!("Hit {max} reinstallation attempts for {server_name:?}");
-                            return None;
-                        }
-
-                        let installation_test_binary = adapter
-                            .installation_test_binary(container_dir.to_path_buf())
-                            .await;
-
-                        this.update(&mut cx, |_, cx| {
-                            Self::check_errored_server(
-                                language,
-                                adapter,
-                                server_id,
-                                installation_test_binary,
-                                cx,
-                            )
-                        })
-                        .ok();
-
-                        None
-                    }
-                }
-            })
-        });
-
-        self.language_servers.insert(server_id, state);
-        self.language_server_ids.insert(key, server_id);
-    }
-
-    fn reinstall_language_server(
-        &mut self,
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<Task<()>> {
-        log::info!("beginning to reinstall server");
-
-        let existing_server = match self.language_servers.remove(&server_id) {
-            Some(LanguageServerState::Running { server, .. }) => Some(server),
-            _ => None,
-        };
-
-        for worktree in &self.worktrees {
-            if let Some(worktree) = worktree.upgrade() {
-                let key = (worktree.read(cx).id(), adapter.name.clone());
-                self.language_server_ids.remove(&key);
-            }
-        }
-
-        Some(cx.spawn(move |this, mut cx| async move {
-            if let Some(task) = existing_server.and_then(|server| server.shutdown()) {
-                log::info!("shutting down existing server");
-                task.await;
-            }
-
-            // TODO: This is race-safe with regards to preventing new instances from
-            // starting while deleting, but existing instances in other projects are going
-            // to be very confused and messed up
-            let Some(task) = this
-                .update(&mut cx, |this, cx| {
-                    this.languages.delete_server_container(adapter.clone(), cx)
-                })
-                .log_err()
-            else {
-                return;
-            };
-            task.await;
-
-            this.update(&mut cx, |this, mut cx| {
-                let worktrees = this.worktrees.clone();
-                for worktree in worktrees {
-                    let worktree = match worktree.upgrade() {
-                        Some(worktree) => worktree.read(cx),
-                        None => continue,
-                    };
-                    let worktree_id = worktree.id();
-                    let root_path = worktree.abs_path();
-
-                    this.start_language_server(
-                        worktree_id,
-                        root_path,
-                        adapter.clone(),
-                        language.clone(),
-                        &mut cx,
-                    );
-                }
-            })
-            .ok();
-        }))
-    }
-
-    async fn setup_and_insert_language_server(
-        this: WeakModel<Self>,
-        worktree_path: &Path,
-        initialization_options: Option<serde_json::Value>,
-        pending_server: PendingLanguageServer,
-        adapter: Arc<CachedLspAdapter>,
-        language: Arc<Language>,
-        server_id: LanguageServerId,
-        key: (WorktreeId, LanguageServerName),
-        cx: &mut AsyncAppContext,
-    ) -> Result<Option<Arc<LanguageServer>>> {
-        let language_server = Self::setup_pending_language_server(
-            this.clone(),
-            initialization_options,
-            pending_server,
-            worktree_path,
-            adapter.clone(),
-            server_id,
-            cx,
-        )
-        .await?;
-
-        let this = match this.upgrade() {
-            Some(this) => this,
-            None => return Err(anyhow!("failed to upgrade project handle")),
-        };
-
-        this.update(cx, |this, cx| {
-            this.insert_newly_running_language_server(
-                language,
-                adapter,
-                language_server.clone(),
-                server_id,
-                key,
-                cx,
-            )
-        })??;
-
-        Ok(Some(language_server))
-    }
-
-    async fn setup_pending_language_server(
-        this: WeakModel<Self>,
-        initialization_options: Option<serde_json::Value>,
-        pending_server: PendingLanguageServer,
-        worktree_path: &Path,
-        adapter: Arc<CachedLspAdapter>,
-        server_id: LanguageServerId,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Arc<LanguageServer>> {
-        let workspace_config = cx
-            .update(|cx| adapter.workspace_configuration(worktree_path, cx))?
-            .await;
-        let language_server = pending_server.task.await?;
-
-        language_server
-            .on_notification::<lsp::notification::PublishDiagnostics, _>({
-                let adapter = adapter.clone();
-                let this = this.clone();
-                move |mut params, mut cx| {
-                    let adapter = adapter.clone();
-                    if let Some(this) = this.upgrade() {
-                        adapter.process_diagnostics(&mut params);
-                        this.update(&mut cx, |this, cx| {
-                            this.update_diagnostics(
-                                server_id,
-                                params,
-                                &adapter.disk_based_diagnostic_sources,
-                                cx,
-                            )
-                            .log_err();
-                        })
-                        .ok();
-                    }
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
-                let adapter = adapter.clone();
-                let worktree_path = worktree_path.to_path_buf();
-                move |params, cx| {
-                    let adapter = adapter.clone();
-                    let worktree_path = worktree_path.clone();
-                    async move {
-                        let workspace_config = cx
-                            .update(|cx| adapter.workspace_configuration(&worktree_path, cx))?
-                            .await;
-                        Ok(params
-                            .items
-                            .into_iter()
-                            .map(|item| {
-                                if let Some(section) = &item.section {
-                                    workspace_config
-                                        .get(section)
-                                        .cloned()
-                                        .unwrap_or(serde_json::Value::Null)
-                                } else {
-                                    workspace_config.clone()
-                                }
-                            })
-                            .collect())
-                    }
-                }
-            })
-            .detach();
-
-        // Even though we don't have handling for these requests, respond to them to
-        // avoid stalling any language server like `gopls` which waits for a response
-        // to these requests when initializing.
-        language_server
-            .on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
-                let this = this.clone();
-                move |params, mut cx| {
-                    let this = this.clone();
-                    async move {
-                        this.update(&mut cx, |this, _| {
-                            if let Some(status) = this.language_server_statuses.get_mut(&server_id)
-                            {
-                                if let lsp::NumberOrString::String(token) = params.token {
-                                    status.progress_tokens.insert(token);
-                                }
-                            }
-                        })?;
-
-                        Ok(())
-                    }
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::RegisterCapability, _, _>({
-                let this = this.clone();
-                move |params, mut cx| {
-                    let this = this.clone();
-                    async move {
-                        for reg in params.registrations {
-                            if reg.method == "workspace/didChangeWatchedFiles" {
-                                if let Some(options) = reg.register_options {
-                                    let options = serde_json::from_value(options)?;
-                                    this.update(&mut cx, |this, cx| {
-                                        this.on_lsp_did_change_watched_files(
-                                            server_id, options, cx,
-                                        );
-                                    })?;
-                                }
-                            }
-                        }
-                        Ok(())
-                    }
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
-                let adapter = adapter.clone();
-                let this = this.clone();
-                move |params, cx| {
-                    Self::on_lsp_workspace_edit(
-                        this.clone(),
-                        params,
-                        server_id,
-                        adapter.clone(),
-                        cx,
-                    )
-                }
-            })
-            .detach();
-
-        language_server
-            .on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
-                let this = this.clone();
-                move |(), mut cx| {
-                    let this = this.clone();
-                    async move {
-                        this.update(&mut cx, |project, cx| {
-                            cx.emit(Event::RefreshInlayHints);
-                            project.remote_id().map(|project_id| {
-                                project.client.send(proto::RefreshInlayHints { project_id })
-                            })
-                        })?
-                        .transpose()?;
-                        Ok(())
-                    }
-                }
-            })
-            .detach();
-
-        let disk_based_diagnostics_progress_token =
-            adapter.disk_based_diagnostics_progress_token.clone();
-
-        language_server
-            .on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
-                if let Some(this) = this.upgrade() {
-                    this.update(&mut cx, |this, cx| {
-                        this.on_lsp_progress(
-                            params,
-                            server_id,
-                            disk_based_diagnostics_progress_token.clone(),
-                            cx,
-                        );
-                    })
-                    .ok();
-                }
-            })
-            .detach();
-
-        let language_server = language_server.initialize(initialization_options).await?;
-
-        language_server
-            .notify::<lsp::notification::DidChangeConfiguration>(
-                lsp::DidChangeConfigurationParams {
-                    settings: workspace_config,
-                },
-            )
-            .ok();
-
-        Ok(language_server)
-    }
-
-    fn insert_newly_running_language_server(
-        &mut self,
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        language_server: Arc<LanguageServer>,
-        server_id: LanguageServerId,
-        key: (WorktreeId, LanguageServerName),
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        // If the language server for this key doesn't match the server id, don't store the
-        // server. Which will cause it to be dropped, killing the process
-        if self
-            .language_server_ids
-            .get(&key)
-            .map(|id| id != &server_id)
-            .unwrap_or(false)
-        {
-            return Ok(());
-        }
-
-        // Update language_servers collection with Running variant of LanguageServerState
-        // indicating that the server is up and running and ready
-        self.language_servers.insert(
-            server_id,
-            LanguageServerState::Running {
-                adapter: adapter.clone(),
-                language: language.clone(),
-                watched_paths: Default::default(),
-                server: language_server.clone(),
-                simulate_disk_based_diagnostics_completion: None,
-            },
-        );
-
-        self.language_server_statuses.insert(
-            server_id,
-            LanguageServerStatus {
-                name: language_server.name().to_string(),
-                pending_work: Default::default(),
-                has_pending_diagnostic_updates: false,
-                progress_tokens: Default::default(),
-            },
-        );
-
-        cx.emit(Event::LanguageServerAdded(server_id));
-
-        if let Some(project_id) = self.remote_id() {
-            self.client.send(proto::StartLanguageServer {
-                project_id,
-                server: Some(proto::LanguageServer {
-                    id: server_id.0 as u64,
-                    name: language_server.name().to_string(),
-                }),
-            })?;
-        }
-
-        // Tell the language server about every open buffer in the worktree that matches the language.
-        for buffer in self.opened_buffers.values() {
-            if let Some(buffer_handle) = buffer.upgrade() {
-                let buffer = buffer_handle.read(cx);
-                let file = match File::from_dyn(buffer.file()) {
-                    Some(file) => file,
-                    None => continue,
-                };
-                let language = match buffer.language() {
-                    Some(language) => language,
-                    None => continue,
-                };
-
-                if file.worktree.read(cx).id() != key.0
-                    || !language.lsp_adapters().iter().any(|a| a.name == key.1)
-                {
-                    continue;
-                }
-
-                let file = match file.as_local() {
-                    Some(file) => file,
-                    None => continue,
-                };
-
-                let versions = self
-                    .buffer_snapshots
-                    .entry(buffer.remote_id())
-                    .or_default()
-                    .entry(server_id)
-                    .or_insert_with(|| {
-                        vec![LspBufferSnapshot {
-                            version: 0,
-                            snapshot: buffer.text_snapshot(),
-                        }]
-                    });
-
-                let snapshot = versions.last().unwrap();
-                let version = snapshot.version;
-                let initial_snapshot = &snapshot.snapshot;
-                let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                language_server.notify::<lsp::notification::DidOpenTextDocument>(
-                    lsp::DidOpenTextDocumentParams {
-                        text_document: lsp::TextDocumentItem::new(
-                            uri,
-                            adapter
-                                .language_ids
-                                .get(language.name().as_ref())
-                                .cloned()
-                                .unwrap_or_default(),
-                            version,
-                            initial_snapshot.text(),
-                        ),
-                    },
-                )?;
-
-                buffer_handle.update(cx, |buffer, cx| {
-                    buffer.set_completion_triggers(
-                        language_server
-                            .capabilities()
-                            .completion_provider
-                            .as_ref()
-                            .and_then(|provider| provider.trigger_characters.clone())
-                            .unwrap_or_default(),
-                        cx,
-                    )
-                });
-            }
-        }
-
-        cx.notify();
-        Ok(())
-    }
-
-    // Returns a list of all of the worktrees which no longer have a language server and the root path
-    // for the stopped server
-    fn stop_language_server(
-        &mut self,
-        worktree_id: WorktreeId,
-        adapter_name: LanguageServerName,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<(Option<PathBuf>, Vec<WorktreeId>)> {
-        let key = (worktree_id, adapter_name);
-        if let Some(server_id) = self.language_server_ids.remove(&key) {
-            log::info!("stopping language server {}", key.1 .0);
-
-            // Remove other entries for this language server as well
-            let mut orphaned_worktrees = vec![worktree_id];
-            let other_keys = self.language_server_ids.keys().cloned().collect::<Vec<_>>();
-            for other_key in other_keys {
-                if self.language_server_ids.get(&other_key) == Some(&server_id) {
-                    self.language_server_ids.remove(&other_key);
-                    orphaned_worktrees.push(other_key.0);
-                }
-            }
-
-            for buffer in self.opened_buffers.values() {
-                if let Some(buffer) = buffer.upgrade() {
-                    buffer.update(cx, |buffer, cx| {
-                        buffer.update_diagnostics(server_id, Default::default(), cx);
-                    });
-                }
-            }
-            for worktree in &self.worktrees {
-                if let Some(worktree) = worktree.upgrade() {
-                    worktree.update(cx, |worktree, cx| {
-                        if let Some(worktree) = worktree.as_local_mut() {
-                            worktree.clear_diagnostics_for_language_server(server_id, cx);
-                        }
-                    });
-                }
-            }
-
-            self.language_server_statuses.remove(&server_id);
-            cx.notify();
-
-            let server_state = self.language_servers.remove(&server_id);
-            cx.emit(Event::LanguageServerRemoved(server_id));
-            cx.spawn(move |this, mut cx| async move {
-                let mut root_path = None;
-
-                let server = match server_state {
-                    Some(LanguageServerState::Starting(task)) => task.await,
-                    Some(LanguageServerState::Running { server, .. }) => Some(server),
-                    None => None,
-                };
-
-                if let Some(server) = server {
-                    root_path = Some(server.root_path().clone());
-                    if let Some(shutdown) = server.shutdown() {
-                        shutdown.await;
-                    }
-                }
-
-                if let Some(this) = this.upgrade() {
-                    this.update(&mut cx, |this, cx| {
-                        this.language_server_statuses.remove(&server_id);
-                        cx.notify();
-                    })
-                    .ok();
-                }
-
-                (root_path, orphaned_worktrees)
-            })
-        } else {
-            Task::ready((None, Vec::new()))
-        }
-    }
-
-    pub fn restart_language_servers_for_buffers(
-        &mut self,
-        buffers: impl IntoIterator<Item = Model<Buffer>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Option<()> {
-        let language_server_lookup_info: HashSet<(Model<Worktree>, Arc<Language>)> = buffers
-            .into_iter()
-            .filter_map(|buffer| {
-                let buffer = buffer.read(cx);
-                let file = File::from_dyn(buffer.file())?;
-                let full_path = file.full_path(cx);
-                let language = self
-                    .languages
-                    .language_for_file(&full_path, Some(buffer.as_rope()))
-                    .now_or_never()?
-                    .ok()?;
-                Some((file.worktree.clone(), language))
-            })
-            .collect();
-        for (worktree, language) in language_server_lookup_info {
-            self.restart_language_servers(worktree, language, cx);
-        }
-
-        None
-    }
-
-    // TODO This will break in the case where the adapter's root paths and worktrees are not equal
-    fn restart_language_servers(
-        &mut self,
-        worktree: Model<Worktree>,
-        language: Arc<Language>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let worktree_id = worktree.read(cx).id();
-        let fallback_path = worktree.read(cx).abs_path();
-
-        let mut stops = Vec::new();
-        for adapter in language.lsp_adapters() {
-            stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx));
-        }
-
-        if stops.is_empty() {
-            return;
-        }
-        let mut stops = stops.into_iter();
-
-        cx.spawn(move |this, mut cx| async move {
-            let (original_root_path, mut orphaned_worktrees) = stops.next().unwrap().await;
-            for stop in stops {
-                let (_, worktrees) = stop.await;
-                orphaned_worktrees.extend_from_slice(&worktrees);
-            }
-
-            let this = match this.upgrade() {
-                Some(this) => this,
-                None => return,
-            };
-
-            this.update(&mut cx, |this, cx| {
-                // Attempt to restart using original server path. Fallback to passed in
-                // path if we could not retrieve the root path
-                let root_path = original_root_path
-                    .map(|path_buf| Arc::from(path_buf.as_path()))
-                    .unwrap_or(fallback_path);
-
-                this.start_language_servers(&worktree, root_path, language.clone(), cx);
-
-                // Lookup new server ids and set them for each of the orphaned worktrees
-                for adapter in language.lsp_adapters() {
-                    if let Some(new_server_id) = this
-                        .language_server_ids
-                        .get(&(worktree_id, adapter.name.clone()))
-                        .cloned()
-                    {
-                        for &orphaned_worktree in &orphaned_worktrees {
-                            this.language_server_ids
-                                .insert((orphaned_worktree, adapter.name.clone()), new_server_id);
-                        }
-                    }
-                }
-            })
-            .ok();
-        })
-        .detach();
-    }
-
-    fn check_errored_server(
-        language: Arc<Language>,
-        adapter: Arc<CachedLspAdapter>,
-        server_id: LanguageServerId,
-        installation_test_binary: Option<LanguageServerBinary>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if !adapter.can_be_reinstalled() {
-            log::info!(
-                "Validation check requested for {:?} but it cannot be reinstalled",
-                adapter.name.0
-            );
-            return;
-        }
-
-        cx.spawn(move |this, mut cx| async move {
-            log::info!("About to spawn test binary");
-
-            // A lack of test binary counts as a failure
-            let process = installation_test_binary.and_then(|binary| {
-                smol::process::Command::new(&binary.path)
-                    .current_dir(&binary.path)
-                    .args(binary.arguments)
-                    .stdin(Stdio::piped())
-                    .stdout(Stdio::piped())
-                    .stderr(Stdio::inherit())
-                    .kill_on_drop(true)
-                    .spawn()
-                    .ok()
-            });
-
-            const PROCESS_TIMEOUT: Duration = Duration::from_secs(5);
-            let mut timeout = cx.background_executor().timer(PROCESS_TIMEOUT).fuse();
-
-            let mut errored = false;
-            if let Some(mut process) = process {
-                futures::select! {
-                    status = process.status().fuse() => match status {
-                        Ok(status) => errored = !status.success(),
-                        Err(_) => errored = true,
-                    },
-
-                    _ = timeout => {
-                        log::info!("test binary time-ed out, this counts as a success");
-                        _ = process.kill();
-                    }
-                }
-            } else {
-                log::warn!("test binary failed to launch");
-                errored = true;
-            }
-
-            if errored {
-                log::warn!("test binary check failed");
-                let task = this
-                    .update(&mut cx, move |this, mut cx| {
-                        this.reinstall_language_server(language, adapter, server_id, &mut cx)
-                    })
-                    .ok()
-                    .flatten();
-
-                if let Some(task) = task {
-                    task.await;
-                }
-            }
-        })
-        .detach();
-    }
-
-    fn on_lsp_progress(
-        &mut self,
-        progress: lsp::ProgressParams,
-        language_server_id: LanguageServerId,
-        disk_based_diagnostics_progress_token: Option<String>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let token = match progress.token {
-            lsp::NumberOrString::String(token) => token,
-            lsp::NumberOrString::Number(token) => {
-                log::info!("skipping numeric progress token {}", token);
-                return;
-            }
-        };
-        let lsp::ProgressParamsValue::WorkDone(progress) = progress.value;
-        let language_server_status =
-            if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-                status
-            } else {
-                return;
-            };
-
-        if !language_server_status.progress_tokens.contains(&token) {
-            return;
-        }
-
-        let is_disk_based_diagnostics_progress = disk_based_diagnostics_progress_token
-            .as_ref()
-            .map_or(false, |disk_based_token| {
-                token.starts_with(disk_based_token)
-            });
-
-        match progress {
-            lsp::WorkDoneProgress::Begin(report) => {
-                if is_disk_based_diagnostics_progress {
-                    language_server_status.has_pending_diagnostic_updates = true;
-                    self.disk_based_diagnostics_started(language_server_id, cx);
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(Default::default())
-                        })
-                        .ok();
-                } else {
-                    self.on_lsp_work_start(
-                        language_server_id,
-                        token.clone(),
-                        LanguageServerProgress {
-                            message: report.message.clone(),
-                            percentage: report.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::WorkStart(
-                                proto::LspWorkStart {
-                                    token,
-                                    message: report.message,
-                                    percentage: report.percentage.map(|p| p as u32),
-                                },
-                            ),
-                        })
-                        .ok();
-                }
-            }
-            lsp::WorkDoneProgress::Report(report) => {
-                if !is_disk_based_diagnostics_progress {
-                    self.on_lsp_work_progress(
-                        language_server_id,
-                        token.clone(),
-                        LanguageServerProgress {
-                            message: report.message.clone(),
-                            percentage: report.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::WorkProgress(
-                                proto::LspWorkProgress {
-                                    token,
-                                    message: report.message,
-                                    percentage: report.percentage.map(|p| p as u32),
-                                },
-                            ),
-                        })
-                        .ok();
-                }
-            }
-            lsp::WorkDoneProgress::End(_) => {
-                language_server_status.progress_tokens.remove(&token);
-
-                if is_disk_based_diagnostics_progress {
-                    language_server_status.has_pending_diagnostic_updates = false;
-                    self.disk_based_diagnostics_finished(language_server_id, cx);
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message:
-                                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
-                                    Default::default(),
-                                ),
-                        })
-                        .ok();
-                } else {
-                    self.on_lsp_work_end(language_server_id, token.clone(), cx);
-                    self.buffer_ordered_messages_tx
-                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
-                            language_server_id,
-                            message: proto::update_language_server::Variant::WorkEnd(
-                                proto::LspWorkEnd { token },
-                            ),
-                        })
-                        .ok();
-                }
-            }
-        }
-    }
-
-    fn on_lsp_work_start(
-        &mut self,
-        language_server_id: LanguageServerId,
-        token: String,
-        progress: LanguageServerProgress,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-            status.pending_work.insert(token, progress);
-            cx.notify();
-        }
-    }
-
-    fn on_lsp_work_progress(
-        &mut self,
-        language_server_id: LanguageServerId,
-        token: String,
-        progress: LanguageServerProgress,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-            let entry = status
-                .pending_work
-                .entry(token)
-                .or_insert(LanguageServerProgress {
-                    message: Default::default(),
-                    percentage: Default::default(),
-                    last_update_at: progress.last_update_at,
-                });
-            if progress.message.is_some() {
-                entry.message = progress.message;
-            }
-            if progress.percentage.is_some() {
-                entry.percentage = progress.percentage;
-            }
-            entry.last_update_at = progress.last_update_at;
-            cx.notify();
-        }
-    }
-
-    fn on_lsp_work_end(
-        &mut self,
-        language_server_id: LanguageServerId,
-        token: String,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
-            cx.emit(Event::RefreshInlayHints);
-            status.pending_work.remove(&token);
-            cx.notify();
-        }
-    }
-
-    fn on_lsp_did_change_watched_files(
-        &mut self,
-        language_server_id: LanguageServerId,
-        params: DidChangeWatchedFilesRegistrationOptions,
-        cx: &mut ModelContext<Self>,
-    ) {
-        if let Some(LanguageServerState::Running { watched_paths, .. }) =
-            self.language_servers.get_mut(&language_server_id)
-        {
-            let mut builders = HashMap::default();
-            for watcher in params.watchers {
-                for worktree in &self.worktrees {
-                    if let Some(worktree) = worktree.upgrade() {
-                        let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
-                            if let Some(abs_path) = tree.abs_path().to_str() {
-                                let relative_glob_pattern = match &watcher.glob_pattern {
-                                    lsp::GlobPattern::String(s) => s
-                                        .strip_prefix(abs_path)
-                                        .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)),
-                                    lsp::GlobPattern::Relative(rp) => {
-                                        let base_uri = match &rp.base_uri {
-                                            lsp::OneOf::Left(workspace_folder) => {
-                                                &workspace_folder.uri
-                                            }
-                                            lsp::OneOf::Right(base_uri) => base_uri,
-                                        };
-                                        base_uri.to_file_path().ok().and_then(|file_path| {
-                                            (file_path.to_str() == Some(abs_path))
-                                                .then_some(rp.pattern.as_str())
-                                        })
-                                    }
-                                };
-                                if let Some(relative_glob_pattern) = relative_glob_pattern {
-                                    let literal_prefix =
-                                        glob_literal_prefix(&relative_glob_pattern);
-                                    tree.as_local_mut()
-                                        .unwrap()
-                                        .add_path_prefix_to_scan(Path::new(literal_prefix).into());
-                                    if let Some(glob) = Glob::new(relative_glob_pattern).log_err() {
-                                        builders
-                                            .entry(tree.id())
-                                            .or_insert_with(|| GlobSetBuilder::new())
-                                            .add(glob);
-                                    }
-                                    return true;
-                                }
-                            }
-                            false
-                        });
-                        if glob_is_inside_worktree {
-                            break;
-                        }
-                    }
-                }
-            }
-
-            watched_paths.clear();
-            for (worktree_id, builder) in builders {
-                if let Ok(globset) = builder.build() {
-                    watched_paths.insert(worktree_id, globset);
-                }
-            }
-
-            cx.notify();
-        }
-    }
-
-    async fn on_lsp_workspace_edit(
-        this: WeakModel<Self>,
-        params: lsp::ApplyWorkspaceEditParams,
-        server_id: LanguageServerId,
-        adapter: Arc<CachedLspAdapter>,
-        mut cx: AsyncAppContext,
-    ) -> Result<lsp::ApplyWorkspaceEditResponse> {
-        let this = this
-            .upgrade()
-            .ok_or_else(|| anyhow!("project project closed"))?;
-        let language_server = this
-            .update(&mut cx, |this, _| this.language_server_for_id(server_id))?
-            .ok_or_else(|| anyhow!("language server not found"))?;
-        let transaction = Self::deserialize_workspace_edit(
-            this.clone(),
-            params.edit,
-            true,
-            adapter.clone(),
-            language_server.clone(),
-            &mut cx,
-        )
-        .await
-        .log_err();
-        this.update(&mut cx, |this, _| {
-            if let Some(transaction) = transaction {
-                this.last_workspace_edits_by_language_server
-                    .insert(server_id, transaction);
-            }
-        })?;
-        Ok(lsp::ApplyWorkspaceEditResponse {
-            applied: true,
-            failed_change: None,
-            failure_reason: None,
-        })
-    }
-
-    pub fn language_server_statuses(
-        &self,
-    ) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
-        self.language_server_statuses.values()
-    }
-
-    pub fn update_diagnostics(
-        &mut self,
-        language_server_id: LanguageServerId,
-        mut params: lsp::PublishDiagnosticsParams,
-        disk_based_sources: &[String],
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        let abs_path = params
-            .uri
-            .to_file_path()
-            .map_err(|_| anyhow!("URI is not a file"))?;
-        let mut diagnostics = Vec::default();
-        let mut primary_diagnostic_group_ids = HashMap::default();
-        let mut sources_by_group_id = HashMap::default();
-        let mut supporting_diagnostics = HashMap::default();
-
-        // Ensure that primary diagnostics are always the most severe
-        params.diagnostics.sort_by_key(|item| item.severity);
-
-        for diagnostic in &params.diagnostics {
-            let source = diagnostic.source.as_ref();
-            let code = diagnostic.code.as_ref().map(|code| match code {
-                lsp::NumberOrString::Number(code) => code.to_string(),
-                lsp::NumberOrString::String(code) => code.clone(),
-            });
-            let range = range_from_lsp(diagnostic.range);
-            let is_supporting = diagnostic
-                .related_information
-                .as_ref()
-                .map_or(false, |infos| {
-                    infos.iter().any(|info| {
-                        primary_diagnostic_group_ids.contains_key(&(
-                            source,
-                            code.clone(),
-                            range_from_lsp(info.location.range),
-                        ))
-                    })
-                });
-
-            let is_unnecessary = diagnostic.tags.as_ref().map_or(false, |tags| {
-                tags.iter().any(|tag| *tag == DiagnosticTag::UNNECESSARY)
-            });
-
-            if is_supporting {
-                supporting_diagnostics.insert(
-                    (source, code.clone(), range),
-                    (diagnostic.severity, is_unnecessary),
-                );
-            } else {
-                let group_id = post_inc(&mut self.next_diagnostic_group_id);
-                let is_disk_based =
-                    source.map_or(false, |source| disk_based_sources.contains(source));
-
-                sources_by_group_id.insert(group_id, source);
-                primary_diagnostic_group_ids
-                    .insert((source, code.clone(), range.clone()), group_id);
-
-                diagnostics.push(DiagnosticEntry {
-                    range,
-                    diagnostic: Diagnostic {
-                        source: diagnostic.source.clone(),
-                        code: code.clone(),
-                        severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
-                        message: diagnostic.message.clone(),
-                        group_id,
-                        is_primary: true,
-                        is_valid: true,
-                        is_disk_based,
-                        is_unnecessary,
-                    },
-                });
-                if let Some(infos) = &diagnostic.related_information {
-                    for info in infos {
-                        if info.location.uri == params.uri && !info.message.is_empty() {
-                            let range = range_from_lsp(info.location.range);
-                            diagnostics.push(DiagnosticEntry {
-                                range,
-                                diagnostic: Diagnostic {
-                                    source: diagnostic.source.clone(),
-                                    code: code.clone(),
-                                    severity: DiagnosticSeverity::INFORMATION,
-                                    message: info.message.clone(),
-                                    group_id,
-                                    is_primary: false,
-                                    is_valid: true,
-                                    is_disk_based,
-                                    is_unnecessary: false,
-                                },
-                            });
-                        }
-                    }
-                }
-            }
-        }
-
-        for entry in &mut diagnostics {
-            let diagnostic = &mut entry.diagnostic;
-            if !diagnostic.is_primary {
-                let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
-                if let Some(&(severity, is_unnecessary)) = supporting_diagnostics.get(&(
-                    source,
-                    diagnostic.code.clone(),
-                    entry.range.clone(),
-                )) {
-                    if let Some(severity) = severity {
-                        diagnostic.severity = severity;
-                    }
-                    diagnostic.is_unnecessary = is_unnecessary;
-                }
-            }
-        }
-
-        self.update_diagnostic_entries(
-            language_server_id,
-            abs_path,
-            params.version,
-            diagnostics,
-            cx,
-        )?;
-        Ok(())
-    }
-
-    pub fn update_diagnostic_entries(
-        &mut self,
-        server_id: LanguageServerId,
-        abs_path: PathBuf,
-        version: Option<i32>,
-        diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        cx: &mut ModelContext<Project>,
-    ) -> Result<(), anyhow::Error> {
-        let (worktree, relative_path) = self
-            .find_local_worktree(&abs_path, cx)
-            .ok_or_else(|| anyhow!("no worktree found for diagnostics path {abs_path:?}"))?;
-
-        let project_path = ProjectPath {
-            worktree_id: worktree.read(cx).id(),
-            path: relative_path.into(),
-        };
-
-        if let Some(buffer) = self.get_open_buffer(&project_path, cx) {
-            self.update_buffer_diagnostics(&buffer, server_id, version, diagnostics.clone(), cx)?;
-        }
-
-        let updated = worktree.update(cx, |worktree, cx| {
-            worktree
-                .as_local_mut()
-                .ok_or_else(|| anyhow!("not a local worktree"))?
-                .update_diagnostics(server_id, project_path.path.clone(), diagnostics, cx)
-        })?;
-        if updated {
-            cx.emit(Event::DiagnosticsUpdated {
-                language_server_id: server_id,
-                path: project_path,
-            });
-        }
-        Ok(())
-    }
-
-    fn update_buffer_diagnostics(
-        &mut self,
-        buffer: &Model<Buffer>,
-        server_id: LanguageServerId,
-        version: Option<i32>,
-        mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        fn compare_diagnostics(a: &Diagnostic, b: &Diagnostic) -> Ordering {
-            Ordering::Equal
-                .then_with(|| b.is_primary.cmp(&a.is_primary))
-                .then_with(|| a.is_disk_based.cmp(&b.is_disk_based))
-                .then_with(|| a.severity.cmp(&b.severity))
-                .then_with(|| a.message.cmp(&b.message))
-        }
-
-        let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx)?;
-
-        diagnostics.sort_unstable_by(|a, b| {
-            Ordering::Equal
-                .then_with(|| a.range.start.cmp(&b.range.start))
-                .then_with(|| b.range.end.cmp(&a.range.end))
-                .then_with(|| compare_diagnostics(&a.diagnostic, &b.diagnostic))
-        });
-
-        let mut sanitized_diagnostics = Vec::new();
-        let edits_since_save = Patch::new(
-            snapshot
-                .edits_since::<Unclipped<PointUtf16>>(buffer.read(cx).saved_version())
-                .collect(),
-        );
-        for entry in diagnostics {
-            let start;
-            let end;
-            if entry.diagnostic.is_disk_based {
-                // Some diagnostics are based on files on disk instead of buffers'
-                // current contents. Adjust these diagnostics' ranges to reflect
-                // any unsaved edits.
-                start = edits_since_save.old_to_new(entry.range.start);
-                end = edits_since_save.old_to_new(entry.range.end);
-            } else {
-                start = entry.range.start;
-                end = entry.range.end;
-            }
-
-            let mut range = snapshot.clip_point_utf16(start, Bias::Left)
-                ..snapshot.clip_point_utf16(end, Bias::Right);
-
-            // Expand empty ranges by one codepoint
-            if range.start == range.end {
-                // This will be go to the next boundary when being clipped
-                range.end.column += 1;
-                range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Right);
-                if range.start == range.end && range.end.column > 0 {
-                    range.start.column -= 1;
-                    range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Left);
-                }
-            }
-
-            sanitized_diagnostics.push(DiagnosticEntry {
-                range,
-                diagnostic: entry.diagnostic,
-            });
-        }
-        drop(edits_since_save);
-
-        let set = DiagnosticSet::new(sanitized_diagnostics, &snapshot);
-        buffer.update(cx, |buffer, cx| {
-            buffer.update_diagnostics(server_id, set, cx)
-        });
-        Ok(())
-    }
-
-    pub fn reload_buffers(
-        &self,
-        buffers: HashSet<Model<Buffer>>,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        let mut local_buffers = Vec::new();
-        let mut remote_buffers = None;
-        for buffer_handle in buffers {
-            let buffer = buffer_handle.read(cx);
-            if buffer.is_dirty() {
-                if let Some(file) = File::from_dyn(buffer.file()) {
-                    if file.is_local() {
-                        local_buffers.push(buffer_handle);
-                    } else {
-                        remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
-                    }
-                }
-            }
-        }
-
-        let remote_buffers = self.remote_id().zip(remote_buffers);
-        let client = self.client.clone();
-
-        cx.spawn(move |this, mut cx| async move {
-            let mut project_transaction = ProjectTransaction::default();
-
-            if let Some((project_id, remote_buffers)) = remote_buffers {
-                let response = client
-                    .request(proto::ReloadBuffers {
-                        project_id,
-                        buffer_ids: remote_buffers
-                            .iter()
-                            .filter_map(|buffer| {
-                                buffer.update(&mut cx, |buffer, _| buffer.remote_id()).ok()
-                            })
-                            .collect(),
-                    })
-                    .await?
-                    .transaction
-                    .ok_or_else(|| anyhow!("missing transaction"))?;
-                project_transaction = this
-                    .update(&mut cx, |this, cx| {
-                        this.deserialize_project_transaction(response, push_to_history, cx)
-                    })?
-                    .await?;
-            }
-
-            for buffer in local_buffers {
-                let transaction = buffer
-                    .update(&mut cx, |buffer, cx| buffer.reload(cx))?
-                    .await?;
-                buffer.update(&mut cx, |buffer, cx| {
-                    if let Some(transaction) = transaction {
-                        if !push_to_history {
-                            buffer.forget_transaction(transaction.id);
-                        }
-                        project_transaction.0.insert(cx.handle(), transaction);
-                    }
-                })?;
-            }
-
-            Ok(project_transaction)
-        })
-    }
-
-    pub fn format(
-        &mut self,
-        buffers: HashSet<Model<Buffer>>,
-        push_to_history: bool,
-        trigger: FormatTrigger,
-        cx: &mut ModelContext<Project>,
-    ) -> Task<anyhow::Result<ProjectTransaction>> {
-        if self.is_local() {
-            let mut buffers_with_paths_and_servers = buffers
-                .into_iter()
-                .filter_map(|buffer_handle| {
-                    let buffer = buffer_handle.read(cx);
-                    let file = File::from_dyn(buffer.file())?;
-                    let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
-                    let server = self
-                        .primary_language_server_for_buffer(buffer, cx)
-                        .map(|s| s.1.clone());
-                    Some((buffer_handle, buffer_abs_path, server))
-                })
-                .collect::<Vec<_>>();
-
-            cx.spawn(move |project, mut cx| async move {
-                // Do not allow multiple concurrent formatting requests for the
-                // same buffer.
-                project.update(&mut cx, |this, cx| {
-                    buffers_with_paths_and_servers.retain(|(buffer, _, _)| {
-                        this.buffers_being_formatted
-                            .insert(buffer.read(cx).remote_id())
-                    });
-                })?;
-
-                let _cleanup = defer({
-                    let this = project.clone();
-                    let mut cx = cx.clone();
-                    let buffers = &buffers_with_paths_and_servers;
-                    move || {
-                        this.update(&mut cx, |this, cx| {
-                            for (buffer, _, _) in buffers {
-                                this.buffers_being_formatted
-                                    .remove(&buffer.read(cx).remote_id());
-                            }
-                        })
-                        .ok();
-                    }
-                });
-
-                let mut project_transaction = ProjectTransaction::default();
-                for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
-                    let settings = buffer.update(&mut cx, |buffer, cx| {
-                        language_settings(buffer.language(), buffer.file(), cx).clone()
-                    })?;
-
-                    let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
-                    let ensure_final_newline = settings.ensure_final_newline_on_save;
-                    let tab_size = settings.tab_size;
-
-                    // First, format buffer's whitespace according to the settings.
-                    let trailing_whitespace_diff = if remove_trailing_whitespace {
-                        Some(
-                            buffer
-                                .update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
-                                .await,
-                        )
-                    } else {
-                        None
-                    };
-                    let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.start_transaction();
-                        if let Some(diff) = trailing_whitespace_diff {
-                            buffer.apply_diff(diff, cx);
-                        }
-                        if ensure_final_newline {
-                            buffer.ensure_final_newline(cx);
-                        }
-                        buffer.end_transaction(cx)
-                    })?;
-
-                    // Apply language-specific formatting using either a language server
-                    // or external command.
-                    let mut format_operation = None;
-                    match (&settings.formatter, &settings.format_on_save) {
-                        (_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
-
-                        (Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
-                        | (_, FormatOnSave::LanguageServer) => {
-                            if let Some((language_server, buffer_abs_path)) =
-                                language_server.as_ref().zip(buffer_abs_path.as_ref())
-                            {
-                                format_operation = Some(FormatOperation::Lsp(
-                                    Self::format_via_lsp(
-                                        &project,
-                                        &buffer,
-                                        buffer_abs_path,
-                                        &language_server,
-                                        tab_size,
-                                        &mut cx,
-                                    )
-                                    .await
-                                    .context("failed to format via language server")?,
-                                ));
-                            }
-                        }
-
-                        (
-                            Formatter::External { command, arguments },
-                            FormatOnSave::On | FormatOnSave::Off,
-                        )
-                        | (_, FormatOnSave::External { command, arguments }) => {
-                            if let Some(buffer_abs_path) = buffer_abs_path {
-                                format_operation = Self::format_via_external_command(
-                                    buffer,
-                                    buffer_abs_path,
-                                    &command,
-                                    &arguments,
-                                    &mut cx,
-                                )
-                                .await
-                                .context(format!(
-                                    "failed to format via external command {:?}",
-                                    command
-                                ))?
-                                .map(FormatOperation::External);
-                            }
-                        }
-                        (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
-                            if let Some(new_operation) =
-                                prettier_support::format_with_prettier(&project, buffer, &mut cx)
-                                    .await
-                            {
-                                format_operation = Some(new_operation);
-                            } else if let Some((language_server, buffer_abs_path)) =
-                                language_server.as_ref().zip(buffer_abs_path.as_ref())
-                            {
-                                format_operation = Some(FormatOperation::Lsp(
-                                    Self::format_via_lsp(
-                                        &project,
-                                        &buffer,
-                                        buffer_abs_path,
-                                        &language_server,
-                                        tab_size,
-                                        &mut cx,
-                                    )
-                                    .await
-                                    .context("failed to format via language server")?,
-                                ));
-                            }
-                        }
-                        (Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => {
-                            if let Some(new_operation) =
-                                prettier_support::format_with_prettier(&project, buffer, &mut cx)
-                                    .await
-                            {
-                                format_operation = Some(new_operation);
-                            }
-                        }
-                    };
-
-                    buffer.update(&mut cx, |b, cx| {
-                        // If the buffer had its whitespace formatted and was edited while the language-specific
-                        // formatting was being computed, avoid applying the language-specific formatting, because
-                        // it can't be grouped with the whitespace formatting in the undo history.
-                        if let Some(transaction_id) = whitespace_transaction_id {
-                            if b.peek_undo_stack()
-                                .map_or(true, |e| e.transaction_id() != transaction_id)
-                            {
-                                format_operation.take();
-                            }
-                        }
-
-                        // Apply any language-specific formatting, and group the two formatting operations
-                        // in the buffer's undo history.
-                        if let Some(operation) = format_operation {
-                            match operation {
-                                FormatOperation::Lsp(edits) => {
-                                    b.edit(edits, None, cx);
-                                }
-                                FormatOperation::External(diff) => {
-                                    b.apply_diff(diff, cx);
-                                }
-                                FormatOperation::Prettier(diff) => {
-                                    b.apply_diff(diff, cx);
-                                }
-                            }
-
-                            if let Some(transaction_id) = whitespace_transaction_id {
-                                b.group_until_transaction(transaction_id);
-                            }
-                        }
-
-                        if let Some(transaction) = b.finalize_last_transaction().cloned() {
-                            if !push_to_history {
-                                b.forget_transaction(transaction.id);
-                            }
-                            project_transaction.0.insert(buffer.clone(), transaction);
-                        }
-                    })?;
-                }
-
-                Ok(project_transaction)
-            })
-        } else {
-            let remote_id = self.remote_id();
-            let client = self.client.clone();
-            cx.spawn(move |this, mut cx| async move {
-                let mut project_transaction = ProjectTransaction::default();
-                if let Some(project_id) = remote_id {
-                    let response = client
-                        .request(proto::FormatBuffers {
-                            project_id,
-                            trigger: trigger as i32,
-                            buffer_ids: buffers
-                                .iter()
-                                .map(|buffer| {
-                                    buffer.update(&mut cx, |buffer, _| buffer.remote_id())
-                                })
-                                .collect::<Result<_>>()?,
-                        })
-                        .await?
-                        .transaction
-                        .ok_or_else(|| anyhow!("missing transaction"))?;
-                    project_transaction = this
-                        .update(&mut cx, |this, cx| {
-                            this.deserialize_project_transaction(response, push_to_history, cx)
-                        })?
-                        .await?;
-                }
-                Ok(project_transaction)
-            })
-        }
-    }
-
-    async fn format_via_lsp(
-        this: &WeakModel<Self>,
-        buffer: &Model<Buffer>,
-        abs_path: &Path,
-        language_server: &Arc<LanguageServer>,
-        tab_size: NonZeroU32,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Vec<(Range<Anchor>, String)>> {
-        let uri = lsp::Url::from_file_path(abs_path)
-            .map_err(|_| anyhow!("failed to convert abs path to uri"))?;
-        let text_document = lsp::TextDocumentIdentifier::new(uri);
-        let capabilities = &language_server.capabilities();
-
-        let formatting_provider = capabilities.document_formatting_provider.as_ref();
-        let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref();
-
-        let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) {
-            language_server
-                .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
-                    text_document,
-                    options: lsp_command::lsp_formatting_options(tab_size.get()),
-                    work_done_progress_params: Default::default(),
-                })
-                .await?
-        } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) {
-            let buffer_start = lsp::Position::new(0, 0);
-            let buffer_end = buffer.update(cx, |b, _| point_to_lsp(b.max_point_utf16()))?;
-
-            language_server
-                .request::<lsp::request::RangeFormatting>(lsp::DocumentRangeFormattingParams {
-                    text_document,
-                    range: lsp::Range::new(buffer_start, buffer_end),
-                    options: lsp_command::lsp_formatting_options(tab_size.get()),
-                    work_done_progress_params: Default::default(),
-                })
-                .await?
-        } else {
-            None
-        };
-
-        if let Some(lsp_edits) = lsp_edits {
-            this.update(cx, |this, cx| {
-                this.edits_from_lsp(buffer, lsp_edits, language_server.server_id(), None, cx)
-            })?
-            .await
-        } else {
-            Ok(Vec::new())
-        }
-    }
-
-    async fn format_via_external_command(
-        buffer: &Model<Buffer>,
-        buffer_abs_path: &Path,
-        command: &str,
-        arguments: &[String],
-        cx: &mut AsyncAppContext,
-    ) -> Result<Option<Diff>> {
-        let working_dir_path = buffer.update(cx, |buffer, cx| {
-            let file = File::from_dyn(buffer.file())?;
-            let worktree = file.worktree.read(cx).as_local()?;
-            let mut worktree_path = worktree.abs_path().to_path_buf();
-            if worktree.root_entry()?.is_file() {
-                worktree_path.pop();
-            }
-            Some(worktree_path)
-        })?;
-
-        if let Some(working_dir_path) = working_dir_path {
-            let mut child =
-                smol::process::Command::new(command)
-                    .args(arguments.iter().map(|arg| {
-                        arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
-                    }))
-                    .current_dir(&working_dir_path)
-                    .stdin(smol::process::Stdio::piped())
-                    .stdout(smol::process::Stdio::piped())
-                    .stderr(smol::process::Stdio::piped())
-                    .spawn()?;
-            let stdin = child
-                .stdin
-                .as_mut()
-                .ok_or_else(|| anyhow!("failed to acquire stdin"))?;
-            let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
-            for chunk in text.chunks() {
-                stdin.write_all(chunk.as_bytes()).await?;
-            }
-            stdin.flush().await?;
-
-            let output = child.output().await?;
-            if !output.status.success() {
-                return Err(anyhow!(
-                    "command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
-                    output.status.code(),
-                    String::from_utf8_lossy(&output.stdout),
-                    String::from_utf8_lossy(&output.stderr),
-                ));
-            }
-
-            let stdout = String::from_utf8(output.stdout)?;
-            Ok(Some(
-                buffer
-                    .update(cx, |buffer, cx| buffer.diff(stdout, cx))?
-                    .await,
-            ))
-        } else {
-            Ok(None)
-        }
-    }
-
-    pub fn definition<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<LocationLink>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetDefinition { position },
-            cx,
-        )
-    }
-
-    pub fn type_definition<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<LocationLink>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetTypeDefinition { position },
-            cx,
-        )
-    }
-
-    pub fn references<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Location>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetReferences { position },
-            cx,
-        )
-    }
-
-    pub fn document_highlights<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<DocumentHighlight>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetDocumentHighlights { position },
-            cx,
-        )
-    }
-
-    pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
-        if self.is_local() {
-            let mut requests = Vec::new();
-            for ((worktree_id, _), server_id) in self.language_server_ids.iter() {
-                let worktree_id = *worktree_id;
-                let worktree_handle = self.worktree_for_id(worktree_id, cx);
-                let worktree = match worktree_handle.and_then(|tree| tree.read(cx).as_local()) {
-                    Some(worktree) => worktree,
-                    None => continue,
-                };
-                let worktree_abs_path = worktree.abs_path().clone();
-
-                let (adapter, language, server) = match self.language_servers.get(server_id) {
-                    Some(LanguageServerState::Running {
-                        adapter,
-                        language,
-                        server,
-                        ..
-                    }) => (adapter.clone(), language.clone(), server),
-
-                    _ => continue,
-                };
-
-                requests.push(
-                    server
-                        .request::<lsp::request::WorkspaceSymbolRequest>(
-                            lsp::WorkspaceSymbolParams {
-                                query: query.to_string(),
-                                ..Default::default()
-                            },
-                        )
-                        .log_err()
-                        .map(move |response| {
-                            let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response {
-                                lsp::WorkspaceSymbolResponse::Flat(flat_responses) => {
-                                    flat_responses.into_iter().map(|lsp_symbol| {
-                                        (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location)
-                                    }).collect::<Vec<_>>()
-                                }
-                                lsp::WorkspaceSymbolResponse::Nested(nested_responses) => {
-                                    nested_responses.into_iter().filter_map(|lsp_symbol| {
-                                        let location = match lsp_symbol.location {
-                                            OneOf::Left(location) => location,
-                                            OneOf::Right(_) => {
-                                                error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport");
-                                                return None
-                                            }
-                                        };
-                                        Some((lsp_symbol.name, lsp_symbol.kind, location))
-                                    }).collect::<Vec<_>>()
-                                }
-                            }).unwrap_or_default();
-
-                            (
-                                adapter,
-                                language,
-                                worktree_id,
-                                worktree_abs_path,
-                                lsp_symbols,
-                            )
-                        }),
-                );
-            }
-
-            cx.spawn(move |this, mut cx| async move {
-                let responses = futures::future::join_all(requests).await;
-                let this = match this.upgrade() {
-                    Some(this) => this,
-                    None => return Ok(Vec::new()),
-                };
-
-                let symbols = this.update(&mut cx, |this, cx| {
-                    let mut symbols = Vec::new();
-                    for (
-                        adapter,
-                        adapter_language,
-                        source_worktree_id,
-                        worktree_abs_path,
-                        lsp_symbols,
-                    ) in responses
-                    {
-                        symbols.extend(lsp_symbols.into_iter().filter_map(
-                            |(symbol_name, symbol_kind, symbol_location)| {
-                                let abs_path = symbol_location.uri.to_file_path().ok()?;
-                                let mut worktree_id = source_worktree_id;
-                                let path;
-                                if let Some((worktree, rel_path)) =
-                                    this.find_local_worktree(&abs_path, cx)
-                                {
-                                    worktree_id = worktree.read(cx).id();
-                                    path = rel_path;
-                                } else {
-                                    path = relativize_path(&worktree_abs_path, &abs_path);
-                                }
-
-                                let project_path = ProjectPath {
-                                    worktree_id,
-                                    path: path.into(),
-                                };
-                                let signature = this.symbol_signature(&project_path);
-                                let adapter_language = adapter_language.clone();
-                                let language = this
-                                    .languages
-                                    .language_for_file(&project_path.path, None)
-                                    .unwrap_or_else(move |_| adapter_language);
-                                let language_server_name = adapter.name.clone();
-                                Some(async move {
-                                    let language = language.await;
-                                    let label =
-                                        language.label_for_symbol(&symbol_name, symbol_kind).await;
-
-                                    Symbol {
-                                        language_server_name,
-                                        source_worktree_id,
-                                        path: project_path,
-                                        label: label.unwrap_or_else(|| {
-                                            CodeLabel::plain(symbol_name.clone(), None)
-                                        }),
-                                        kind: symbol_kind,
-                                        name: symbol_name,
-                                        range: range_from_lsp(symbol_location.range),
-                                        signature,
-                                    }
-                                })
-                            },
-                        ));
-                    }
-
-                    symbols
-                })?;
-
-                Ok(futures::future::join_all(symbols).await)
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let request = self.client.request(proto::GetProjectSymbols {
-                project_id,
-                query: query.to_string(),
-            });
-            cx.spawn(move |this, mut cx| async move {
-                let response = request.await?;
-                let mut symbols = Vec::new();
-                if let Some(this) = this.upgrade() {
-                    let new_symbols = this.update(&mut cx, |this, _| {
-                        response
-                            .symbols
-                            .into_iter()
-                            .map(|symbol| this.deserialize_symbol(symbol))
-                            .collect::<Vec<_>>()
-                    })?;
-                    symbols = futures::future::join_all(new_symbols)
-                        .await
-                        .into_iter()
-                        .filter_map(|symbol| symbol.log_err())
-                        .collect::<Vec<_>>();
-                }
-                Ok(symbols)
-            })
-        } else {
-            Task::ready(Ok(Default::default()))
-        }
-    }
-
-    pub fn open_buffer_for_symbol(
-        &mut self,
-        symbol: &Symbol,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        if self.is_local() {
-            let language_server_id = if let Some(id) = self.language_server_ids.get(&(
-                symbol.source_worktree_id,
-                symbol.language_server_name.clone(),
-            )) {
-                *id
-            } else {
-                return Task::ready(Err(anyhow!(
-                    "language server for worktree and language not found"
-                )));
-            };
-
-            let worktree_abs_path = if let Some(worktree_abs_path) = self
-                .worktree_for_id(symbol.path.worktree_id, cx)
-                .and_then(|worktree| worktree.read(cx).as_local())
-                .map(|local_worktree| local_worktree.abs_path())
-            {
-                worktree_abs_path
-            } else {
-                return Task::ready(Err(anyhow!("worktree not found for symbol")));
-            };
-            let symbol_abs_path = worktree_abs_path.join(&symbol.path.path);
-            let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) {
-                uri
-            } else {
-                return Task::ready(Err(anyhow!("invalid symbol path")));
-            };
-
-            self.open_local_buffer_via_lsp(
-                symbol_uri,
-                language_server_id,
-                symbol.language_server_name.clone(),
-                cx,
-            )
-        } else if let Some(project_id) = self.remote_id() {
-            let request = self.client.request(proto::OpenBufferForSymbol {
-                project_id,
-                symbol: Some(serialize_symbol(symbol)),
-            });
-            cx.spawn(move |this, mut cx| async move {
-                let response = request.await?;
-                this.update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(response.buffer_id, cx)
-                })?
-                .await
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    pub fn hover<T: ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Hover>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            GetHover { position },
-            cx,
-        )
-    }
-
-    pub fn completions<T: ToOffset + ToPointUtf16>(
-        &self,
-        buffer: &Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<Completion>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        if self.is_local() {
-            let snapshot = buffer.read(cx).snapshot();
-            let offset = position.to_offset(&snapshot);
-            let scope = snapshot.language_scope_at(offset);
-
-            let server_ids: Vec<_> = self
-                .language_servers_for_buffer(buffer.read(cx), cx)
-                .filter(|(_, server)| server.capabilities().completion_provider.is_some())
-                .filter(|(adapter, _)| {
-                    scope
-                        .as_ref()
-                        .map(|scope| scope.language_allowed(&adapter.name))
-                        .unwrap_or(true)
-                })
-                .map(|(_, server)| server.server_id())
-                .collect();
-
-            let buffer = buffer.clone();
-            cx.spawn(move |this, mut cx| async move {
-                let mut tasks = Vec::with_capacity(server_ids.len());
-                this.update(&mut cx, |this, cx| {
-                    for server_id in server_ids {
-                        tasks.push(this.request_lsp(
-                            buffer.clone(),
-                            LanguageServerToQuery::Other(server_id),
-                            GetCompletions { position },
-                            cx,
-                        ));
-                    }
-                })?;
-
-                let mut completions = Vec::new();
-                for task in tasks {
-                    if let Ok(new_completions) = task.await {
-                        completions.extend_from_slice(&new_completions);
-                    }
-                }
-
-                Ok(completions)
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            self.send_lsp_proto_request(buffer.clone(), project_id, GetCompletions { position }, cx)
-        } else {
-            Task::ready(Ok(Default::default()))
-        }
-    }
-
-    pub fn apply_additional_edits_for_completion(
-        &self,
-        buffer_handle: Model<Buffer>,
-        completion: Completion,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Transaction>>> {
-        let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
-
-        if self.is_local() {
-            let server_id = completion.server_id;
-            let lang_server = match self.language_server_for_buffer(buffer, server_id, cx) {
-                Some((_, server)) => server.clone(),
-                _ => return Task::ready(Ok(Default::default())),
-            };
-
-            cx.spawn(move |this, mut cx| async move {
-                let can_resolve = lang_server
-                    .capabilities()
-                    .completion_provider
-                    .as_ref()
-                    .and_then(|options| options.resolve_provider)
-                    .unwrap_or(false);
-                let additional_text_edits = if can_resolve {
-                    lang_server
-                        .request::<lsp::request::ResolveCompletionItem>(completion.lsp_completion)
-                        .await?
-                        .additional_text_edits
-                } else {
-                    completion.lsp_completion.additional_text_edits
-                };
-                if let Some(edits) = additional_text_edits {
-                    let edits = this
-                        .update(&mut cx, |this, cx| {
-                            this.edits_from_lsp(
-                                &buffer_handle,
-                                edits,
-                                lang_server.server_id(),
-                                None,
-                                cx,
-                            )
-                        })?
-                        .await?;
-
-                    buffer_handle.update(&mut cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.start_transaction();
-
-                        for (range, text) in edits {
-                            let primary = &completion.old_range;
-                            let start_within = primary.start.cmp(&range.start, buffer).is_le()
-                                && primary.end.cmp(&range.start, buffer).is_ge();
-                            let end_within = range.start.cmp(&primary.end, buffer).is_le()
-                                && range.end.cmp(&primary.end, buffer).is_ge();
-
-                            //Skip additional edits which overlap with the primary completion edit
-                            //https://github.com/zed-industries/zed/pull/1871
-                            if !start_within && !end_within {
-                                buffer.edit([(range, text)], None, cx);
-                            }
-                        }
-
-                        let transaction = if buffer.end_transaction(cx).is_some() {
-                            let transaction = buffer.finalize_last_transaction().unwrap().clone();
-                            if !push_to_history {
-                                buffer.forget_transaction(transaction.id);
-                            }
-                            Some(transaction)
-                        } else {
-                            None
-                        };
-                        Ok(transaction)
-                    })?
-                } else {
-                    Ok(None)
-                }
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            cx.spawn(move |_, mut cx| async move {
-                let response = client
-                    .request(proto::ApplyCompletionAdditionalEdits {
-                        project_id,
-                        buffer_id,
-                        completion: Some(language::proto::serialize_completion(&completion)),
-                    })
-                    .await?;
-
-                if let Some(transaction) = response.transaction {
-                    let transaction = language::proto::deserialize_transaction(transaction)?;
-                    buffer_handle
-                        .update(&mut cx, |buffer, _| {
-                            buffer.wait_for_edits(transaction.edit_ids.iter().copied())
-                        })?
-                        .await?;
-                    if push_to_history {
-                        buffer_handle.update(&mut cx, |buffer, _| {
-                            buffer.push_transaction(transaction.clone(), Instant::now());
-                        })?;
-                    }
-                    Ok(Some(transaction))
-                } else {
-                    Ok(None)
-                }
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    pub fn code_actions<T: Clone + ToOffset>(
-        &self,
-        buffer_handle: &Model<Buffer>,
-        range: Range<T>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<CodeAction>>> {
-        let buffer = buffer_handle.read(cx);
-        let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
-        self.request_lsp(
-            buffer_handle.clone(),
-            LanguageServerToQuery::Primary,
-            GetCodeActions { range },
-            cx,
-        )
-    }
-
-    pub fn apply_code_action(
-        &self,
-        buffer_handle: Model<Buffer>,
-        mut action: CodeAction,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        if self.is_local() {
-            let buffer = buffer_handle.read(cx);
-            let (lsp_adapter, lang_server) = if let Some((adapter, server)) =
-                self.language_server_for_buffer(buffer, action.server_id, cx)
-            {
-                (adapter.clone(), server.clone())
-            } else {
-                return Task::ready(Ok(Default::default()));
-            };
-            let range = action.range.to_point_utf16(buffer);
-
-            cx.spawn(move |this, mut cx| async move {
-                if let Some(lsp_range) = action
-                    .lsp_action
-                    .data
-                    .as_mut()
-                    .and_then(|d| d.get_mut("codeActionParams"))
-                    .and_then(|d| d.get_mut("range"))
-                {
-                    *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
-                    action.lsp_action = lang_server
-                        .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
-                        .await?;
-                } else {
-                    let actions = this
-                        .update(&mut cx, |this, cx| {
-                            this.code_actions(&buffer_handle, action.range, cx)
-                        })?
-                        .await?;
-                    action.lsp_action = actions
-                        .into_iter()
-                        .find(|a| a.lsp_action.title == action.lsp_action.title)
-                        .ok_or_else(|| anyhow!("code action is outdated"))?
-                        .lsp_action;
-                }
-
-                if let Some(edit) = action.lsp_action.edit {
-                    if edit.changes.is_some() || edit.document_changes.is_some() {
-                        return Self::deserialize_workspace_edit(
-                            this.upgrade().ok_or_else(|| anyhow!("no app present"))?,
-                            edit,
-                            push_to_history,
-                            lsp_adapter.clone(),
-                            lang_server.clone(),
-                            &mut cx,
-                        )
-                        .await;
-                    }
-                }
-
-                if let Some(command) = action.lsp_action.command {
-                    this.update(&mut cx, |this, _| {
-                        this.last_workspace_edits_by_language_server
-                            .remove(&lang_server.server_id());
-                    })?;
-
-                    let result = lang_server
-                        .request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
-                            command: command.command,
-                            arguments: command.arguments.unwrap_or_default(),
-                            ..Default::default()
-                        })
-                        .await;
-
-                    if let Err(err) = result {
-                        // TODO: LSP ERROR
-                        return Err(err);
-                    }
-
-                    return Ok(this.update(&mut cx, |this, _| {
-                        this.last_workspace_edits_by_language_server
-                            .remove(&lang_server.server_id())
-                            .unwrap_or_default()
-                    })?);
-                }
-
-                Ok(ProjectTransaction::default())
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::ApplyCodeAction {
-                project_id,
-                buffer_id: buffer_handle.read(cx).remote_id(),
-                action: Some(language::proto::serialize_code_action(&action)),
-            };
-            cx.spawn(move |this, mut cx| async move {
-                let response = client
-                    .request(request)
-                    .await?
-                    .transaction
-                    .ok_or_else(|| anyhow!("missing transaction"))?;
-                this.update(&mut cx, |this, cx| {
-                    this.deserialize_project_transaction(response, push_to_history, cx)
-                })?
-                .await
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    fn apply_on_type_formatting(
-        &self,
-        buffer: Model<Buffer>,
-        position: Anchor,
-        trigger: String,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Transaction>>> {
-        if self.is_local() {
-            cx.spawn(move |this, mut cx| async move {
-                // Do not allow multiple concurrent formatting requests for the
-                // same buffer.
-                this.update(&mut cx, |this, cx| {
-                    this.buffers_being_formatted
-                        .insert(buffer.read(cx).remote_id())
-                })?;
-
-                let _cleanup = defer({
-                    let this = this.clone();
-                    let mut cx = cx.clone();
-                    let closure_buffer = buffer.clone();
-                    move || {
-                        this.update(&mut cx, |this, cx| {
-                            this.buffers_being_formatted
-                                .remove(&closure_buffer.read(cx).remote_id());
-                        })
-                        .ok();
-                    }
-                });
-
-                buffer
-                    .update(&mut cx, |buffer, _| {
-                        buffer.wait_for_edits(Some(position.timestamp))
-                    })?
-                    .await?;
-                this.update(&mut cx, |this, cx| {
-                    let position = position.to_point_utf16(buffer.read(cx));
-                    this.on_type_format(buffer, position, trigger, false, cx)
-                })?
-                .await
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::OnTypeFormatting {
-                project_id,
-                buffer_id: buffer.read(cx).remote_id(),
-                position: Some(serialize_anchor(&position)),
-                trigger,
-                version: serialize_version(&buffer.read(cx).version()),
-            };
-            cx.spawn(move |_, _| async move {
-                client
-                    .request(request)
-                    .await?
-                    .transaction
-                    .map(language::proto::deserialize_transaction)
-                    .transpose()
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    async fn deserialize_edits(
-        this: Model<Self>,
-        buffer_to_edit: Model<Buffer>,
-        edits: Vec<lsp::TextEdit>,
-        push_to_history: bool,
-        _: Arc<CachedLspAdapter>,
-        language_server: Arc<LanguageServer>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Option<Transaction>> {
-        let edits = this
-            .update(cx, |this, cx| {
-                this.edits_from_lsp(
-                    &buffer_to_edit,
-                    edits,
-                    language_server.server_id(),
-                    None,
-                    cx,
-                )
-            })?
-            .await?;
-
-        let transaction = buffer_to_edit.update(cx, |buffer, cx| {
-            buffer.finalize_last_transaction();
-            buffer.start_transaction();
-            for (range, text) in edits {
-                buffer.edit([(range, text)], None, cx);
-            }
-
-            if buffer.end_transaction(cx).is_some() {
-                let transaction = buffer.finalize_last_transaction().unwrap().clone();
-                if !push_to_history {
-                    buffer.forget_transaction(transaction.id);
-                }
-                Some(transaction)
-            } else {
-                None
-            }
-        })?;
-
-        Ok(transaction)
-    }
-
-    async fn deserialize_workspace_edit(
-        this: Model<Self>,
-        edit: lsp::WorkspaceEdit,
-        push_to_history: bool,
-        lsp_adapter: Arc<CachedLspAdapter>,
-        language_server: Arc<LanguageServer>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<ProjectTransaction> {
-        let fs = this.update(cx, |this, _| this.fs.clone())?;
-        let mut operations = Vec::new();
-        if let Some(document_changes) = edit.document_changes {
-            match document_changes {
-                lsp::DocumentChanges::Edits(edits) => {
-                    operations.extend(edits.into_iter().map(lsp::DocumentChangeOperation::Edit))
-                }
-                lsp::DocumentChanges::Operations(ops) => operations = ops,
-            }
-        } else if let Some(changes) = edit.changes {
-            operations.extend(changes.into_iter().map(|(uri, edits)| {
-                lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
-                    text_document: lsp::OptionalVersionedTextDocumentIdentifier {
-                        uri,
-                        version: None,
-                    },
-                    edits: edits.into_iter().map(OneOf::Left).collect(),
-                })
-            }));
-        }
-
-        let mut project_transaction = ProjectTransaction::default();
-        for operation in operations {
-            match operation {
-                lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => {
-                    let abs_path = op
-                        .uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-
-                    if let Some(parent_path) = abs_path.parent() {
-                        fs.create_dir(parent_path).await?;
-                    }
-                    if abs_path.ends_with("/") {
-                        fs.create_dir(&abs_path).await?;
-                    } else {
-                        fs.create_file(
-                            &abs_path,
-                            op.options
-                                .map(|options| fs::CreateOptions {
-                                    overwrite: options.overwrite.unwrap_or(false),
-                                    ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
-                                })
-                                .unwrap_or_default(),
-                        )
-                        .await?;
-                    }
-                }
-
-                lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => {
-                    let source_abs_path = op
-                        .old_uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    let target_abs_path = op
-                        .new_uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    fs.rename(
-                        &source_abs_path,
-                        &target_abs_path,
-                        op.options
-                            .map(|options| fs::RenameOptions {
-                                overwrite: options.overwrite.unwrap_or(false),
-                                ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
-                            })
-                            .unwrap_or_default(),
-                    )
-                    .await?;
-                }
-
-                lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => {
-                    let abs_path = op
-                        .uri
-                        .to_file_path()
-                        .map_err(|_| anyhow!("can't convert URI to path"))?;
-                    let options = op
-                        .options
-                        .map(|options| fs::RemoveOptions {
-                            recursive: options.recursive.unwrap_or(false),
-                            ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
-                        })
-                        .unwrap_or_default();
-                    if abs_path.ends_with("/") {
-                        fs.remove_dir(&abs_path, options).await?;
-                    } else {
-                        fs.remove_file(&abs_path, options).await?;
-                    }
-                }
-
-                lsp::DocumentChangeOperation::Edit(op) => {
-                    let buffer_to_edit = this
-                        .update(cx, |this, cx| {
-                            this.open_local_buffer_via_lsp(
-                                op.text_document.uri,
-                                language_server.server_id(),
-                                lsp_adapter.name.clone(),
-                                cx,
-                            )
-                        })?
-                        .await?;
-
-                    let edits = this
-                        .update(cx, |this, cx| {
-                            let edits = op.edits.into_iter().map(|edit| match edit {
-                                OneOf::Left(edit) => edit,
-                                OneOf::Right(edit) => edit.text_edit,
-                            });
-                            this.edits_from_lsp(
-                                &buffer_to_edit,
-                                edits,
-                                language_server.server_id(),
-                                op.text_document.version,
-                                cx,
-                            )
-                        })?
-                        .await?;
-
-                    let transaction = buffer_to_edit.update(cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.start_transaction();
-                        for (range, text) in edits {
-                            buffer.edit([(range, text)], None, cx);
-                        }
-                        let transaction = if buffer.end_transaction(cx).is_some() {
-                            let transaction = buffer.finalize_last_transaction().unwrap().clone();
-                            if !push_to_history {
-                                buffer.forget_transaction(transaction.id);
-                            }
-                            Some(transaction)
-                        } else {
-                            None
-                        };
-
-                        transaction
-                    })?;
-                    if let Some(transaction) = transaction {
-                        project_transaction.0.insert(buffer_to_edit, transaction);
-                    }
-                }
-            }
-        }
-
-        Ok(project_transaction)
-    }
-
-    pub fn prepare_rename<T: ToPointUtf16>(
-        &self,
-        buffer: Model<Buffer>,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Range<Anchor>>>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer,
-            LanguageServerToQuery::Primary,
-            PrepareRename { position },
-            cx,
-        )
-    }
-
-    pub fn perform_rename<T: ToPointUtf16>(
-        &self,
-        buffer: Model<Buffer>,
-        position: T,
-        new_name: String,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        let position = position.to_point_utf16(buffer.read(cx));
-        self.request_lsp(
-            buffer,
-            LanguageServerToQuery::Primary,
-            PerformRename {
-                position,
-                new_name,
-                push_to_history,
-            },
-            cx,
-        )
-    }
-
-    pub fn on_type_format<T: ToPointUtf16>(
-        &self,
-        buffer: Model<Buffer>,
-        position: T,
-        trigger: String,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Option<Transaction>>> {
-        let (position, tab_size) = buffer.update(cx, |buffer, cx| {
-            let position = position.to_point_utf16(buffer);
-            (
-                position,
-                language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx)
-                    .tab_size,
-            )
-        });
-        self.request_lsp(
-            buffer.clone(),
-            LanguageServerToQuery::Primary,
-            OnTypeFormatting {
-                position,
-                trigger,
-                options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
-                push_to_history,
-            },
-            cx,
-        )
-    }
-
-    pub fn inlay_hints<T: ToOffset>(
-        &self,
-        buffer_handle: Model<Buffer>,
-        range: Range<T>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<anyhow::Result<Vec<InlayHint>>> {
-        let buffer = buffer_handle.read(cx);
-        let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
-        let range_start = range.start;
-        let range_end = range.end;
-        let buffer_id = buffer.remote_id();
-        let buffer_version = buffer.version().clone();
-        let lsp_request = InlayHints { range };
-
-        if self.is_local() {
-            let lsp_request_task = self.request_lsp(
-                buffer_handle.clone(),
-                LanguageServerToQuery::Primary,
-                lsp_request,
-                cx,
-            );
-            cx.spawn(move |_, mut cx| async move {
-                buffer_handle
-                    .update(&mut cx, |buffer, _| {
-                        buffer.wait_for_edits(vec![range_start.timestamp, range_end.timestamp])
-                    })?
-                    .await
-                    .context("waiting for inlay hint request range edits")?;
-                lsp_request_task.await.context("inlay hints LSP request")
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::InlayHints {
-                project_id,
-                buffer_id,
-                start: Some(serialize_anchor(&range_start)),
-                end: Some(serialize_anchor(&range_end)),
-                version: serialize_version(&buffer_version),
-            };
-            cx.spawn(move |project, cx| async move {
-                let response = client
-                    .request(request)
-                    .await
-                    .context("inlay hints proto request")?;
-                let hints_request_result = LspCommand::response_from_proto(
-                    lsp_request,
-                    response,
-                    project.upgrade().ok_or_else(|| anyhow!("No project"))?,
-                    buffer_handle.clone(),
-                    cx,
-                )
-                .await;
-
-                hints_request_result.context("inlay hints proto response conversion")
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    pub fn resolve_inlay_hint(
-        &self,
-        hint: InlayHint,
-        buffer_handle: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<anyhow::Result<InlayHint>> {
-        if self.is_local() {
-            let buffer = buffer_handle.read(cx);
-            let (_, lang_server) = if let Some((adapter, server)) =
-                self.language_server_for_buffer(buffer, server_id, cx)
-            {
-                (adapter.clone(), server.clone())
-            } else {
-                return Task::ready(Ok(hint));
-            };
-            if !InlayHints::can_resolve_inlays(lang_server.capabilities()) {
-                return Task::ready(Ok(hint));
-            }
-
-            let buffer_snapshot = buffer.snapshot();
-            cx.spawn(move |_, mut cx| async move {
-                let resolve_task = lang_server.request::<lsp::request::InlayHintResolveRequest>(
-                    InlayHints::project_to_lsp_hint(hint, &buffer_snapshot),
-                );
-                let resolved_hint = resolve_task
-                    .await
-                    .context("inlay hint resolve LSP request")?;
-                let resolved_hint = InlayHints::lsp_to_project_hint(
-                    resolved_hint,
-                    &buffer_handle,
-                    server_id,
-                    ResolveState::Resolved,
-                    false,
-                    &mut cx,
-                )
-                .await?;
-                Ok(resolved_hint)
-            })
-        } else if let Some(project_id) = self.remote_id() {
-            let client = self.client.clone();
-            let request = proto::ResolveInlayHint {
-                project_id,
-                buffer_id: buffer_handle.read(cx).remote_id(),
-                language_server_id: server_id.0 as u64,
-                hint: Some(InlayHints::project_to_proto_hint(hint.clone())),
-            };
-            cx.spawn(move |_, _| async move {
-                let response = client
-                    .request(request)
-                    .await
-                    .context("inlay hints proto request")?;
-                match response.hint {
-                    Some(resolved_hint) => InlayHints::proto_to_project_hint(resolved_hint)
-                        .context("inlay hints proto resolve response conversion"),
-                    None => Ok(hint),
-                }
-            })
-        } else {
-            Task::ready(Err(anyhow!("project does not have a remote id")))
-        }
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn search(
-        &self,
-        query: SearchQuery,
-        cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
-        if self.is_local() {
-            self.search_local(query, cx)
-        } else if let Some(project_id) = self.remote_id() {
-            let (tx, rx) = smol::channel::unbounded();
-            let request = self.client.request(query.to_proto(project_id));
-            cx.spawn(move |this, mut cx| async move {
-                let response = request.await?;
-                let mut result = HashMap::default();
-                for location in response.locations {
-                    let target_buffer = this
-                        .update(&mut cx, |this, cx| {
-                            this.wait_for_remote_buffer(location.buffer_id, cx)
-                        })?
-                        .await?;
-                    let start = location
-                        .start
-                        .and_then(deserialize_anchor)
-                        .ok_or_else(|| anyhow!("missing target start"))?;
-                    let end = location
-                        .end
-                        .and_then(deserialize_anchor)
-                        .ok_or_else(|| anyhow!("missing target end"))?;
-                    result
-                        .entry(target_buffer)
-                        .or_insert(Vec::new())
-                        .push(start..end)
-                }
-                for (buffer, ranges) in result {
-                    let _ = tx.send((buffer, ranges)).await;
-                }
-                Result::<(), anyhow::Error>::Ok(())
-            })
-            .detach_and_log_err(cx);
-            rx
-        } else {
-            unimplemented!();
-        }
-    }
-
-    pub fn search_local(
-        &self,
-        query: SearchQuery,
-        cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
-        // Local search is split into several phases.
-        // TL;DR is that we do 2 passes; initial pass to pick files which contain at least one match
-        // and the second phase that finds positions of all the matches found in the candidate files.
-        // The Receiver obtained from this function returns matches sorted by buffer path. Files without a buffer path are reported first.
-        //
-        // It gets a bit hairy though, because we must account for files that do not have a persistent representation
-        // on FS. Namely, if you have an untitled buffer or unsaved changes in a buffer, we want to scan that too.
-        //
-        // 1. We initialize a queue of match candidates and feed all opened buffers into it (== unsaved files / untitled buffers).
-        //    Then, we go through a worktree and check for files that do match a predicate. If the file had an opened version, we skip the scan
-        //    of FS version for that file altogether - after all, what we have in memory is more up-to-date than what's in FS.
-        // 2. At this point, we have a list of all potentially matching buffers/files.
-        //    We sort that list by buffer path - this list is retained for later use.
-        //    We ensure that all buffers are now opened and available in project.
-        // 3. We run a scan over all the candidate buffers on multiple background threads.
-        //    We cannot assume that there will even be a match - while at least one match
-        //    is guaranteed for files obtained from FS, the buffers we got from memory (unsaved files/unnamed buffers) might not have a match at all.
-        //    There is also an auxilliary background thread responsible for result gathering.
-        //    This is where the sorted list of buffers comes into play to maintain sorted order; Whenever this background thread receives a notification (buffer has/doesn't have matches),
-        //    it keeps it around. It reports matches in sorted order, though it accepts them in unsorted order as well.
-        //    As soon as the match info on next position in sorted order becomes available, it reports it (if it's a match) or skips to the next
-        //    entry - which might already be available thanks to out-of-order processing.
-        //
-        // We could also report matches fully out-of-order, without maintaining a sorted list of matching paths.
-        // This however would mean that project search (that is the main user of this function) would have to do the sorting itself, on the go.
-        // This isn't as straightforward as running an insertion sort sadly, and would also mean that it would have to care about maintaining match index
-        // in face of constantly updating list of sorted matches.
-        // Meanwhile, this implementation offers index stability, since the matches are already reported in a sorted order.
-        let snapshots = self
-            .visible_worktrees(cx)
-            .filter_map(|tree| {
-                let tree = tree.read(cx).as_local()?;
-                Some(tree.snapshot())
-            })
-            .collect::<Vec<_>>();
-
-        let background = cx.background_executor().clone();
-        let path_count: usize = snapshots
-            .iter()
-            .map(|s| {
-                if query.include_ignored() {
-                    s.file_count()
-                } else {
-                    s.visible_file_count()
-                }
-            })
-            .sum();
-        if path_count == 0 {
-            let (_, rx) = smol::channel::bounded(1024);
-            return rx;
-        }
-        let workers = background.num_cpus().min(path_count);
-        let (matching_paths_tx, matching_paths_rx) = smol::channel::bounded(1024);
-        let mut unnamed_files = vec![];
-        let opened_buffers = self
-            .opened_buffers
-            .iter()
-            .filter_map(|(_, b)| {
-                let buffer = b.upgrade()?;
-                let (is_ignored, snapshot) = buffer.update(cx, |buffer, cx| {
-                    let is_ignored = buffer
-                        .project_path(cx)
-                        .and_then(|path| self.entry_for_path(&path, cx))
-                        .map_or(false, |entry| entry.is_ignored);
-                    (is_ignored, buffer.snapshot())
-                });
-                if is_ignored && !query.include_ignored() {
-                    return None;
-                } else if let Some(path) = snapshot.file().map(|file| file.path()) {
-                    Some((path.clone(), (buffer, snapshot)))
-                } else {
-                    unnamed_files.push(buffer);
-                    None
-                }
-            })
-            .collect();
-        cx.background_executor()
-            .spawn(Self::background_search(
-                unnamed_files,
-                opened_buffers,
-                cx.background_executor().clone(),
-                self.fs.clone(),
-                workers,
-                query.clone(),
-                path_count,
-                snapshots,
-                matching_paths_tx,
-            ))
-            .detach();
-
-        let (buffers, buffers_rx) = Self::sort_candidates_and_open_buffers(matching_paths_rx, cx);
-        let background = cx.background_executor().clone();
-        let (result_tx, result_rx) = smol::channel::bounded(1024);
-        cx.background_executor()
-            .spawn(async move {
-                let Ok(buffers) = buffers.await else {
-                    return;
-                };
-
-                let buffers_len = buffers.len();
-                if buffers_len == 0 {
-                    return;
-                }
-                let query = &query;
-                let (finished_tx, mut finished_rx) = smol::channel::unbounded();
-                background
-                    .scoped(|scope| {
-                        #[derive(Clone)]
-                        struct FinishedStatus {
-                            entry: Option<(Model<Buffer>, Vec<Range<Anchor>>)>,
-                            buffer_index: SearchMatchCandidateIndex,
-                        }
-
-                        for _ in 0..workers {
-                            let finished_tx = finished_tx.clone();
-                            let mut buffers_rx = buffers_rx.clone();
-                            scope.spawn(async move {
-                                while let Some((entry, buffer_index)) = buffers_rx.next().await {
-                                    let buffer_matches = if let Some((_, snapshot)) = entry.as_ref()
-                                    {
-                                        if query.file_matches(
-                                            snapshot.file().map(|file| file.path().as_ref()),
-                                        ) {
-                                            query
-                                                .search(&snapshot, None)
-                                                .await
-                                                .iter()
-                                                .map(|range| {
-                                                    snapshot.anchor_before(range.start)
-                                                        ..snapshot.anchor_after(range.end)
-                                                })
-                                                .collect()
-                                        } else {
-                                            Vec::new()
-                                        }
-                                    } else {
-                                        Vec::new()
-                                    };
-
-                                    let status = if !buffer_matches.is_empty() {
-                                        let entry = if let Some((buffer, _)) = entry.as_ref() {
-                                            Some((buffer.clone(), buffer_matches))
-                                        } else {
-                                            None
-                                        };
-                                        FinishedStatus {
-                                            entry,
-                                            buffer_index,
-                                        }
-                                    } else {
-                                        FinishedStatus {
-                                            entry: None,
-                                            buffer_index,
-                                        }
-                                    };
-                                    if finished_tx.send(status).await.is_err() {
-                                        break;
-                                    }
-                                }
-                            });
-                        }
-                        // Report sorted matches
-                        scope.spawn(async move {
-                            let mut current_index = 0;
-                            let mut scratch = vec![None; buffers_len];
-                            while let Some(status) = finished_rx.next().await {
-                                debug_assert!(
-                                    scratch[status.buffer_index].is_none(),
-                                    "Got match status of position {} twice",
-                                    status.buffer_index
-                                );
-                                let index = status.buffer_index;
-                                scratch[index] = Some(status);
-                                while current_index < buffers_len {
-                                    let Some(current_entry) = scratch[current_index].take() else {
-                                        // We intentionally **do not** increment `current_index` here. When next element arrives
-                                        // from `finished_rx`, we will inspect the same position again, hoping for it to be Some(_)
-                                        // this time.
-                                        break;
-                                    };
-                                    if let Some(entry) = current_entry.entry {
-                                        result_tx.send(entry).await.log_err();
-                                    }
-                                    current_index += 1;
-                                }
-                                if current_index == buffers_len {
-                                    break;
-                                }
-                            }
-                        });
-                    })
-                    .await;
-            })
-            .detach();
-        result_rx
-    }
-
-    /// Pick paths that might potentially contain a match of a given search query.
-    async fn background_search(
-        unnamed_buffers: Vec<Model<Buffer>>,
-        opened_buffers: HashMap<Arc<Path>, (Model<Buffer>, BufferSnapshot)>,
-        executor: BackgroundExecutor,
-        fs: Arc<dyn Fs>,
-        workers: usize,
-        query: SearchQuery,
-        path_count: usize,
-        snapshots: Vec<LocalSnapshot>,
-        matching_paths_tx: Sender<SearchMatchCandidate>,
-    ) {
-        let fs = &fs;
-        let query = &query;
-        let matching_paths_tx = &matching_paths_tx;
-        let snapshots = &snapshots;
-        let paths_per_worker = (path_count + workers - 1) / workers;
-        for buffer in unnamed_buffers {
-            matching_paths_tx
-                .send(SearchMatchCandidate::OpenBuffer {
-                    buffer: buffer.clone(),
-                    path: None,
-                })
-                .await
-                .log_err();
-        }
-        for (path, (buffer, _)) in opened_buffers.iter() {
-            matching_paths_tx
-                .send(SearchMatchCandidate::OpenBuffer {
-                    buffer: buffer.clone(),
-                    path: Some(path.clone()),
-                })
-                .await
-                .log_err();
-        }
-        executor
-            .scoped(|scope| {
-                let max_concurrent_workers = Arc::new(Semaphore::new(workers));
-
-                for worker_ix in 0..workers {
-                    let worker_start_ix = worker_ix * paths_per_worker;
-                    let worker_end_ix = worker_start_ix + paths_per_worker;
-                    let unnamed_buffers = opened_buffers.clone();
-                    let limiter = Arc::clone(&max_concurrent_workers);
-                    scope.spawn(async move {
-                        let _guard = limiter.acquire().await;
-                        let mut snapshot_start_ix = 0;
-                        let mut abs_path = PathBuf::new();
-                        for snapshot in snapshots {
-                            let snapshot_end_ix = snapshot_start_ix
-                                + if query.include_ignored() {
-                                    snapshot.file_count()
-                                } else {
-                                    snapshot.visible_file_count()
-                                };
-                            if worker_end_ix <= snapshot_start_ix {
-                                break;
-                            } else if worker_start_ix > snapshot_end_ix {
-                                snapshot_start_ix = snapshot_end_ix;
-                                continue;
-                            } else {
-                                let start_in_snapshot =
-                                    worker_start_ix.saturating_sub(snapshot_start_ix);
-                                let end_in_snapshot =
-                                    cmp::min(worker_end_ix, snapshot_end_ix) - snapshot_start_ix;
-
-                                for entry in snapshot
-                                    .files(query.include_ignored(), start_in_snapshot)
-                                    .take(end_in_snapshot - start_in_snapshot)
-                                {
-                                    if matching_paths_tx.is_closed() {
-                                        break;
-                                    }
-                                    if unnamed_buffers.contains_key(&entry.path) {
-                                        continue;
-                                    }
-                                    let matches = if query.file_matches(Some(&entry.path)) {
-                                        abs_path.clear();
-                                        abs_path.push(&snapshot.abs_path());
-                                        abs_path.push(&entry.path);
-                                        if let Some(file) = fs.open_sync(&abs_path).await.log_err()
-                                        {
-                                            query.detect(file).unwrap_or(false)
-                                        } else {
-                                            false
-                                        }
-                                    } else {
-                                        false
-                                    };
-
-                                    if matches {
-                                        let project_path = SearchMatchCandidate::Path {
-                                            worktree_id: snapshot.id(),
-                                            path: entry.path.clone(),
-                                            is_ignored: entry.is_ignored,
-                                        };
-                                        if matching_paths_tx.send(project_path).await.is_err() {
-                                            break;
-                                        }
-                                    }
-                                }
-
-                                snapshot_start_ix = snapshot_end_ix;
-                            }
-                        }
-                    });
-                }
-
-                if query.include_ignored() {
-                    for snapshot in snapshots {
-                        for ignored_entry in snapshot
-                            .entries(query.include_ignored())
-                            .filter(|e| e.is_ignored)
-                        {
-                            let limiter = Arc::clone(&max_concurrent_workers);
-                            scope.spawn(async move {
-                                let _guard = limiter.acquire().await;
-                                let mut ignored_paths_to_process =
-                                    VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]);
-                                while let Some(ignored_abs_path) =
-                                    ignored_paths_to_process.pop_front()
-                                {
-                                    if let Some(fs_metadata) = fs
-                                        .metadata(&ignored_abs_path)
-                                        .await
-                                        .with_context(|| {
-                                            format!("fetching fs metadata for {ignored_abs_path:?}")
-                                        })
-                                        .log_err()
-                                        .flatten()
-                                    {
-                                        if fs_metadata.is_dir {
-                                            if let Some(mut subfiles) = fs
-                                                .read_dir(&ignored_abs_path)
-                                                .await
-                                                .with_context(|| {
-                                                    format!(
-                                                        "listing ignored path {ignored_abs_path:?}"
-                                                    )
-                                                })
-                                                .log_err()
-                                            {
-                                                while let Some(subfile) = subfiles.next().await {
-                                                    if let Some(subfile) = subfile.log_err() {
-                                                        ignored_paths_to_process.push_back(subfile);
-                                                    }
-                                                }
-                                            }
-                                        } else if !fs_metadata.is_symlink {
-                                            if !query.file_matches(Some(&ignored_abs_path))
-                                                || snapshot.is_path_excluded(
-                                                    ignored_entry.path.to_path_buf(),
-                                                )
-                                            {
-                                                continue;
-                                            }
-                                            let matches = if let Some(file) = fs
-                                                .open_sync(&ignored_abs_path)
-                                                .await
-                                                .with_context(|| {
-                                                    format!(
-                                                        "Opening ignored path {ignored_abs_path:?}"
-                                                    )
-                                                })
-                                                .log_err()
-                                            {
-                                                query.detect(file).unwrap_or(false)
-                                            } else {
-                                                false
-                                            };
-                                            if matches {
-                                                let project_path = SearchMatchCandidate::Path {
-                                                    worktree_id: snapshot.id(),
-                                                    path: Arc::from(
-                                                        ignored_abs_path
-                                                            .strip_prefix(snapshot.abs_path())
-                                                            .expect(
-                                                                "scanning worktree-related files",
-                                                            ),
-                                                    ),
-                                                    is_ignored: true,
-                                                };
-                                                if matching_paths_tx
-                                                    .send(project_path)
-                                                    .await
-                                                    .is_err()
-                                                {
-                                                    return;
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            });
-                        }
-                    }
-                }
-            })
-            .await;
-    }
-
-    pub fn request_lsp<R: LspCommand>(
-        &self,
-        buffer_handle: Model<Buffer>,
-        server: LanguageServerToQuery,
-        request: R,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<R::Response>>
-    where
-        <R::LspRequest as lsp::request::Request>::Result: Send,
-        <R::LspRequest as lsp::request::Request>::Params: Send,
-    {
-        let buffer = buffer_handle.read(cx);
-        if self.is_local() {
-            let language_server = match server {
-                LanguageServerToQuery::Primary => {
-                    match self.primary_language_server_for_buffer(buffer, cx) {
-                        Some((_, server)) => Some(Arc::clone(server)),
-                        None => return Task::ready(Ok(Default::default())),
-                    }
-                }
-                LanguageServerToQuery::Other(id) => self
-                    .language_server_for_buffer(buffer, id, cx)
-                    .map(|(_, server)| Arc::clone(server)),
-            };
-            let file = File::from_dyn(buffer.file()).and_then(File::as_local);
-            if let (Some(file), Some(language_server)) = (file, language_server) {
-                let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
-                return cx.spawn(move |this, cx| async move {
-                    if !request.check_capabilities(language_server.capabilities()) {
-                        return Ok(Default::default());
-                    }
-
-                    let result = language_server.request::<R::LspRequest>(lsp_params).await;
-                    let response = match result {
-                        Ok(response) => response,
-
-                        Err(err) => {
-                            log::warn!(
-                                "Generic lsp request to {} failed: {}",
-                                language_server.name(),
-                                err
-                            );
-                            return Err(err);
-                        }
-                    };
-
-                    request
-                        .response_from_lsp(
-                            response,
-                            this.upgrade().ok_or_else(|| anyhow!("no app context"))?,
-                            buffer_handle,
-                            language_server.server_id(),
-                            cx,
-                        )
-                        .await
-                });
-            }
-        } else if let Some(project_id) = self.remote_id() {
-            return self.send_lsp_proto_request(buffer_handle, project_id, request, cx);
-        }
-
-        Task::ready(Ok(Default::default()))
-    }
-
-    fn send_lsp_proto_request<R: LspCommand>(
-        &self,
-        buffer: Model<Buffer>,
-        project_id: u64,
-        request: R,
-        cx: &mut ModelContext<'_, Project>,
-    ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
-        let rpc = self.client.clone();
-        let message = request.to_proto(project_id, buffer.read(cx));
-        cx.spawn(move |this, mut cx| async move {
-            // Ensure the project is still alive by the time the task
-            // is scheduled.
-            this.upgrade().context("project dropped")?;
-            let response = rpc.request(message).await?;
-            let this = this.upgrade().context("project dropped")?;
-            if this.update(&mut cx, |this, _| this.is_read_only())? {
-                Err(anyhow!("disconnected before completing request"))
-            } else {
-                request
-                    .response_from_proto(response, this, buffer, cx)
-                    .await
-            }
-        })
-    }
-
-    fn sort_candidates_and_open_buffers(
-        mut matching_paths_rx: Receiver<SearchMatchCandidate>,
-        cx: &mut ModelContext<Self>,
-    ) -> (
-        futures::channel::oneshot::Receiver<Vec<SearchMatchCandidate>>,
-        Receiver<(
-            Option<(Model<Buffer>, BufferSnapshot)>,
-            SearchMatchCandidateIndex,
-        )>,
-    ) {
-        let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
-        let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();
-        cx.spawn(move |this, cx| async move {
-            let mut buffers = Vec::new();
-            let mut ignored_buffers = Vec::new();
-            while let Some(entry) = matching_paths_rx.next().await {
-                if matches!(
-                    entry,
-                    SearchMatchCandidate::Path {
-                        is_ignored: true,
-                        ..
-                    }
-                ) {
-                    ignored_buffers.push(entry);
-                } else {
-                    buffers.push(entry);
-                }
-            }
-            buffers.sort_by_key(|candidate| candidate.path());
-            ignored_buffers.sort_by_key(|candidate| candidate.path());
-            buffers.extend(ignored_buffers);
-            let matching_paths = buffers.clone();
-            let _ = sorted_buffers_tx.send(buffers);
-            for (index, candidate) in matching_paths.into_iter().enumerate() {
-                if buffers_tx.is_closed() {
-                    break;
-                }
-                let this = this.clone();
-                let buffers_tx = buffers_tx.clone();
-                cx.spawn(move |mut cx| async move {
-                    let buffer = match candidate {
-                        SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),
-                        SearchMatchCandidate::Path {
-                            worktree_id, path, ..
-                        } => this
-                            .update(&mut cx, |this, cx| {
-                                this.open_buffer((worktree_id, path), cx)
-                            })?
-                            .await
-                            .log_err(),
-                    };
-                    if let Some(buffer) = buffer {
-                        let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
-                        buffers_tx
-                            .send((Some((buffer, snapshot)), index))
-                            .await
-                            .log_err();
-                    } else {
-                        buffers_tx.send((None, index)).await.log_err();
-                    }
-
-                    Ok::<_, anyhow::Error>(())
-                })
-                .detach();
-            }
-        })
-        .detach();
-        (sorted_buffers_rx, buffers_rx)
-    }
-
-    pub fn find_or_create_local_worktree(
-        &mut self,
-        abs_path: impl AsRef<Path>,
-        visible: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
-        let abs_path = abs_path.as_ref();
-        if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
-            Task::ready(Ok((tree, relative_path)))
-        } else {
-            let worktree = self.create_local_worktree(abs_path, visible, cx);
-            cx.background_executor()
-                .spawn(async move { Ok((worktree.await?, PathBuf::new())) })
-        }
-    }
-
-    pub fn find_local_worktree(
-        &self,
-        abs_path: &Path,
-        cx: &AppContext,
-    ) -> Option<(Model<Worktree>, PathBuf)> {
-        for tree in &self.worktrees {
-            if let Some(tree) = tree.upgrade() {
-                if let Some(relative_path) = tree
-                    .read(cx)
-                    .as_local()
-                    .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
-                {
-                    return Some((tree.clone(), relative_path.into()));
-                }
-            }
-        }
-        None
-    }
-
-    pub fn is_shared(&self) -> bool {
-        match &self.client_state {
-            Some(ProjectClientState::Local { .. }) => true,
-            _ => false,
-        }
-    }
-
-    fn create_local_worktree(
-        &mut self,
-        abs_path: impl AsRef<Path>,
-        visible: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Worktree>>> {
-        let fs = self.fs.clone();
-        let client = self.client.clone();
-        let next_entry_id = self.next_entry_id.clone();
-        let path: Arc<Path> = abs_path.as_ref().into();
-        let task = self
-            .loading_local_worktrees
-            .entry(path.clone())
-            .or_insert_with(|| {
-                cx.spawn(move |project, mut cx| {
-                    async move {
-                        let worktree = Worktree::local(
-                            client.clone(),
-                            path.clone(),
-                            visible,
-                            fs,
-                            next_entry_id,
-                            &mut cx,
-                        )
-                        .await;
-
-                        project.update(&mut cx, |project, _| {
-                            project.loading_local_worktrees.remove(&path);
-                        })?;
-
-                        let worktree = worktree?;
-                        project
-                            .update(&mut cx, |project, cx| project.add_worktree(&worktree, cx))?;
-                        Ok(worktree)
-                    }
-                    .map_err(Arc::new)
-                })
-                .shared()
-            })
-            .clone();
-        cx.background_executor().spawn(async move {
-            match task.await {
-                Ok(worktree) => Ok(worktree),
-                Err(err) => Err(anyhow!("{}", err)),
-            }
-        })
-    }
-
-    pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
-        self.worktrees.retain(|worktree| {
-            if let Some(worktree) = worktree.upgrade() {
-                let id = worktree.read(cx).id();
-                if id == id_to_remove {
-                    cx.emit(Event::WorktreeRemoved(id));
-                    false
-                } else {
-                    true
-                }
-            } else {
-                false
-            }
-        });
-        self.metadata_changed(cx);
-    }
-
-    fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
-        cx.observe(worktree, |_, _, cx| cx.notify()).detach();
-        if worktree.read(cx).is_local() {
-            cx.subscribe(worktree, |this, worktree, event, cx| match event {
-                worktree::Event::UpdatedEntries(changes) => {
-                    this.update_local_worktree_buffers(&worktree, changes, cx);
-                    this.update_local_worktree_language_servers(&worktree, changes, cx);
-                    this.update_local_worktree_settings(&worktree, changes, cx);
-                    this.update_prettier_settings(&worktree, changes, cx);
-                    cx.emit(Event::WorktreeUpdatedEntries(
-                        worktree.read(cx).id(),
-                        changes.clone(),
-                    ));
-                }
-                worktree::Event::UpdatedGitRepositories(updated_repos) => {
-                    this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
-                }
-            })
-            .detach();
-        }
-
-        let push_strong_handle = {
-            let worktree = worktree.read(cx);
-            self.is_shared() || worktree.is_visible() || worktree.is_remote()
-        };
-        if push_strong_handle {
-            self.worktrees
-                .push(WorktreeHandle::Strong(worktree.clone()));
-        } else {
-            self.worktrees
-                .push(WorktreeHandle::Weak(worktree.downgrade()));
-        }
-
-        let handle_id = worktree.entity_id();
-        cx.observe_release(worktree, move |this, worktree, cx| {
-            let _ = this.remove_worktree(worktree.id(), cx);
-            cx.update_global::<SettingsStore, _>(|store, cx| {
-                store
-                    .clear_local_settings(handle_id.as_u64() as usize, cx)
-                    .log_err()
-            });
-        })
-        .detach();
-
-        cx.emit(Event::WorktreeAdded);
-        self.metadata_changed(cx);
-    }
-
-    fn update_local_worktree_buffers(
-        &mut self,
-        worktree_handle: &Model<Worktree>,
-        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
-        cx: &mut ModelContext<Self>,
-    ) {
-        let snapshot = worktree_handle.read(cx).snapshot();
-
-        let mut renamed_buffers = Vec::new();
-        for (path, entry_id, _) in changes {
-            let worktree_id = worktree_handle.read(cx).id();
-            let project_path = ProjectPath {
-                worktree_id,
-                path: path.clone(),
-            };
-
-            let buffer_id = match self.local_buffer_ids_by_entry_id.get(entry_id) {
-                Some(&buffer_id) => buffer_id,
-                None => match self.local_buffer_ids_by_path.get(&project_path) {
-                    Some(&buffer_id) => buffer_id,
-                    None => {
-                        continue;
-                    }
-                },
-            };
-
-            let open_buffer = self.opened_buffers.get(&buffer_id);
-            let buffer = if let Some(buffer) = open_buffer.and_then(|buffer| buffer.upgrade()) {
-                buffer
-            } else {
-                self.opened_buffers.remove(&buffer_id);
-                self.local_buffer_ids_by_path.remove(&project_path);
-                self.local_buffer_ids_by_entry_id.remove(entry_id);
-                continue;
-            };
-
-            buffer.update(cx, |buffer, cx| {
-                if let Some(old_file) = File::from_dyn(buffer.file()) {
-                    if old_file.worktree != *worktree_handle {
-                        return;
-                    }
-
-                    let new_file = if let Some(entry) = old_file
-                        .entry_id
-                        .and_then(|entry_id| snapshot.entry_for_id(entry_id))
-                    {
-                        File {
-                            is_local: true,
-                            entry_id: Some(entry.id),
-                            mtime: entry.mtime,
-                            path: entry.path.clone(),
-                            worktree: worktree_handle.clone(),
-                            is_deleted: false,
-                        }
-                    } else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) {
-                        File {
-                            is_local: true,
-                            entry_id: Some(entry.id),
-                            mtime: entry.mtime,
-                            path: entry.path.clone(),
-                            worktree: worktree_handle.clone(),
-                            is_deleted: false,
-                        }
-                    } else {
-                        File {
-                            is_local: true,
-                            entry_id: old_file.entry_id,
-                            path: old_file.path().clone(),
-                            mtime: old_file.mtime(),
-                            worktree: worktree_handle.clone(),
-                            is_deleted: true,
-                        }
-                    };
-
-                    let old_path = old_file.abs_path(cx);
-                    if new_file.abs_path(cx) != old_path {
-                        renamed_buffers.push((cx.handle(), old_file.clone()));
-                        self.local_buffer_ids_by_path.remove(&project_path);
-                        self.local_buffer_ids_by_path.insert(
-                            ProjectPath {
-                                worktree_id,
-                                path: path.clone(),
-                            },
-                            buffer_id,
-                        );
-                    }
-
-                    if new_file.entry_id != Some(*entry_id) {
-                        self.local_buffer_ids_by_entry_id.remove(entry_id);
-                        if let Some(entry_id) = new_file.entry_id {
-                            self.local_buffer_ids_by_entry_id
-                                .insert(entry_id, buffer_id);
-                        }
-                    }
-
-                    if new_file != *old_file {
-                        if let Some(project_id) = self.remote_id() {
-                            self.client
-                                .send(proto::UpdateBufferFile {
-                                    project_id,
-                                    buffer_id: buffer_id as u64,
-                                    file: Some(new_file.to_proto()),
-                                })
-                                .log_err();
-                        }
-
-                        buffer.file_updated(Arc::new(new_file), cx);
-                    }
-                }
-            });
-        }
-
-        for (buffer, old_file) in renamed_buffers {
-            self.unregister_buffer_from_language_servers(&buffer, &old_file, cx);
-            self.detect_language_for_buffer(&buffer, cx);
-            self.register_buffer_with_language_servers(&buffer, cx);
-        }
-    }
-
-    fn update_local_worktree_language_servers(
-        &mut self,
-        worktree_handle: &Model<Worktree>,
-        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
-        cx: &mut ModelContext<Self>,
-    ) {
-        if changes.is_empty() {
-            return;
-        }
-
-        let worktree_id = worktree_handle.read(cx).id();
-        let mut language_server_ids = self
-            .language_server_ids
-            .iter()
-            .filter_map(|((server_worktree_id, _), server_id)| {
-                (*server_worktree_id == worktree_id).then_some(*server_id)
-            })
-            .collect::<Vec<_>>();
-        language_server_ids.sort();
-        language_server_ids.dedup();
-
-        let abs_path = worktree_handle.read(cx).abs_path();
-        for server_id in &language_server_ids {
-            if let Some(LanguageServerState::Running {
-                server,
-                watched_paths,
-                ..
-            }) = self.language_servers.get(server_id)
-            {
-                if let Some(watched_paths) = watched_paths.get(&worktree_id) {
-                    let params = lsp::DidChangeWatchedFilesParams {
-                        changes: changes
-                            .iter()
-                            .filter_map(|(path, _, change)| {
-                                if !watched_paths.is_match(&path) {
-                                    return None;
-                                }
-                                let typ = match change {
-                                    PathChange::Loaded => return None,
-                                    PathChange::Added => lsp::FileChangeType::CREATED,
-                                    PathChange::Removed => lsp::FileChangeType::DELETED,
-                                    PathChange::Updated => lsp::FileChangeType::CHANGED,
-                                    PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED,
-                                };
-                                Some(lsp::FileEvent {
-                                    uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(),
-                                    typ,
-                                })
-                            })
-                            .collect(),
-                    };
-
-                    if !params.changes.is_empty() {
-                        server
-                            .notify::<lsp::notification::DidChangeWatchedFiles>(params)
-                            .log_err();
-                    }
-                }
-            }
-        }
-    }
-
-    fn update_local_worktree_buffers_git_repos(
-        &mut self,
-        worktree_handle: Model<Worktree>,
-        changed_repos: &UpdatedGitRepositoriesSet,
-        cx: &mut ModelContext<Self>,
-    ) {
-        debug_assert!(worktree_handle.read(cx).is_local());
-
-        // Identify the loading buffers whose containing repository that has changed.
-        let future_buffers = self
-            .loading_buffers_by_path
-            .iter()
-            .filter_map(|(project_path, receiver)| {
-                if project_path.worktree_id != worktree_handle.read(cx).id() {
-                    return None;
-                }
-                let path = &project_path.path;
-                changed_repos
-                    .iter()
-                    .find(|(work_dir, _)| path.starts_with(work_dir))?;
-                let receiver = receiver.clone();
-                let path = path.clone();
-                Some(async move {
-                    wait_for_loading_buffer(receiver)
-                        .await
-                        .ok()
-                        .map(|buffer| (buffer, path))
-                })
-            })
-            .collect::<FuturesUnordered<_>>();
-
-        // Identify the current buffers whose containing repository has changed.
-        let current_buffers = self
-            .opened_buffers
-            .values()
-            .filter_map(|buffer| {
-                let buffer = buffer.upgrade()?;
-                let file = File::from_dyn(buffer.read(cx).file())?;
-                if file.worktree != worktree_handle {
-                    return None;
-                }
-                let path = file.path();
-                changed_repos
-                    .iter()
-                    .find(|(work_dir, _)| path.starts_with(work_dir))?;
-                Some((buffer, path.clone()))
-            })
-            .collect::<Vec<_>>();
-
-        if future_buffers.len() + current_buffers.len() == 0 {
-            return;
-        }
-
-        let remote_id = self.remote_id();
-        let client = self.client.clone();
-        cx.spawn(move |_, mut cx| async move {
-            // Wait for all of the buffers to load.
-            let future_buffers = future_buffers.collect::<Vec<_>>().await;
-
-            // Reload the diff base for every buffer whose containing git repository has changed.
-            let snapshot =
-                worktree_handle.update(&mut cx, |tree, _| tree.as_local().unwrap().snapshot())?;
-            let diff_bases_by_buffer = cx
-                .background_executor()
-                .spawn(async move {
-                    future_buffers
-                        .into_iter()
-                        .filter_map(|e| e)
-                        .chain(current_buffers)
-                        .filter_map(|(buffer, path)| {
-                            let (work_directory, repo) =
-                                snapshot.repository_and_work_directory_for_path(&path)?;
-                            let repo = snapshot.get_local_repo(&repo)?;
-                            let relative_path = path.strip_prefix(&work_directory).ok()?;
-                            let base_text = repo.repo_ptr.lock().load_index_text(&relative_path);
-                            Some((buffer, base_text))
-                        })
-                        .collect::<Vec<_>>()
-                })
-                .await;
-
-            // Assign the new diff bases on all of the buffers.
-            for (buffer, diff_base) in diff_bases_by_buffer {
-                let buffer_id = buffer.update(&mut cx, |buffer, cx| {
-                    buffer.set_diff_base(diff_base.clone(), cx);
-                    buffer.remote_id()
-                })?;
-                if let Some(project_id) = remote_id {
-                    client
-                        .send(proto::UpdateDiffBase {
-                            project_id,
-                            buffer_id,
-                            diff_base,
-                        })
-                        .log_err();
-                }
-            }
-
-            anyhow::Ok(())
-        })
-        .detach();
-    }
-
-    fn update_local_worktree_settings(
-        &mut self,
-        worktree: &Model<Worktree>,
-        changes: &UpdatedEntriesSet,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let project_id = self.remote_id();
-        let worktree_id = worktree.entity_id();
-        let worktree = worktree.read(cx).as_local().unwrap();
-        let remote_worktree_id = worktree.id();
-
-        let mut settings_contents = Vec::new();
-        for (path, _, change) in changes.iter() {
-            if path.ends_with(&*LOCAL_SETTINGS_RELATIVE_PATH) {
-                let settings_dir = Arc::from(
-                    path.ancestors()
-                        .nth(LOCAL_SETTINGS_RELATIVE_PATH.components().count())
-                        .unwrap(),
-                );
-                let fs = self.fs.clone();
-                let removed = *change == PathChange::Removed;
-                let abs_path = worktree.absolutize(path);
-                settings_contents.push(async move {
-                    (settings_dir, (!removed).then_some(fs.load(&abs_path).await))
-                });
-            }
-        }
-
-        if settings_contents.is_empty() {
-            return;
-        }
-
-        let client = self.client.clone();
-        cx.spawn(move |_, cx| async move {
-            let settings_contents: Vec<(Arc<Path>, _)> =
-                futures::future::join_all(settings_contents).await;
-            cx.update(|cx| {
-                cx.update_global::<SettingsStore, _>(|store, cx| {
-                    for (directory, file_content) in settings_contents {
-                        let file_content = file_content.and_then(|content| content.log_err());
-                        store
-                            .set_local_settings(
-                                worktree_id.as_u64() as usize,
-                                directory.clone(),
-                                file_content.as_ref().map(String::as_str),
-                                cx,
-                            )
-                            .log_err();
-                        if let Some(remote_id) = project_id {
-                            client
-                                .send(proto::UpdateWorktreeSettings {
-                                    project_id: remote_id,
-                                    worktree_id: remote_worktree_id.to_proto(),
-                                    path: directory.to_string_lossy().into_owned(),
-                                    content: file_content,
-                                })
-                                .log_err();
-                        }
-                    }
-                });
-            })
-            .ok();
-        })
-        .detach();
-    }
-
-    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
-        let new_active_entry = entry.and_then(|project_path| {
-            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
-            let entry = worktree.read(cx).entry_for_path(project_path.path)?;
-            Some(entry.id)
-        });
-        if new_active_entry != self.active_entry {
-            self.active_entry = new_active_entry;
-            cx.emit(Event::ActiveEntryChanged(new_active_entry));
-        }
-    }
-
-    pub fn language_servers_running_disk_based_diagnostics(
-        &self,
-    ) -> impl Iterator<Item = LanguageServerId> + '_ {
-        self.language_server_statuses
-            .iter()
-            .filter_map(|(id, status)| {
-                if status.has_pending_diagnostic_updates {
-                    Some(*id)
-                } else {
-                    None
-                }
-            })
-    }
-
-    pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
-        let mut summary = DiagnosticSummary::default();
-        for (_, _, path_summary) in
-            self.diagnostic_summaries(include_ignored, cx)
-                .filter(|(path, _, _)| {
-                    let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
-                    include_ignored || worktree == Some(false)
-                })
-        {
-            summary.error_count += path_summary.error_count;
-            summary.warning_count += path_summary.warning_count;
-        }
-        summary
-    }
-
-    pub fn diagnostic_summaries<'a>(
-        &'a self,
-        include_ignored: bool,
-        cx: &'a AppContext,
-    ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
-        self.visible_worktrees(cx)
-            .flat_map(move |worktree| {
-                let worktree = worktree.read(cx);
-                let worktree_id = worktree.id();
-                worktree
-                    .diagnostic_summaries()
-                    .map(move |(path, server_id, summary)| {
-                        (ProjectPath { worktree_id, path }, server_id, summary)
-                    })
-            })
-            .filter(move |(path, _, _)| {
-                let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
-                include_ignored || worktree == Some(false)
-            })
-    }
-
-    pub fn disk_based_diagnostics_started(
-        &mut self,
-        language_server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) {
-        cx.emit(Event::DiskBasedDiagnosticsStarted { language_server_id });
-    }
-
-    pub fn disk_based_diagnostics_finished(
-        &mut self,
-        language_server_id: LanguageServerId,
-        cx: &mut ModelContext<Self>,
-    ) {
-        cx.emit(Event::DiskBasedDiagnosticsFinished { language_server_id });
-    }
-
-    pub fn active_entry(&self) -> Option<ProjectEntryId> {
-        self.active_entry
-    }
-
-    pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
-        self.worktree_for_id(path.worktree_id, cx)?
-            .read(cx)
-            .entry_for_path(&path.path)
-            .cloned()
-    }
-
-    pub fn path_for_entry(&self, entry_id: ProjectEntryId, cx: &AppContext) -> Option<ProjectPath> {
-        let worktree = self.worktree_for_entry(entry_id, cx)?;
-        let worktree = worktree.read(cx);
-        let worktree_id = worktree.id();
-        let path = worktree.entry_for_id(entry_id)?.path.clone();
-        Some(ProjectPath { worktree_id, path })
-    }
-
-    pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
-        let workspace_root = self
-            .worktree_for_id(project_path.worktree_id, cx)?
-            .read(cx)
-            .abs_path();
-        let project_path = project_path.path.as_ref();
-
-        Some(if project_path == Path::new("") {
-            workspace_root.to_path_buf()
-        } else {
-            workspace_root.join(project_path)
-        })
-    }
-
-    // RPC message handlers
-
-    async fn handle_unshare_project(
-        this: Model<Self>,
-        _: TypedEnvelope<proto::UnshareProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            if this.is_local() {
-                this.unshare(cx)?;
-            } else {
-                this.disconnected_from_host(cx);
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_add_collaborator(
-        this: Model<Self>,
-        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let collaborator = envelope
-            .payload
-            .collaborator
-            .take()
-            .ok_or_else(|| anyhow!("empty collaborator"))?;
-
-        let collaborator = Collaborator::from_proto(collaborator)?;
-        this.update(&mut cx, |this, cx| {
-            this.shared_buffers.remove(&collaborator.peer_id);
-            cx.emit(Event::CollaboratorJoined(collaborator.peer_id));
-            this.collaborators
-                .insert(collaborator.peer_id, collaborator);
-            cx.notify();
-        })?;
-
-        Ok(())
-    }
-
-    async fn handle_update_project_collaborator(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let old_peer_id = envelope
-            .payload
-            .old_peer_id
-            .ok_or_else(|| anyhow!("missing old peer id"))?;
-        let new_peer_id = envelope
-            .payload
-            .new_peer_id
-            .ok_or_else(|| anyhow!("missing new peer id"))?;
-        this.update(&mut cx, |this, cx| {
-            let collaborator = this
-                .collaborators
-                .remove(&old_peer_id)
-                .ok_or_else(|| anyhow!("received UpdateProjectCollaborator for unknown peer"))?;
-            let is_host = collaborator.replica_id == 0;
-            this.collaborators.insert(new_peer_id, collaborator);
-
-            let buffers = this.shared_buffers.remove(&old_peer_id);
-            log::info!(
-                "peer {} became {}. moving buffers {:?}",
-                old_peer_id,
-                new_peer_id,
-                &buffers
-            );
-            if let Some(buffers) = buffers {
-                this.shared_buffers.insert(new_peer_id, buffers);
-            }
-
-            if is_host {
-                this.opened_buffers
-                    .retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
-                this.buffer_ordered_messages_tx
-                    .unbounded_send(BufferOrderedMessage::Resync)
-                    .unwrap();
-            }
-
-            cx.emit(Event::CollaboratorUpdated {
-                old_peer_id,
-                new_peer_id,
-            });
-            cx.notify();
-            Ok(())
-        })?
-    }
-
-    async fn handle_remove_collaborator(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let peer_id = envelope
-                .payload
-                .peer_id
-                .ok_or_else(|| anyhow!("invalid peer id"))?;
-            let replica_id = this
-                .collaborators
-                .remove(&peer_id)
-                .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
-                .replica_id;
-            for buffer in this.opened_buffers.values() {
-                if let Some(buffer) = buffer.upgrade() {
-                    buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
-                }
-            }
-            this.shared_buffers.remove(&peer_id);
-
-            cx.emit(Event::CollaboratorLeft(peer_id));
-            cx.notify();
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_project(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            // Don't handle messages that were sent before the response to us joining the project
-            if envelope.message_id > this.join_project_response_message_id {
-                this.set_worktrees_from_proto(envelope.payload.worktrees, cx)?;
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_worktree(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateWorktree>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
-                worktree.update(cx, |worktree, _| {
-                    let worktree = worktree.as_remote_mut().unwrap();
-                    worktree.update_from_remote(envelope.payload);
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_worktree_settings(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
-                cx.update_global::<SettingsStore, _>(|store, cx| {
-                    store
-                        .set_local_settings(
-                            worktree.entity_id().as_u64() as usize,
-                            PathBuf::from(&envelope.payload.path).into(),
-                            envelope.payload.content.as_ref().map(String::as_str),
-                            cx,
-                        )
-                        .log_err();
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_create_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CreateProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let worktree = this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            this.worktree_for_id(worktree_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        let entry = worktree
-            .update(&mut cx, |worktree, cx| {
-                let worktree = worktree.as_local_mut().unwrap();
-                let path = PathBuf::from(envelope.payload.path);
-                worktree.create_entry(path, envelope.payload.is_directory, cx)
-            })?
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: entry.as_ref().map(|e| e.into()),
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_rename_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::RenameProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let worktree = this.update(&mut cx, |this, cx| {
-            this.worktree_for_entry(entry_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        let entry = worktree
-            .update(&mut cx, |worktree, cx| {
-                let new_path = PathBuf::from(envelope.payload.new_path);
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .rename_entry(entry_id, new_path, cx)
-            })?
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: entry.as_ref().map(|e| e.into()),
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_copy_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CopyProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let worktree = this.update(&mut cx, |this, cx| {
-            this.worktree_for_entry(entry_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        let entry = worktree
-            .update(&mut cx, |worktree, cx| {
-                let new_path = PathBuf::from(envelope.payload.new_path);
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .copy_entry(entry_id, new_path, cx)
-            })?
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: entry.as_ref().map(|e| e.into()),
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_delete_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::DeleteProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-
-        this.update(&mut cx, |_, cx| cx.emit(Event::DeletedEntry(entry_id)))?;
-
-        let worktree = this.update(&mut cx, |this, cx| {
-            this.worktree_for_entry(entry_id, cx)
-                .ok_or_else(|| anyhow!("worktree not found"))
-        })??;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())?;
-        worktree
-            .update(&mut cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .delete_entry(entry_id, cx)
-                    .ok_or_else(|| anyhow!("invalid entry"))
-            })??
-            .await?;
-        Ok(proto::ProjectEntryResponse {
-            entry: None,
-            worktree_scan_id: worktree_scan_id as u64,
-        })
-    }
-
-    async fn handle_expand_project_entry(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ExpandProjectEntry>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ExpandProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let worktree = this
-            .update(&mut cx, |this, cx| this.worktree_for_entry(entry_id, cx))?
-            .ok_or_else(|| anyhow!("invalid request"))?;
-        worktree
-            .update(&mut cx, |worktree, cx| {
-                worktree
-                    .as_local_mut()
-                    .unwrap()
-                    .expand_entry(entry_id, cx)
-                    .ok_or_else(|| anyhow!("invalid entry"))
-            })??
-            .await?;
-        let worktree_scan_id = worktree.update(&mut cx, |worktree, _| worktree.scan_id())? as u64;
-        Ok(proto::ExpandProjectEntryResponse { worktree_scan_id })
-    }
-
-    async fn handle_update_diagnostic_summary(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
-                if let Some(summary) = envelope.payload.summary {
-                    let project_path = ProjectPath {
-                        worktree_id,
-                        path: Path::new(&summary.path).into(),
-                    };
-                    worktree.update(cx, |worktree, _| {
-                        worktree
-                            .as_remote_mut()
-                            .unwrap()
-                            .update_diagnostic_summary(project_path.path.clone(), &summary);
-                    });
-                    cx.emit(Event::DiagnosticsUpdated {
-                        language_server_id: LanguageServerId(summary.language_server_id as usize),
-                        path: project_path,
-                    });
-                }
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_start_language_server(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::StartLanguageServer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let server = envelope
-            .payload
-            .server
-            .ok_or_else(|| anyhow!("invalid server"))?;
-        this.update(&mut cx, |this, cx| {
-            this.language_server_statuses.insert(
-                LanguageServerId(server.id as usize),
-                LanguageServerStatus {
-                    name: server.name,
-                    pending_work: Default::default(),
-                    has_pending_diagnostic_updates: false,
-                    progress_tokens: Default::default(),
-                },
-            );
-            cx.notify();
-        })?;
-        Ok(())
-    }
-
-    async fn handle_update_language_server(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateLanguageServer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let language_server_id = LanguageServerId(envelope.payload.language_server_id as usize);
-
-            match envelope
-                .payload
-                .variant
-                .ok_or_else(|| anyhow!("invalid variant"))?
-            {
-                proto::update_language_server::Variant::WorkStart(payload) => {
-                    this.on_lsp_work_start(
-                        language_server_id,
-                        payload.token,
-                        LanguageServerProgress {
-                            message: payload.message,
-                            percentage: payload.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                }
-
-                proto::update_language_server::Variant::WorkProgress(payload) => {
-                    this.on_lsp_work_progress(
-                        language_server_id,
-                        payload.token,
-                        LanguageServerProgress {
-                            message: payload.message,
-                            percentage: payload.percentage.map(|p| p as usize),
-                            last_update_at: Instant::now(),
-                        },
-                        cx,
-                    );
-                }
-
-                proto::update_language_server::Variant::WorkEnd(payload) => {
-                    this.on_lsp_work_end(language_server_id, payload.token, cx);
-                }
-
-                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => {
-                    this.disk_based_diagnostics_started(language_server_id, cx);
-                }
-
-                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => {
-                    this.disk_based_diagnostics_finished(language_server_id, cx)
-                }
-            }
-
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_buffer(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateBuffer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        this.update(&mut cx, |this, cx| {
-            let payload = envelope.payload.clone();
-            let buffer_id = payload.buffer_id;
-            let ops = payload
-                .operations
-                .into_iter()
-                .map(language::proto::deserialize_operation)
-                .collect::<Result<Vec<_>, _>>()?;
-            let is_remote = this.is_remote();
-            match this.opened_buffers.entry(buffer_id) {
-                hash_map::Entry::Occupied(mut e) => match e.get_mut() {
-                    OpenBuffer::Strong(buffer) => {
-                        buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
-                    }
-                    OpenBuffer::Operations(operations) => operations.extend_from_slice(&ops),
-                    OpenBuffer::Weak(_) => {}
-                },
-                hash_map::Entry::Vacant(e) => {
-                    assert!(
-                        is_remote,
-                        "received buffer update from {:?}",
-                        envelope.original_sender_id
-                    );
-                    e.insert(OpenBuffer::Operations(ops));
-                }
-            }
-            Ok(proto::Ack {})
-        })?
-    }
-
-    async fn handle_create_buffer_for_peer(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CreateBufferForPeer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            match envelope
-                .payload
-                .variant
-                .ok_or_else(|| anyhow!("missing variant"))?
-            {
-                proto::create_buffer_for_peer::Variant::State(mut state) => {
-                    let mut buffer_file = None;
-                    if let Some(file) = state.file.take() {
-                        let worktree_id = WorktreeId::from_proto(file.worktree_id);
-                        let worktree = this.worktree_for_id(worktree_id, cx).ok_or_else(|| {
-                            anyhow!("no worktree found for id {}", file.worktree_id)
-                        })?;
-                        buffer_file = Some(Arc::new(File::from_proto(file, worktree.clone(), cx)?)
-                            as Arc<dyn language::File>);
-                    }
-
-                    let buffer_id = state.id;
-                    let buffer = cx.new_model(|_| {
-                        Buffer::from_proto(this.replica_id(), state, buffer_file).unwrap()
-                    });
-                    this.incomplete_remote_buffers
-                        .insert(buffer_id, Some(buffer));
-                }
-                proto::create_buffer_for_peer::Variant::Chunk(chunk) => {
-                    let buffer = this
-                        .incomplete_remote_buffers
-                        .get(&chunk.buffer_id)
-                        .cloned()
-                        .flatten()
-                        .ok_or_else(|| {
-                            anyhow!(
-                                "received chunk for buffer {} without initial state",
-                                chunk.buffer_id
-                            )
-                        })?;
-                    let operations = chunk
-                        .operations
-                        .into_iter()
-                        .map(language::proto::deserialize_operation)
-                        .collect::<Result<Vec<_>>>()?;
-                    buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
-
-                    if chunk.is_last {
-                        this.incomplete_remote_buffers.remove(&chunk.buffer_id);
-                        this.register_buffer(&buffer, cx)?;
-                    }
-                }
-            }
-
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_diff_base(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateDiffBase>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        this.update(&mut cx, |this, cx| {
-            let buffer_id = envelope.payload.buffer_id;
-            let diff_base = envelope.payload.diff_base;
-            if let Some(buffer) = this
-                .opened_buffers
-                .get_mut(&buffer_id)
-                .and_then(|b| b.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&buffer_id)
-                        .cloned()
-                        .flatten()
-                })
-            {
-                buffer.update(cx, |buffer, cx| buffer.set_diff_base(diff_base, cx));
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_buffer_file(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateBufferFile>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let buffer_id = envelope.payload.buffer_id;
-
-        this.update(&mut cx, |this, cx| {
-            let payload = envelope.payload.clone();
-            if let Some(buffer) = this
-                .opened_buffers
-                .get(&buffer_id)
-                .and_then(|b| b.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&buffer_id)
-                        .cloned()
-                        .flatten()
-                })
-            {
-                let file = payload.file.ok_or_else(|| anyhow!("invalid file"))?;
-                let worktree = this
-                    .worktree_for_id(WorktreeId::from_proto(file.worktree_id), cx)
-                    .ok_or_else(|| anyhow!("no such worktree"))?;
-                let file = File::from_proto(file, worktree, cx)?;
-                buffer.update(cx, |buffer, cx| {
-                    buffer.file_updated(Arc::new(file), cx);
-                });
-                this.detect_language_for_buffer(&buffer, cx);
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_save_buffer(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::SaveBuffer>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::BufferSaved> {
-        let buffer_id = envelope.payload.buffer_id;
-        let (project_id, buffer) = this.update(&mut cx, |this, _cx| {
-            let project_id = this.remote_id().ok_or_else(|| anyhow!("not connected"))?;
-            let buffer = this
-                .opened_buffers
-                .get(&buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
-            anyhow::Ok((project_id, buffer))
-        })??;
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(deserialize_version(&envelope.payload.version))
-            })?
-            .await?;
-        let buffer_id = buffer.update(&mut cx, |buffer, _| buffer.remote_id())?;
-
-        this.update(&mut cx, |this, cx| this.save_buffer(buffer.clone(), cx))?
-            .await?;
-        Ok(buffer.update(&mut cx, |buffer, _| proto::BufferSaved {
-            project_id,
-            buffer_id,
-            version: serialize_version(buffer.saved_version()),
-            mtime: Some(buffer.saved_mtime().into()),
-            fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()),
-        })?)
-    }
-
-    async fn handle_reload_buffers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ReloadBuffers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ReloadBuffersResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let reload = this.update(&mut cx, |this, cx| {
-            let mut buffers = HashSet::default();
-            for buffer_id in &envelope.payload.buffer_ids {
-                buffers.insert(
-                    this.opened_buffers
-                        .get(buffer_id)
-                        .and_then(|buffer| buffer.upgrade())
-                        .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
-                );
-            }
-            Ok::<_, anyhow::Error>(this.reload_buffers(buffers, false, cx))
-        })??;
-
-        let project_transaction = reload.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        })?;
-        Ok(proto::ReloadBuffersResponse {
-            transaction: Some(project_transaction),
-        })
-    }
-
-    async fn handle_synchronize_buffers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::SynchronizeBuffers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::SynchronizeBuffersResponse> {
-        let project_id = envelope.payload.project_id;
-        let mut response = proto::SynchronizeBuffersResponse {
-            buffers: Default::default(),
-        };
-
-        this.update(&mut cx, |this, cx| {
-            let Some(guest_id) = envelope.original_sender_id else {
-                error!("missing original_sender_id on SynchronizeBuffers request");
-                return;
-            };
-
-            this.shared_buffers.entry(guest_id).or_default().clear();
-            for buffer in envelope.payload.buffers {
-                let buffer_id = buffer.id;
-                let remote_version = language::proto::deserialize_version(&buffer.version);
-                if let Some(buffer) = this.buffer_for_id(buffer_id) {
-                    this.shared_buffers
-                        .entry(guest_id)
-                        .or_default()
-                        .insert(buffer_id);
-
-                    let buffer = buffer.read(cx);
-                    response.buffers.push(proto::BufferVersion {
-                        id: buffer_id,
-                        version: language::proto::serialize_version(&buffer.version),
-                    });
-
-                    let operations = buffer.serialize_ops(Some(remote_version), cx);
-                    let client = this.client.clone();
-                    if let Some(file) = buffer.file() {
-                        client
-                            .send(proto::UpdateBufferFile {
-                                project_id,
-                                buffer_id: buffer_id as u64,
-                                file: Some(file.to_proto()),
-                            })
-                            .log_err();
-                    }
-
-                    client
-                        .send(proto::UpdateDiffBase {
-                            project_id,
-                            buffer_id: buffer_id as u64,
-                            diff_base: buffer.diff_base().map(Into::into),
-                        })
-                        .log_err();
-
-                    client
-                        .send(proto::BufferReloaded {
-                            project_id,
-                            buffer_id,
-                            version: language::proto::serialize_version(buffer.saved_version()),
-                            mtime: Some(buffer.saved_mtime().into()),
-                            fingerprint: language::proto::serialize_fingerprint(
-                                buffer.saved_version_fingerprint(),
-                            ),
-                            line_ending: language::proto::serialize_line_ending(
-                                buffer.line_ending(),
-                            ) as i32,
-                        })
-                        .log_err();
-
-                    cx.background_executor()
-                        .spawn(
-                            async move {
-                                let operations = operations.await;
-                                for chunk in split_operations(operations) {
-                                    client
-                                        .request(proto::UpdateBuffer {
-                                            project_id,
-                                            buffer_id,
-                                            operations: chunk,
-                                        })
-                                        .await?;
-                                }
-                                anyhow::Ok(())
-                            }
-                            .log_err(),
-                        )
-                        .detach();
-                }
-            }
-        })?;
-
-        Ok(response)
-    }
-
-    async fn handle_format_buffers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::FormatBuffers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::FormatBuffersResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let format = this.update(&mut cx, |this, cx| {
-            let mut buffers = HashSet::default();
-            for buffer_id in &envelope.payload.buffer_ids {
-                buffers.insert(
-                    this.opened_buffers
-                        .get(buffer_id)
-                        .and_then(|buffer| buffer.upgrade())
-                        .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
-                );
-            }
-            let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
-            Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
-        })??;
-
-        let project_transaction = format.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        })?;
-        Ok(proto::FormatBuffersResponse {
-            transaction: Some(project_transaction),
-        })
-    }
-
-    async fn handle_apply_additional_edits_for_completion(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
-        let (buffer, completion) = this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
-            let language = buffer.read(cx).language();
-            let completion = language::proto::deserialize_completion(
-                envelope
-                    .payload
-                    .completion
-                    .ok_or_else(|| anyhow!("invalid completion"))?,
-                language.cloned(),
-            );
-            Ok::<_, anyhow::Error>((buffer, completion))
-        })??;
-
-        let completion = completion.await?;
-
-        let apply_additional_edits = this.update(&mut cx, |this, cx| {
-            this.apply_additional_edits_for_completion(buffer, completion, false, cx)
-        })?;
-
-        Ok(proto::ApplyCompletionAdditionalEditsResponse {
-            transaction: apply_additional_edits
-                .await?
-                .as_ref()
-                .map(language::proto::serialize_transaction),
-        })
-    }
-
-    async fn handle_apply_code_action(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ApplyCodeAction>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ApplyCodeActionResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let action = language::proto::deserialize_code_action(
-            envelope
-                .payload
-                .action
-                .ok_or_else(|| anyhow!("invalid action"))?,
-        )?;
-        let apply_code_action = this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
-            Ok::<_, anyhow::Error>(this.apply_code_action(buffer, action, false, cx))
-        })??;
-
-        let project_transaction = apply_code_action.await?;
-        let project_transaction = this.update(&mut cx, |this, cx| {
-            this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
-        })?;
-        Ok(proto::ApplyCodeActionResponse {
-            transaction: Some(project_transaction),
-        })
-    }
-
-    async fn handle_on_type_formatting(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OnTypeFormatting>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OnTypeFormattingResponse> {
-        let on_type_formatting = this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
-            let position = envelope
-                .payload
-                .position
-                .and_then(deserialize_anchor)
-                .ok_or_else(|| anyhow!("invalid position"))?;
-            Ok::<_, anyhow::Error>(this.apply_on_type_formatting(
-                buffer,
-                position,
-                envelope.payload.trigger.clone(),
-                cx,
-            ))
-        })??;
-
-        let transaction = on_type_formatting
-            .await?
-            .as_ref()
-            .map(language::proto::serialize_transaction);
-        Ok(proto::OnTypeFormattingResponse { transaction })
-    }
-
-    async fn handle_inlay_hints(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::InlayHints>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::InlayHintsResponse> {
-        let sender_id = envelope.original_sender_id()?;
-        let buffer = this.update(&mut cx, |this, _| {
-            this.opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
-        })??;
-        let buffer_version = deserialize_version(&envelope.payload.version);
-
-        buffer
-            .update(&mut cx, |buffer, _| {
-                buffer.wait_for_version(buffer_version.clone())
-            })?
-            .await
-            .with_context(|| {
-                format!(
-                    "waiting for version {:?} for buffer {}",
-                    buffer_version,
-                    buffer.entity_id()
-                )
-            })?;
-
-        let start = envelope
-            .payload
-            .start
-            .and_then(deserialize_anchor)
-            .context("missing range start")?;
-        let end = envelope
-            .payload
-            .end
-            .and_then(deserialize_anchor)
-            .context("missing range end")?;
-        let buffer_hints = this
-            .update(&mut cx, |project, cx| {
-                project.inlay_hints(buffer, start..end, cx)
-            })?
-            .await
-            .context("inlay hints fetch")?;
-
-        Ok(this.update(&mut cx, |project, cx| {
-            InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx)
-        })?)
-    }
-
-    async fn handle_resolve_inlay_hint(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ResolveInlayHint>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ResolveInlayHintResponse> {
-        let proto_hint = envelope
-            .payload
-            .hint
-            .expect("incorrect protobuf resolve inlay hint message: missing the inlay hint");
-        let hint = InlayHints::proto_to_project_hint(proto_hint)
-            .context("resolved proto inlay hint conversion")?;
-        let buffer = this.update(&mut cx, |this, _cx| {
-            this.opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
-        })??;
-        let response_hint = this
-            .update(&mut cx, |project, cx| {
-                project.resolve_inlay_hint(
-                    hint,
-                    buffer,
-                    LanguageServerId(envelope.payload.language_server_id as usize),
-                    cx,
-                )
-            })?
-            .await
-            .context("inlay hints fetch")?;
-        Ok(proto::ResolveInlayHintResponse {
-            hint: Some(InlayHints::project_to_proto_hint(response_hint)),
-        })
-    }
-
-    async fn handle_refresh_inlay_hints(
-        this: Model<Self>,
-        _: TypedEnvelope<proto::RefreshInlayHints>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        this.update(&mut cx, |_, cx| {
-            cx.emit(Event::RefreshInlayHints);
-        })?;
-        Ok(proto::Ack {})
-    }
-
-    async fn handle_lsp_command<T: LspCommand>(
-        this: Model<Self>,
-        envelope: TypedEnvelope<T::ProtoRequest>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<<T::ProtoRequest as proto::RequestMessage>::Response>
-    where
-        <T::LspRequest as lsp::request::Request>::Params: Send,
-        <T::LspRequest as lsp::request::Request>::Result: Send,
-    {
-        let sender_id = envelope.original_sender_id()?;
-        let buffer_id = T::buffer_id_from_proto(&envelope.payload);
-        let buffer_handle = this.update(&mut cx, |this, _cx| {
-            this.opened_buffers
-                .get(&buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))
-        })??;
-        let request = T::from_proto(
-            envelope.payload,
-            this.clone(),
-            buffer_handle.clone(),
-            cx.clone(),
-        )
-        .await?;
-        let buffer_version = buffer_handle.update(&mut cx, |buffer, _| buffer.version())?;
-        let response = this
-            .update(&mut cx, |this, cx| {
-                this.request_lsp(buffer_handle, LanguageServerToQuery::Primary, request, cx)
-            })?
-            .await?;
-        this.update(&mut cx, |this, cx| {
-            Ok(T::response_to_proto(
-                response,
-                this,
-                sender_id,
-                &buffer_version,
-                cx,
-            ))
-        })?
-    }
-
-    async fn handle_get_project_symbols(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::GetProjectSymbols>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::GetProjectSymbolsResponse> {
-        let symbols = this
-            .update(&mut cx, |this, cx| {
-                this.symbols(&envelope.payload.query, cx)
-            })?
-            .await?;
-
-        Ok(proto::GetProjectSymbolsResponse {
-            symbols: symbols.iter().map(serialize_symbol).collect(),
-        })
-    }
-
-    async fn handle_search_project(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::SearchProject>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::SearchProjectResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let query = SearchQuery::from_proto(envelope.payload)?;
-        let mut result = this.update(&mut cx, |this, cx| this.search(query, cx))?;
-
-        cx.spawn(move |mut cx| async move {
-            let mut locations = Vec::new();
-            while let Some((buffer, ranges)) = result.next().await {
-                for range in ranges {
-                    let start = serialize_anchor(&range.start);
-                    let end = serialize_anchor(&range.end);
-                    let buffer_id = this.update(&mut cx, |this, cx| {
-                        this.create_buffer_for_peer(&buffer, peer_id, cx)
-                    })?;
-                    locations.push(proto::Location {
-                        buffer_id,
-                        start: Some(start),
-                        end: Some(end),
-                    });
-                }
-            }
-            Ok(proto::SearchProjectResponse { locations })
-        })
-        .await
-    }
-
-    async fn handle_open_buffer_for_symbol(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OpenBufferForSymbol>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OpenBufferForSymbolResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let symbol = envelope
-            .payload
-            .symbol
-            .ok_or_else(|| anyhow!("invalid symbol"))?;
-        let symbol = this
-            .update(&mut cx, |this, _| this.deserialize_symbol(symbol))?
-            .await?;
-        let symbol = this.update(&mut cx, |this, _| {
-            let signature = this.symbol_signature(&symbol.path);
-            if signature == symbol.signature {
-                Ok(symbol)
-            } else {
-                Err(anyhow!("invalid symbol signature"))
-            }
-        })??;
-        let buffer = this
-            .update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))?
-            .await?;
-
-        Ok(proto::OpenBufferForSymbolResponse {
-            buffer_id: this.update(&mut cx, |this, cx| {
-                this.create_buffer_for_peer(&buffer, peer_id, cx)
-            })?,
-        })
-    }
-
-    fn symbol_signature(&self, project_path: &ProjectPath) -> [u8; 32] {
-        let mut hasher = Sha256::new();
-        hasher.update(project_path.worktree_id.to_proto().to_be_bytes());
-        hasher.update(project_path.path.to_string_lossy().as_bytes());
-        hasher.update(self.nonce.to_be_bytes());
-        hasher.finalize().as_slice().try_into().unwrap()
-    }
-
-    async fn handle_open_buffer_by_id(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OpenBufferById>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OpenBufferResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let buffer = this
-            .update(&mut cx, |this, cx| {
-                this.open_buffer_by_id(envelope.payload.id, cx)
-            })?
-            .await?;
-        this.update(&mut cx, |this, cx| {
-            Ok(proto::OpenBufferResponse {
-                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx),
-            })
-        })?
-    }
-
-    async fn handle_open_buffer_by_path(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::OpenBufferByPath>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::OpenBufferResponse> {
-        let peer_id = envelope.original_sender_id()?;
-        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-        let open_buffer = this.update(&mut cx, |this, cx| {
-            this.open_buffer(
-                ProjectPath {
-                    worktree_id,
-                    path: PathBuf::from(envelope.payload.path).into(),
-                },
-                cx,
-            )
-        })?;
-
-        let buffer = open_buffer.await?;
-        this.update(&mut cx, |this, cx| {
-            Ok(proto::OpenBufferResponse {
-                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx),
-            })
-        })?
-    }
-
-    fn serialize_project_transaction_for_peer(
-        &mut self,
-        project_transaction: ProjectTransaction,
-        peer_id: proto::PeerId,
-        cx: &mut AppContext,
-    ) -> proto::ProjectTransaction {
-        let mut serialized_transaction = proto::ProjectTransaction {
-            buffer_ids: Default::default(),
-            transactions: Default::default(),
-        };
-        for (buffer, transaction) in project_transaction.0 {
-            serialized_transaction
-                .buffer_ids
-                .push(self.create_buffer_for_peer(&buffer, peer_id, cx));
-            serialized_transaction
-                .transactions
-                .push(language::proto::serialize_transaction(&transaction));
-        }
-        serialized_transaction
-    }
-
-    fn deserialize_project_transaction(
-        &mut self,
-        message: proto::ProjectTransaction,
-        push_to_history: bool,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<ProjectTransaction>> {
-        cx.spawn(move |this, mut cx| async move {
-            let mut project_transaction = ProjectTransaction::default();
-            for (buffer_id, transaction) in message.buffer_ids.into_iter().zip(message.transactions)
-            {
-                let buffer = this
-                    .update(&mut cx, |this, cx| {
-                        this.wait_for_remote_buffer(buffer_id, cx)
-                    })?
-                    .await?;
-                let transaction = language::proto::deserialize_transaction(transaction)?;
-                project_transaction.0.insert(buffer, transaction);
-            }
-
-            for (buffer, transaction) in &project_transaction.0 {
-                buffer
-                    .update(&mut cx, |buffer, _| {
-                        buffer.wait_for_edits(transaction.edit_ids.iter().copied())
-                    })?
-                    .await?;
-
-                if push_to_history {
-                    buffer.update(&mut cx, |buffer, _| {
-                        buffer.push_transaction(transaction.clone(), Instant::now());
-                    })?;
-                }
-            }
-
-            Ok(project_transaction)
-        })
-    }
-
-    fn create_buffer_for_peer(
-        &mut self,
-        buffer: &Model<Buffer>,
-        peer_id: proto::PeerId,
-        cx: &mut AppContext,
-    ) -> u64 {
-        let buffer_id = buffer.read(cx).remote_id();
-        if let Some(ProjectClientState::Local { updates_tx, .. }) = &self.client_state {
-            updates_tx
-                .unbounded_send(LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id })
-                .ok();
-        }
-        buffer_id
-    }
-
-    fn wait_for_remote_buffer(
-        &mut self,
-        id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let mut opened_buffer_rx = self.opened_buffer.1.clone();
-
-        cx.spawn(move |this, mut cx| async move {
-            let buffer = loop {
-                let Some(this) = this.upgrade() else {
-                    return Err(anyhow!("project dropped"));
-                };
-
-                let buffer = this.update(&mut cx, |this, _cx| {
-                    this.opened_buffers
-                        .get(&id)
-                        .and_then(|buffer| buffer.upgrade())
-                })?;
-
-                if let Some(buffer) = buffer {
-                    break buffer;
-                } else if this.update(&mut cx, |this, _| this.is_read_only())? {
-                    return Err(anyhow!("disconnected before buffer {} could be opened", id));
-                }
-
-                this.update(&mut cx, |this, _| {
-                    this.incomplete_remote_buffers.entry(id).or_default();
-                })?;
-                drop(this);
-
-                opened_buffer_rx
-                    .next()
-                    .await
-                    .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
-            };
-
-            Ok(buffer)
-        })
-    }
-
-    fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        let project_id = match self.client_state.as_ref() {
-            Some(ProjectClientState::Remote {
-                sharing_has_stopped,
-                remote_id,
-                ..
-            }) => {
-                if *sharing_has_stopped {
-                    return Task::ready(Err(anyhow!(
-                        "can't synchronize remote buffers on a readonly project"
-                    )));
-                } else {
-                    *remote_id
-                }
-            }
-            Some(ProjectClientState::Local { .. }) | None => {
-                return Task::ready(Err(anyhow!(
-                    "can't synchronize remote buffers on a local project"
-                )))
-            }
-        };
-
-        let client = self.client.clone();
-        cx.spawn(move |this, mut cx| async move {
-            let (buffers, incomplete_buffer_ids) = this.update(&mut cx, |this, cx| {
-                let buffers = this
-                    .opened_buffers
-                    .iter()
-                    .filter_map(|(id, buffer)| {
-                        let buffer = buffer.upgrade()?;
-                        Some(proto::BufferVersion {
-                            id: *id,
-                            version: language::proto::serialize_version(&buffer.read(cx).version),
-                        })
-                    })
-                    .collect();
-                let incomplete_buffer_ids = this
-                    .incomplete_remote_buffers
-                    .keys()
-                    .copied()
-                    .collect::<Vec<_>>();
-
-                (buffers, incomplete_buffer_ids)
-            })?;
-            let response = client
-                .request(proto::SynchronizeBuffers {
-                    project_id,
-                    buffers,
-                })
-                .await?;
-
-            let send_updates_for_buffers = this.update(&mut cx, |this, cx| {
-                response
-                    .buffers
-                    .into_iter()
-                    .map(|buffer| {
-                        let client = client.clone();
-                        let buffer_id = buffer.id;
-                        let remote_version = language::proto::deserialize_version(&buffer.version);
-                        if let Some(buffer) = this.buffer_for_id(buffer_id) {
-                            let operations =
-                                buffer.read(cx).serialize_ops(Some(remote_version), cx);
-                            cx.background_executor().spawn(async move {
-                                let operations = operations.await;
-                                for chunk in split_operations(operations) {
-                                    client
-                                        .request(proto::UpdateBuffer {
-                                            project_id,
-                                            buffer_id,
-                                            operations: chunk,
-                                        })
-                                        .await?;
-                                }
-                                anyhow::Ok(())
-                            })
-                        } else {
-                            Task::ready(Ok(()))
-                        }
-                    })
-                    .collect::<Vec<_>>()
-            })?;
-
-            // Any incomplete buffers have open requests waiting. Request that the host sends
-            // creates these buffers for us again to unblock any waiting futures.
-            for id in incomplete_buffer_ids {
-                cx.background_executor()
-                    .spawn(client.request(proto::OpenBufferById { project_id, id }))
-                    .detach();
-            }
-
-            futures::future::join_all(send_updates_for_buffers)
-                .await
-                .into_iter()
-                .collect()
-        })
-    }
-
-    pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
-        self.worktrees()
-            .map(|worktree| {
-                let worktree = worktree.read(cx);
-                proto::WorktreeMetadata {
-                    id: worktree.id().to_proto(),
-                    root_name: worktree.root_name().into(),
-                    visible: worktree.is_visible(),
-                    abs_path: worktree.abs_path().to_string_lossy().into(),
-                }
-            })
-            .collect()
-    }
-
-    fn set_worktrees_from_proto(
-        &mut self,
-        worktrees: Vec<proto::WorktreeMetadata>,
-        cx: &mut ModelContext<Project>,
-    ) -> Result<()> {
-        let replica_id = self.replica_id();
-        let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
-
-        let mut old_worktrees_by_id = self
-            .worktrees
-            .drain(..)
-            .filter_map(|worktree| {
-                let worktree = worktree.upgrade()?;
-                Some((worktree.read(cx).id(), worktree))
-            })
-            .collect::<HashMap<_, _>>();
-
-        for worktree in worktrees {
-            if let Some(old_worktree) =
-                old_worktrees_by_id.remove(&WorktreeId::from_proto(worktree.id))
-            {
-                self.worktrees.push(WorktreeHandle::Strong(old_worktree));
-            } else {
-                let worktree =
-                    Worktree::remote(remote_id, replica_id, worktree, self.client.clone(), cx);
-                let _ = self.add_worktree(&worktree, cx);
-            }
-        }
-
-        self.metadata_changed(cx);
-        for id in old_worktrees_by_id.keys() {
-            cx.emit(Event::WorktreeRemoved(*id));
-        }
-
-        Ok(())
-    }
-
-    fn set_collaborators_from_proto(
-        &mut self,
-        messages: Vec<proto::Collaborator>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        let mut collaborators = HashMap::default();
-        for message in messages {
-            let collaborator = Collaborator::from_proto(message)?;
-            collaborators.insert(collaborator.peer_id, collaborator);
-        }
-        for old_peer_id in self.collaborators.keys() {
-            if !collaborators.contains_key(old_peer_id) {
-                cx.emit(Event::CollaboratorLeft(*old_peer_id));
-            }
-        }
-        self.collaborators = collaborators;
-        Ok(())
-    }
-
-    fn deserialize_symbol(
-        &self,
-        serialized_symbol: proto::Symbol,
-    ) -> impl Future<Output = Result<Symbol>> {
-        let languages = self.languages.clone();
-        async move {
-            let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id);
-            let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id);
-            let start = serialized_symbol
-                .start
-                .ok_or_else(|| anyhow!("invalid start"))?;
-            let end = serialized_symbol
-                .end
-                .ok_or_else(|| anyhow!("invalid end"))?;
-            let kind = unsafe { mem::transmute(serialized_symbol.kind) };
-            let path = ProjectPath {
-                worktree_id,
-                path: PathBuf::from(serialized_symbol.path).into(),
-            };
-            let language = languages
-                .language_for_file(&path.path, None)
-                .await
-                .log_err();
-            Ok(Symbol {
-                language_server_name: LanguageServerName(
-                    serialized_symbol.language_server_name.into(),
-                ),
-                source_worktree_id,
-                path,
-                label: {
-                    match language {
-                        Some(language) => {
-                            language
-                                .label_for_symbol(&serialized_symbol.name, kind)
-                                .await
-                        }
-                        None => None,
-                    }
-                    .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None))
-                },
-
-                name: serialized_symbol.name,
-                range: Unclipped(PointUtf16::new(start.row, start.column))
-                    ..Unclipped(PointUtf16::new(end.row, end.column)),
-                kind,
-                signature: serialized_symbol
-                    .signature
-                    .try_into()
-                    .map_err(|_| anyhow!("invalid signature"))?,
-            })
-        }
-    }
-
-    async fn handle_buffer_saved(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::BufferSaved>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?;
-        let version = deserialize_version(&envelope.payload.version);
-        let mtime = envelope
-            .payload
-            .mtime
-            .ok_or_else(|| anyhow!("missing mtime"))?
-            .into();
-
-        this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&envelope.payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&envelope.payload.buffer_id)
-                        .and_then(|b| b.clone())
-                });
-            if let Some(buffer) = buffer {
-                buffer.update(cx, |buffer, cx| {
-                    buffer.did_save(version, fingerprint, mtime, cx);
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_buffer_reloaded(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::BufferReloaded>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let payload = envelope.payload;
-        let version = deserialize_version(&payload.version);
-        let fingerprint = deserialize_fingerprint(&payload.fingerprint)?;
-        let line_ending = deserialize_line_ending(
-            proto::LineEnding::from_i32(payload.line_ending)
-                .ok_or_else(|| anyhow!("missing line ending"))?,
-        );
-        let mtime = payload
-            .mtime
-            .ok_or_else(|| anyhow!("missing mtime"))?
-            .into();
-        this.update(&mut cx, |this, cx| {
-            let buffer = this
-                .opened_buffers
-                .get(&payload.buffer_id)
-                .and_then(|buffer| buffer.upgrade())
-                .or_else(|| {
-                    this.incomplete_remote_buffers
-                        .get(&payload.buffer_id)
-                        .cloned()
-                        .flatten()
-                });
-            if let Some(buffer) = buffer {
-                buffer.update(cx, |buffer, cx| {
-                    buffer.did_reload(version, fingerprint, line_ending, mtime, cx);
-                });
-            }
-            Ok(())
-        })?
-    }
-
-    #[allow(clippy::type_complexity)]
-    fn edits_from_lsp(
-        &mut self,
-        buffer: &Model<Buffer>,
-        lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
-        server_id: LanguageServerId,
-        version: Option<i32>,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<(Range<Anchor>, String)>>> {
-        let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx);
-        cx.background_executor().spawn(async move {
-            let snapshot = snapshot?;
-            let mut lsp_edits = lsp_edits
-                .into_iter()
-                .map(|edit| (range_from_lsp(edit.range), edit.new_text))
-                .collect::<Vec<_>>();
-            lsp_edits.sort_by_key(|(range, _)| range.start);
-
-            let mut lsp_edits = lsp_edits.into_iter().peekable();
-            let mut edits = Vec::new();
-            while let Some((range, mut new_text)) = lsp_edits.next() {
-                // Clip invalid ranges provided by the language server.
-                let mut range = snapshot.clip_point_utf16(range.start, Bias::Left)
-                    ..snapshot.clip_point_utf16(range.end, Bias::Left);
-
-                // Combine any LSP edits that are adjacent.
-                //
-                // Also, combine LSP edits that are separated from each other by only
-                // a newline. This is important because for some code actions,
-                // Rust-analyzer rewrites the entire buffer via a series of edits that
-                // are separated by unchanged newline characters.
-                //
-                // In order for the diffing logic below to work properly, any edits that
-                // cancel each other out must be combined into one.
-                while let Some((next_range, next_text)) = lsp_edits.peek() {
-                    if next_range.start.0 > range.end {
-                        if next_range.start.0.row > range.end.row + 1
-                            || next_range.start.0.column > 0
-                            || snapshot.clip_point_utf16(
-                                Unclipped(PointUtf16::new(range.end.row, u32::MAX)),
-                                Bias::Left,
-                            ) > range.end
-                        {
-                            break;
-                        }
-                        new_text.push('\n');
-                    }
-                    range.end = snapshot.clip_point_utf16(next_range.end, Bias::Left);
-                    new_text.push_str(next_text);
-                    lsp_edits.next();
-                }
-
-                // For multiline edits, perform a diff of the old and new text so that
-                // we can identify the changes more precisely, preserving the locations
-                // of any anchors positioned in the unchanged regions.
-                if range.end.row > range.start.row {
-                    let mut offset = range.start.to_offset(&snapshot);
-                    let old_text = snapshot.text_for_range(range).collect::<String>();
-
-                    let diff = TextDiff::from_lines(old_text.as_str(), &new_text);
-                    let mut moved_since_edit = true;
-                    for change in diff.iter_all_changes() {
-                        let tag = change.tag();
-                        let value = change.value();
-                        match tag {
-                            ChangeTag::Equal => {
-                                offset += value.len();
-                                moved_since_edit = true;
-                            }
-                            ChangeTag::Delete => {
-                                let start = snapshot.anchor_after(offset);
-                                let end = snapshot.anchor_before(offset + value.len());
-                                if moved_since_edit {
-                                    edits.push((start..end, String::new()));
-                                } else {
-                                    edits.last_mut().unwrap().0.end = end;
-                                }
-                                offset += value.len();
-                                moved_since_edit = false;
-                            }
-                            ChangeTag::Insert => {
-                                if moved_since_edit {
-                                    let anchor = snapshot.anchor_after(offset);
-                                    edits.push((anchor..anchor, value.to_string()));
-                                } else {
-                                    edits.last_mut().unwrap().1.push_str(value);
-                                }
-                                moved_since_edit = false;
-                            }
-                        }
-                    }
-                } else if range.end == range.start {
-                    let anchor = snapshot.anchor_after(range.start);
-                    edits.push((anchor..anchor, new_text));
-                } else {
-                    let edit_start = snapshot.anchor_after(range.start);
-                    let edit_end = snapshot.anchor_before(range.end);
-                    edits.push((edit_start..edit_end, new_text));
-                }
-            }
-
-            Ok(edits)
-        })
-    }
-
-    fn buffer_snapshot_for_lsp_version(
-        &mut self,
-        buffer: &Model<Buffer>,
-        server_id: LanguageServerId,
-        version: Option<i32>,
-        cx: &AppContext,
-    ) -> Result<TextBufferSnapshot> {
-        const OLD_VERSIONS_TO_RETAIN: i32 = 10;
-
-        if let Some(version) = version {
-            let buffer_id = buffer.read(cx).remote_id();
-            let snapshots = self
-                .buffer_snapshots
-                .get_mut(&buffer_id)
-                .and_then(|m| m.get_mut(&server_id))
-                .ok_or_else(|| {
-                    anyhow!("no snapshots found for buffer {buffer_id} and server {server_id}")
-                })?;
-
-            let found_snapshot = snapshots
-                .binary_search_by_key(&version, |e| e.version)
-                .map(|ix| snapshots[ix].snapshot.clone())
-                .map_err(|_| {
-                    anyhow!("snapshot not found for buffer {buffer_id} server {server_id} at version {version}")
-                })?;
-
-            snapshots.retain(|snapshot| snapshot.version + OLD_VERSIONS_TO_RETAIN >= version);
-            Ok(found_snapshot)
-        } else {
-            Ok((buffer.read(cx)).text_snapshot())
-        }
-    }
-
-    pub fn language_servers(
-        &self,
-    ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
-        self.language_server_ids
-            .iter()
-            .map(|((worktree_id, server_name), server_id)| {
-                (*server_id, server_name.clone(), *worktree_id)
-            })
-    }
-
-    pub fn supplementary_language_servers(
-        &self,
-    ) -> impl '_
-           + Iterator<
-        Item = (
-            &LanguageServerId,
-            &(LanguageServerName, Arc<LanguageServer>),
-        ),
-    > {
-        self.supplementary_language_servers.iter()
-    }
-
-    pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
-        if let Some(LanguageServerState::Running { server, .. }) = self.language_servers.get(&id) {
-            Some(server.clone())
-        } else if let Some((_, server)) = self.supplementary_language_servers.get(&id) {
-            Some(Arc::clone(server))
-        } else {
-            None
-        }
-    }
-
-    pub fn language_servers_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> impl Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_server_ids_for_buffer(buffer, cx)
-            .into_iter()
-            .filter_map(|server_id| match self.language_servers.get(&server_id)? {
-                LanguageServerState::Running {
-                    adapter, server, ..
-                } => Some((adapter, server)),
-                _ => None,
-            })
-    }
-
-    fn primary_language_server_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_for_buffer(buffer, cx).next()
-    }
-
-    pub fn language_server_for_buffer(
-        &self,
-        buffer: &Buffer,
-        server_id: LanguageServerId,
-        cx: &AppContext,
-    ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_for_buffer(buffer, cx)
-            .find(|(_, s)| s.server_id() == server_id)
-    }
-
-    fn language_server_ids_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> Vec<LanguageServerId> {
-        if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
-            let worktree_id = file.worktree_id(cx);
-            language
-                .lsp_adapters()
-                .iter()
-                .flat_map(|adapter| {
-                    let key = (worktree_id, adapter.name.clone());
-                    self.language_server_ids.get(&key).copied()
-                })
-                .collect()
-        } else {
-            Vec::new()
-        }
-    }
-}
-
-fn subscribe_for_copilot_events(
-    copilot: &Model<Copilot>,
-    cx: &mut ModelContext<'_, Project>,
-) -> gpui::Subscription {
-    cx.subscribe(
-        copilot,
-        |project, copilot, copilot_event, cx| match copilot_event {
-            copilot::Event::CopilotLanguageServerStarted => {
-                match copilot.read(cx).language_server() {
-                    Some((name, copilot_server)) => {
-                        // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
-                        if !copilot_server.has_notification_handler::<copilot::request::LogMessage>() {
-                            let new_server_id = copilot_server.server_id();
-                            let weak_project = cx.weak_model();
-                            let copilot_log_subscription = copilot_server
-                                .on_notification::<copilot::request::LogMessage, _>(
-                                    move |params, mut cx| {
-                                        weak_project.update(&mut cx, |_, cx| {
-                                            cx.emit(Event::LanguageServerLog(
-                                                new_server_id,
-                                                params.message,
-                                            ));
-                                        }).ok();
-                                    },
-                                );
-                            project.supplementary_language_servers.insert(new_server_id, (name.clone(), Arc::clone(copilot_server)));
-                            project.copilot_log_subscription = Some(copilot_log_subscription);
-                            cx.emit(Event::LanguageServerAdded(new_server_id));
-                        }
-                    }
-                    None => debug_panic!("Received Copilot language server started event, but no language server is running"),
-                }
-            }
-        },
-    )
-}
-
-fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str {
-    let mut literal_end = 0;
-    for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
-        if part.contains(&['*', '?', '{', '}']) {
-            break;
-        } else {
-            if i > 0 {
-                // Acount for separator prior to this part
-                literal_end += path::MAIN_SEPARATOR.len_utf8();
-            }
-            literal_end += part.len();
-        }
-    }
-    &glob[..literal_end]
-}
-
-impl WorktreeHandle {
-    pub fn upgrade(&self) -> Option<Model<Worktree>> {
-        match self {
-            WorktreeHandle::Strong(handle) => Some(handle.clone()),
-            WorktreeHandle::Weak(handle) => handle.upgrade(),
-        }
-    }
-
-    pub fn handle_id(&self) -> usize {
-        match self {
-            WorktreeHandle::Strong(handle) => handle.entity_id().as_u64() as usize,
-            WorktreeHandle::Weak(handle) => handle.entity_id().as_u64() as usize,
-        }
-    }
-}
-
-impl OpenBuffer {
-    pub fn upgrade(&self) -> Option<Model<Buffer>> {
-        match self {
-            OpenBuffer::Strong(handle) => Some(handle.clone()),
-            OpenBuffer::Weak(handle) => handle.upgrade(),
-            OpenBuffer::Operations(_) => None,
-        }
-    }
-}
-
-pub struct PathMatchCandidateSet {
-    pub snapshot: Snapshot,
-    pub include_ignored: bool,
-    pub include_root_name: bool,
-}
-
-impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet {
-    type Candidates = PathMatchCandidateSetIter<'a>;
-
-    fn id(&self) -> usize {
-        self.snapshot.id().to_usize()
-    }
-
-    fn len(&self) -> usize {
-        if self.include_ignored {
-            self.snapshot.file_count()
-        } else {
-            self.snapshot.visible_file_count()
-        }
-    }
-
-    fn prefix(&self) -> Arc<str> {
-        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
-            self.snapshot.root_name().into()
-        } else if self.include_root_name {
-            format!("{}/", self.snapshot.root_name()).into()
-        } else {
-            "".into()
-        }
-    }
-
-    fn candidates(&'a self, start: usize) -> Self::Candidates {
-        PathMatchCandidateSetIter {
-            traversal: self.snapshot.files(self.include_ignored, start),
-        }
-    }
-}
-
-pub struct PathMatchCandidateSetIter<'a> {
-    traversal: Traversal<'a>,
-}
-
-impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
-    type Item = fuzzy::PathMatchCandidate<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.traversal.next().map(|entry| {
-            if let EntryKind::File(char_bag) = entry.kind {
-                fuzzy::PathMatchCandidate {
-                    path: &entry.path,
-                    char_bag,
-                }
-            } else {
-                unreachable!()
-            }
-        })
-    }
-}
-
-impl EventEmitter<Event> for Project {}
-
-impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
-    fn from((worktree_id, path): (WorktreeId, P)) -> Self {
-        Self {
-            worktree_id,
-            path: path.as_ref().into(),
-        }
-    }
-}
-
-impl ProjectLspAdapterDelegate {
-    fn new(project: &Project, cx: &ModelContext<Project>) -> Arc<Self> {
-        Arc::new(Self {
-            project: cx.handle(),
-            http_client: project.client.http_client(),
-        })
-    }
-}
-
-impl LspAdapterDelegate for ProjectLspAdapterDelegate {
-    fn show_notification(&self, message: &str, cx: &mut AppContext) {
-        self.project
-            .update(cx, |_, cx| cx.emit(Event::Notification(message.to_owned())));
-    }
-
-    fn http_client(&self) -> Arc<dyn HttpClient> {
-        self.http_client.clone()
-    }
-}
-
-fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
-    proto::Symbol {
-        language_server_name: symbol.language_server_name.0.to_string(),
-        source_worktree_id: symbol.source_worktree_id.to_proto(),
-        worktree_id: symbol.path.worktree_id.to_proto(),
-        path: symbol.path.path.to_string_lossy().to_string(),
-        name: symbol.name.clone(),
-        kind: unsafe { mem::transmute(symbol.kind) },
-        start: Some(proto::PointUtf16 {
-            row: symbol.range.start.0.row,
-            column: symbol.range.start.0.column,
-        }),
-        end: Some(proto::PointUtf16 {
-            row: symbol.range.end.0.row,
-            column: symbol.range.end.0.column,
-        }),
-        signature: symbol.signature.to_vec(),
-    }
-}
-
-fn relativize_path(base: &Path, path: &Path) -> PathBuf {
-    let mut path_components = path.components();
-    let mut base_components = base.components();
-    let mut components: Vec<Component> = Vec::new();
-    loop {
-        match (path_components.next(), base_components.next()) {
-            (None, None) => break,
-            (Some(a), None) => {
-                components.push(a);
-                components.extend(path_components.by_ref());
-                break;
-            }
-            (None, _) => components.push(Component::ParentDir),
-            (Some(a), Some(b)) if components.is_empty() && a == b => (),
-            (Some(a), Some(b)) if b == Component::CurDir => components.push(a),
-            (Some(a), Some(_)) => {
-                components.push(Component::ParentDir);
-                for _ in base_components {
-                    components.push(Component::ParentDir);
-                }
-                components.push(a);
-                components.extend(path_components.by_ref());
-                break;
-            }
-        }
-    }
-    components.iter().map(|c| c.as_os_str()).collect()
-}
-
-impl Item for Buffer {
-    fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
-        File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx))
-    }
-
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-        File::from_dyn(self.file()).map(|file| ProjectPath {
-            worktree_id: file.worktree_id(cx),
-            path: file.path().clone(),
-        })
-    }
-}
-
-async fn wait_for_loading_buffer(
-    mut receiver: postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
-) -> Result<Model<Buffer>, Arc<anyhow::Error>> {
-    loop {
-        if let Some(result) = receiver.borrow().as_ref() {
-            match result {
-                Ok(buffer) => return Ok(buffer.to_owned()),
-                Err(e) => return Err(e.to_owned()),
-            }
-        }
-        receiver.next().await;
-    }
-}
-
-fn include_text(server: &lsp::LanguageServer) -> bool {
-    server
-        .capabilities()
-        .text_document_sync
-        .as_ref()
-        .and_then(|sync| match sync {
-            lsp::TextDocumentSyncCapability::Kind(_) => None,
-            lsp::TextDocumentSyncCapability::Options(options) => options.save.as_ref(),
-        })
-        .and_then(|save_options| match save_options {
-            lsp::TextDocumentSyncSaveOptions::Supported(_) => None,
-            lsp::TextDocumentSyncSaveOptions::SaveOptions(options) => options.include_text,
-        })
-        .unwrap_or(false)
-}

crates/project2/src/project_settings.rs 🔗

@@ -1,50 +0,0 @@
-use collections::HashMap;
-use gpui::AppContext;
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use std::sync::Arc;
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ProjectSettings {
-    #[serde(default)]
-    pub lsp: HashMap<Arc<str>, LspSettings>,
-    #[serde(default)]
-    pub git: GitSettings,
-    #[serde(default)]
-    pub file_scan_exclusions: Option<Vec<String>>,
-}
-
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct GitSettings {
-    pub git_gutter: Option<GitGutterSetting>,
-    pub gutter_debounce: Option<u64>,
-}
-
-#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum GitGutterSetting {
-    #[default]
-    TrackedFiles,
-    Hide,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct LspSettings {
-    pub initialization_options: Option<serde_json::Value>,
-}
-
-impl Settings for ProjectSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = Self;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/project2/src/project_tests.rs 🔗

@@ -1,4317 +0,0 @@
-use crate::{Event, *};
-use fs::FakeFs;
-use futures::{future, StreamExt};
-use gpui::AppContext;
-use language::{
-    language_settings::{AllLanguageSettings, LanguageSettingsContent},
-    tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
-    LineEnding, OffsetRangeExt, Point, ToPoint,
-};
-use lsp::Url;
-use parking_lot::Mutex;
-use pretty_assertions::assert_eq;
-use serde_json::json;
-use std::{os, task::Poll};
-use unindent::Unindent as _;
-use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
-
-#[gpui::test]
-async fn test_block_via_channel(cx: &mut gpui::TestAppContext) {
-    cx.executor().allow_parking();
-
-    let (tx, mut rx) = futures::channel::mpsc::unbounded();
-    let _thread = std::thread::spawn(move || {
-        std::fs::metadata("/Users").unwrap();
-        std::thread::sleep(Duration::from_millis(1000));
-        tx.unbounded_send(1).unwrap();
-    });
-    rx.next().await.unwrap();
-}
-
-#[gpui::test]
-async fn test_block_via_smol(cx: &mut gpui::TestAppContext) {
-    cx.executor().allow_parking();
-
-    let io_task = smol::unblock(move || {
-        println!("sleeping on thread {:?}", std::thread::current().id());
-        std::thread::sleep(Duration::from_millis(10));
-        1
-    });
-
-    let task = cx.foreground_executor().spawn(async move {
-        io_task.await;
-    });
-
-    task.await;
-}
-
-#[gpui::test]
-async fn test_symlinks(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-
-    let dir = temp_tree(json!({
-        "root": {
-            "apple": "",
-            "banana": {
-                "carrot": {
-                    "date": "",
-                    "endive": "",
-                }
-            },
-            "fennel": {
-                "grape": "",
-            }
-        }
-    }));
-
-    let root_link_path = dir.path().join("root_link");
-    os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
-    os::unix::fs::symlink(
-        &dir.path().join("root/fennel"),
-        &dir.path().join("root/finnochio"),
-    )
-    .unwrap();
-
-    let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
-
-    project.update(cx, |project, cx| {
-        let tree = project.worktrees().next().unwrap().read(cx);
-        assert_eq!(tree.file_count(), 5);
-        assert_eq!(
-            tree.inode_for_path("fennel/grape"),
-            tree.inode_for_path("finnochio/grape")
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            ".zed": {
-                "settings.json": r#"{ "tab_size": 8 }"#
-            },
-            "a": {
-                "a.rs": "fn a() {\n    A\n}"
-            },
-            "b": {
-                ".zed": {
-                    "settings.json": r#"{ "tab_size": 2 }"#
-                },
-                "b.rs": "fn b() {\n  B\n}"
-            }
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-
-    cx.executor().run_until_parked();
-    cx.update(|cx| {
-        let tree = worktree.read(cx);
-
-        let settings_a = language_settings(
-            None,
-            Some(
-                &(File::for_entry(
-                    tree.entry_for_path("a/a.rs").unwrap().clone(),
-                    worktree.clone(),
-                ) as _),
-            ),
-            cx,
-        );
-        let settings_b = language_settings(
-            None,
-            Some(
-                &(File::for_entry(
-                    tree.entry_for_path("b/b.rs").unwrap().clone(),
-                    worktree.clone(),
-                ) as _),
-            ),
-            cx,
-        );
-
-        assert_eq!(settings_a.tab_size.get(), 8);
-        assert_eq!(settings_b.tab_size.get(), 2);
-    });
-}
-
-#[gpui::test]
-async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut rust_language = Language::new(
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_rust::language()),
-    );
-    let mut json_language = Language::new(
-        LanguageConfig {
-            name: "JSON".into(),
-            path_suffixes: vec!["json".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_rust_servers = rust_language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-rust-language-server",
-            capabilities: lsp::ServerCapabilities {
-                completion_provider: Some(lsp::CompletionOptions {
-                    trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-    let mut fake_json_servers = json_language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-json-language-server",
-            capabilities: lsp::ServerCapabilities {
-                completion_provider: Some(lsp::CompletionOptions {
-                    trigger_characters: Some(vec![":".to_string()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            "test.rs": "const A: i32 = 1;",
-            "test2.rs": "",
-            "Cargo.toml": "a = 1",
-            "package.json": "{\"a\": 1}",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-
-    // Open a buffer without an associated language server.
-    let toml_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/Cargo.toml", cx)
-        })
-        .await
-        .unwrap();
-
-    // Open a buffer with an associated language server before the language for it has been loaded.
-    let rust_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/test.rs", cx)
-        })
-        .await
-        .unwrap();
-    rust_buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.language().map(|l| l.name()), None);
-    });
-
-    // Now we add the languages to the project, and ensure they get assigned to all
-    // the relevant open buffers.
-    project.update(cx, |project, _| {
-        project.languages.add(Arc::new(json_language));
-        project.languages.add(Arc::new(rust_language));
-    });
-    cx.executor().run_until_parked();
-    rust_buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
-    });
-
-    // A server is started up, and it is notified about Rust files.
-    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
-            version: 0,
-            text: "const A: i32 = 1;".to_string(),
-            language_id: Default::default()
-        }
-    );
-
-    // The buffer is configured based on the language server's capabilities.
-    rust_buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer.completion_triggers(),
-            &[".".to_string(), "::".to_string()]
-        );
-    });
-    toml_buffer.update(cx, |buffer, _| {
-        assert!(buffer.completion_triggers().is_empty());
-    });
-
-    // Edit a buffer. The changes are reported to the language server.
-    rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx));
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidChangeTextDocument>()
-            .await
-            .text_document,
-        lsp::VersionedTextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
-            1
-        )
-    );
-
-    // Open a third buffer with a different associated language server.
-    let json_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/package.json", cx)
-        })
-        .await
-        .unwrap();
-
-    // A json language server is started up and is only notified about the json buffer.
-    let mut fake_json_server = fake_json_servers.next().await.unwrap();
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
-            version: 0,
-            text: "{\"a\": 1}".to_string(),
-            language_id: Default::default()
-        }
-    );
-
-    // This buffer is configured based on the second language server's
-    // capabilities.
-    json_buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
-    });
-
-    // When opening another buffer whose language server is already running,
-    // it is also configured based on the existing language server's capabilities.
-    let rust_buffer2 = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/test2.rs", cx)
-        })
-        .await
-        .unwrap();
-    rust_buffer2.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer.completion_triggers(),
-            &[".".to_string(), "::".to_string()]
-        );
-    });
-
-    // Changes are reported only to servers matching the buffer's language.
-    toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx));
-    rust_buffer2.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "let x = 1;")], None, cx)
-    });
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidChangeTextDocument>()
-            .await
-            .text_document,
-        lsp::VersionedTextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/test2.rs").unwrap(),
-            1
-        )
-    );
-
-    // Save notifications are reported to all servers.
-    project
-        .update(cx, |project, cx| project.save_buffer(toml_buffer, cx))
-        .await
-        .unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidSaveTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
-    );
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidSaveTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
-    );
-
-    // Renames are reported only to servers matching the buffer's language.
-    fs.rename(
-        Path::new("/the-root/test2.rs"),
-        Path::new("/the-root/test3.rs"),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidCloseTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test2.rs").unwrap()),
-    );
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
-            version: 0,
-            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
-            language_id: Default::default()
-        },
-    );
-
-    rust_buffer2.update(cx, |buffer, cx| {
-        buffer.update_diagnostics(
-            LanguageServerId(0),
-            DiagnosticSet::from_sorted_entries(
-                vec![DiagnosticEntry {
-                    diagnostic: Default::default(),
-                    range: Anchor::MIN..Anchor::MAX,
-                }],
-                &buffer.snapshot(),
-            ),
-            cx,
-        );
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
-                .count(),
-            1
-        );
-    });
-
-    // When the rename changes the extension of the file, the buffer gets closed on the old
-    // language server and gets opened on the new one.
-    fs.rename(
-        Path::new("/the-root/test3.rs"),
-        Path::new("/the-root/test3.json"),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidCloseTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),),
-    );
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
-            version: 0,
-            text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
-            language_id: Default::default()
-        },
-    );
-
-    // We clear the diagnostics, since the language has changed.
-    rust_buffer2.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
-                .count(),
-            0
-        );
-    });
-
-    // The renamed file's version resets after changing language server.
-    rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx));
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidChangeTextDocument>()
-            .await
-            .text_document,
-        lsp::VersionedTextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
-            1
-        )
-    );
-
-    // Restart language servers
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers(
-            vec![rust_buffer.clone(), json_buffer.clone()],
-            cx,
-        );
-    });
-
-    let mut rust_shutdown_requests = fake_rust_server
-        .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
-    let mut json_shutdown_requests = fake_json_server
-        .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
-    futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
-
-    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
-    let mut fake_json_server = fake_json_servers.next().await.unwrap();
-
-    // Ensure rust document is reopened in new rust language server
-    assert_eq!(
-        fake_rust_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document,
-        lsp::TextDocumentItem {
-            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
-            version: 0,
-            text: rust_buffer.update(cx, |buffer, _| buffer.text()),
-            language_id: Default::default()
-        }
-    );
-
-    // Ensure json documents are reopened in new json language server
-    assert_set_eq!(
-        [
-            fake_json_server
-                .receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await
-                .text_document,
-            fake_json_server
-                .receive_notification::<lsp::notification::DidOpenTextDocument>()
-                .await
-                .text_document,
-        ],
-        [
-            lsp::TextDocumentItem {
-                uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
-                version: 0,
-                text: json_buffer.update(cx, |buffer, _| buffer.text()),
-                language_id: Default::default()
-            },
-            lsp::TextDocumentItem {
-                uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
-                version: 0,
-                text: rust_buffer2.update(cx, |buffer, _| buffer.text()),
-                language_id: Default::default()
-            }
-        ]
-    );
-
-    // Close notifications are reported only to servers matching the buffer's language.
-    cx.update(|_| drop(json_buffer));
-    let close_message = lsp::DidCloseTextDocumentParams {
-        text_document: lsp::TextDocumentIdentifier::new(
-            lsp::Url::from_file_path("/the-root/package.json").unwrap(),
-        ),
-    };
-    assert_eq!(
-        fake_json_server
-            .receive_notification::<lsp::notification::DidCloseTextDocument>()
-            .await,
-        close_message,
-    );
-}
-
-#[gpui::test]
-async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    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_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-language-server",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-root",
-        json!({
-            ".gitignore": "target\n",
-            "src": {
-                "a.rs": "",
-                "b.rs": "",
-            },
-            "target": {
-                "x": {
-                    "out": {
-                        "x.rs": ""
-                    }
-                },
-                "y": {
-                    "out": {
-                        "y.rs": "",
-                    }
-                },
-                "z": {
-                    "out": {
-                        "z.rs": ""
-                    }
-                }
-            }
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
-    project.update(cx, |project, _| {
-        project.languages.add(Arc::new(language));
-    });
-    cx.executor().run_until_parked();
-
-    // Start the language server by opening a buffer with a compatible file extension.
-    let _buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/the-root/src/a.rs", cx)
-        })
-        .await
-        .unwrap();
-
-    // Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
-    project.update(cx, |project, cx| {
-        let worktree = project.worktrees().next().unwrap();
-        assert_eq!(
-            worktree
-                .read(cx)
-                .snapshot()
-                .entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-                (Path::new("target"), true),
-            ]
-        );
-    });
-
-    let prev_read_dir_count = fs.read_dir_call_count();
-
-    // Keep track of the FS events reported to the language server.
-    let fake_server = fake_servers.next().await.unwrap();
-    let file_changes = Arc::new(Mutex::new(Vec::new()));
-    fake_server
-        .request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
-            registrations: vec![lsp::Registration {
-                id: Default::default(),
-                method: "workspace/didChangeWatchedFiles".to_string(),
-                register_options: serde_json::to_value(
-                    lsp::DidChangeWatchedFilesRegistrationOptions {
-                        watchers: vec![
-                            lsp::FileSystemWatcher {
-                                glob_pattern: lsp::GlobPattern::String(
-                                    "/the-root/Cargo.toml".to_string(),
-                                ),
-                                kind: None,
-                            },
-                            lsp::FileSystemWatcher {
-                                glob_pattern: lsp::GlobPattern::String(
-                                    "/the-root/src/*.{rs,c}".to_string(),
-                                ),
-                                kind: None,
-                            },
-                            lsp::FileSystemWatcher {
-                                glob_pattern: lsp::GlobPattern::String(
-                                    "/the-root/target/y/**/*.rs".to_string(),
-                                ),
-                                kind: None,
-                            },
-                        ],
-                    },
-                )
-                .ok(),
-            }],
-        })
-        .await
-        .unwrap();
-    fake_server.handle_notification::<lsp::notification::DidChangeWatchedFiles, _>({
-        let file_changes = file_changes.clone();
-        move |params, _| {
-            let mut file_changes = file_changes.lock();
-            file_changes.extend(params.changes);
-            file_changes.sort_by(|a, b| a.uri.cmp(&b.uri));
-        }
-    });
-
-    cx.executor().run_until_parked();
-    assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
-    assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4);
-
-    // Now the language server has asked us to watch an ignored directory path,
-    // so we recursively load it.
-    project.update(cx, |project, cx| {
-        let worktree = project.worktrees().next().unwrap();
-        assert_eq!(
-            worktree
-                .read(cx)
-                .snapshot()
-                .entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-                (Path::new("target"), true),
-                (Path::new("target/x"), true),
-                (Path::new("target/y"), true),
-                (Path::new("target/y/out"), true),
-                (Path::new("target/y/out/y.rs"), true),
-                (Path::new("target/z"), true),
-            ]
-        );
-    });
-
-    // Perform some file system mutations, two of which match the watched patterns,
-    // and one of which does not.
-    fs.create_file("/the-root/src/c.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.create_file("/the-root/src/d.txt".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-    fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default())
-        .await
-        .unwrap();
-
-    // The language server receives events for the FS mutations that match its watch patterns.
-    cx.executor().run_until_parked();
-    assert_eq!(
-        &*file_changes.lock(),
-        &[
-            lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/src/b.rs").unwrap(),
-                typ: lsp::FileChangeType::DELETED,
-            },
-            lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/src/c.rs").unwrap(),
-                typ: lsp::FileChangeType::CREATED,
-            },
-            lsp::FileEvent {
-                uri: lsp::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(),
-                typ: lsp::FileChangeType::CREATED,
-            },
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": "let a = 1;",
-            "b.rs": "let b = 2;"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await;
-
-    let buffer_a = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-    let buffer_b = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
-        .await
-        .unwrap();
-
-    project.update(cx, |project, cx| {
-        project
-            .update_diagnostics(
-                LanguageServerId(0),
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/dir/a.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
-                        severity: Some(lsp::DiagnosticSeverity::ERROR),
-                        message: "error 1".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-        project
-            .update_diagnostics(
-                LanguageServerId(0),
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/dir/b.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
-                        severity: Some(lsp::DiagnosticSeverity::WARNING),
-                        message: "error 2".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-    });
-
-    buffer_a.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let ", None),
-                ("a", Some(DiagnosticSeverity::ERROR)),
-                (" = 1;", None),
-            ]
-        );
-    });
-    buffer_b.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let ", None),
-                ("b", Some(DiagnosticSeverity::WARNING)),
-                (" = 2;", None),
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "dir": {
-                ".git": {
-                    "HEAD": "ref: refs/heads/main",
-                },
-                ".gitignore": "b.rs",
-                "a.rs": "let a = 1;",
-                "b.rs": "let b = 2;",
-            },
-            "other.rs": "let b = c;"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
-    let (worktree, _) = project
-        .update(cx, |project, cx| {
-            project.find_or_create_local_worktree("/root/dir", true, cx)
-        })
-        .await
-        .unwrap();
-    let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
-
-    let (worktree, _) = project
-        .update(cx, |project, cx| {
-            project.find_or_create_local_worktree("/root/other.rs", false, cx)
-        })
-        .await
-        .unwrap();
-    let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
-
-    let server_id = LanguageServerId(0);
-    project.update(cx, |project, cx| {
-        project
-            .update_diagnostics(
-                server_id,
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
-                        severity: Some(lsp::DiagnosticSeverity::ERROR),
-                        message: "unused variable 'b'".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-        project
-            .update_diagnostics(
-                server_id,
-                lsp::PublishDiagnosticsParams {
-                    uri: Url::from_file_path("/root/other.rs").unwrap(),
-                    version: None,
-                    diagnostics: vec![lsp::Diagnostic {
-                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 9)),
-                        severity: Some(lsp::DiagnosticSeverity::ERROR),
-                        message: "unknown variable 'c'".to_string(),
-                        ..Default::default()
-                    }],
-                },
-                &[],
-                cx,
-            )
-            .unwrap();
-    });
-
-    let main_ignored_buffer = project
-        .update(cx, |project, cx| {
-            project.open_buffer((main_worktree_id, "b.rs"), cx)
-        })
-        .await
-        .unwrap();
-    main_ignored_buffer.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let ", None),
-                ("b", Some(DiagnosticSeverity::ERROR)),
-                (" = 2;", None),
-            ],
-            "Gigitnored buffers should still get in-buffer diagnostics",
-        );
-    });
-    let other_buffer = project
-        .update(cx, |project, cx| {
-            project.open_buffer((other_worktree_id, ""), cx)
-        })
-        .await
-        .unwrap();
-    other_buffer.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let b = ", None),
-                ("c", Some(DiagnosticSeverity::ERROR)),
-                (";", None),
-            ],
-            "Buffers from hidden projects should still get in-buffer diagnostics"
-        );
-    });
-
-    project.update(cx, |project, cx| {
-        assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
-        assert_eq!(
-            project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
-            vec![(
-                ProjectPath {
-                    worktree_id: main_worktree_id,
-                    path: Arc::from(Path::new("b.rs")),
-                },
-                server_id,
-                DiagnosticSummary {
-                    error_count: 1,
-                    warning_count: 0,
-                }
-            )]
-        );
-        assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
-        assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
-    });
-}
-
-#[gpui::test]
-async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let progress_token = "the-progress-token";
-    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_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            disk_based_diagnostics_progress_token: Some(progress_token.into()),
-            disk_based_diagnostics_sources: vec!["disk".into()],
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": "fn a() { A }",
-            "b.rs": "const y: i32 = 1",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let worktree_id = project.update(cx, |p, cx| p.worktrees().next().unwrap().read(cx).id());
-
-    // Cause worktree to start the fake language server
-    let _buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
-        .await
-        .unwrap();
-
-    let mut events = cx.events(&project);
-
-    let fake_server = fake_servers.next().await.unwrap();
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::LanguageServerAdded(LanguageServerId(0)),
-    );
-
-    fake_server
-        .start_progress(format!("{}/0", progress_token))
-        .await;
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsStarted {
-            language_server_id: LanguageServerId(0),
-        }
-    );
-
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: vec![lsp::Diagnostic {
-            range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-            severity: Some(lsp::DiagnosticSeverity::ERROR),
-            message: "undefined variable 'A'".to_string(),
-            ..Default::default()
-        }],
-    });
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiagnosticsUpdated {
-            language_server_id: LanguageServerId(0),
-            path: (worktree_id, Path::new("a.rs")).into()
-        }
-    );
-
-    fake_server.end_progress(format!("{}/0", progress_token));
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsFinished {
-            language_server_id: LanguageServerId(0)
-        }
-    );
-
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, _| {
-        let snapshot = buffer.snapshot();
-        let diagnostics = snapshot
-            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-            .collect::<Vec<_>>();
-        assert_eq!(
-            diagnostics,
-            &[DiagnosticEntry {
-                range: Point::new(0, 9)..Point::new(0, 10),
-                diagnostic: Diagnostic {
-                    severity: lsp::DiagnosticSeverity::ERROR,
-                    message: "undefined variable 'A'".to_string(),
-                    group_id: 0,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            }]
-        )
-    });
-
-    // Ensure publishing empty diagnostics twice only results in one update event.
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: Default::default(),
-    });
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiagnosticsUpdated {
-            language_server_id: LanguageServerId(0),
-            path: (worktree_id, Path::new("a.rs")).into()
-        }
-    );
-
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: Default::default(),
-    });
-    cx.executor().run_until_parked();
-    assert_eq!(futures::poll!(events.next()), Poll::Pending);
-}
-
-#[gpui::test]
-async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let progress_token = "the-progress-token";
-    let mut language = Language::new(
-        LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            disk_based_diagnostics_sources: vec!["disk".into()],
-            disk_based_diagnostics_progress_token: Some(progress_token.into()),
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Simulate diagnostics starting to update.
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.start_progress(progress_token).await;
-
-    // Restart the server before the diagnostics finish updating.
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer], cx);
-    });
-    let mut events = cx.events(&project);
-
-    // Simulate the newly started server sending more diagnostics.
-    let fake_server = fake_servers.next().await.unwrap();
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::LanguageServerAdded(LanguageServerId(1))
-    );
-    fake_server.start_progress(progress_token).await;
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsStarted {
-            language_server_id: LanguageServerId(1)
-        }
-    );
-    project.update(cx, |project, _| {
-        assert_eq!(
-            project
-                .language_servers_running_disk_based_diagnostics()
-                .collect::<Vec<_>>(),
-            [LanguageServerId(1)]
-        );
-    });
-
-    // All diagnostics are considered done, despite the old server's diagnostic
-    // task never completing.
-    fake_server.end_progress(progress_token);
-    assert_eq!(
-        events.next().await.unwrap(),
-        Event::DiskBasedDiagnosticsFinished {
-            language_server_id: LanguageServerId(1)
-        }
-    );
-    project.update(cx, |project, _| {
-        assert_eq!(
-            project
-                .language_servers_running_disk_based_diagnostics()
-                .collect::<Vec<_>>(),
-            [LanguageServerId(0); 0]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Publish diagnostics
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: Url::from_file_path("/dir/a.rs").unwrap(),
-        version: None,
-        diagnostics: vec![lsp::Diagnostic {
-            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
-            severity: Some(lsp::DiagnosticSeverity::ERROR),
-            message: "the message".to_string(),
-            ..Default::default()
-        }],
-    });
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..1, false)
-                .map(|entry| entry.diagnostic.message.clone())
-                .collect::<Vec<_>>(),
-            ["the message".to_string()]
-        );
-    });
-    project.update(cx, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summary(false, cx),
-            DiagnosticSummary {
-                error_count: 1,
-                warning_count: 0,
-            }
-        );
-    });
-
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx);
-    });
-
-    // The diagnostics are cleared.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, usize>(0..1, false)
-                .map(|entry| entry.diagnostic.message.clone())
-                .collect::<Vec<_>>(),
-            Vec::<String>::new(),
-        );
-    });
-    project.update(cx, |project, cx| {
-        assert_eq!(
-            project.diagnostic_summary(false, cx),
-            DiagnosticSummary {
-                error_count: 0,
-                warning_count: 0,
-            }
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "the-lsp",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Before restarting the server, report diagnostics with an unknown buffer version.
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(10000),
-        diagnostics: Vec::new(),
-    });
-    cx.executor().run_until_parked();
-
-    project.update(cx, |project, cx| {
-        project.restart_language_servers_for_buffers([buffer.clone()], cx);
-    });
-    let mut fake_server = fake_servers.next().await.unwrap();
-    let notification = fake_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await
-        .text_document;
-    assert_eq!(notification.version, 0);
-}
-
-#[gpui::test]
-async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut rust = Language::new(
-        LanguageConfig {
-            name: Arc::from("Rust"),
-            path_suffixes: vec!["rs".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_rust_servers = rust
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "rust-lsp",
-            ..Default::default()
-        }))
-        .await;
-    let mut js = Language::new(
-        LanguageConfig {
-            name: Arc::from("JavaScript"),
-            path_suffixes: vec!["js".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_js_servers = js
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            name: "js-lsp",
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
-        .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| {
-        project.languages.add(Arc::new(rust));
-        project.languages.add(Arc::new(js));
-    });
-
-    let _rs_buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-    let _js_buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx))
-        .await
-        .unwrap();
-
-    let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap();
-    assert_eq!(
-        fake_rust_server_1
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document
-            .uri
-            .as_str(),
-        "file:///dir/a.rs"
-    );
-
-    let mut fake_js_server = fake_js_servers.next().await.unwrap();
-    assert_eq!(
-        fake_js_server
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document
-            .uri
-            .as_str(),
-        "file:///dir/b.js"
-    );
-
-    // Disable Rust language server, ensuring only that server gets stopped.
-    cx.update(|cx| {
-        cx.update_global(|settings: &mut SettingsStore, cx| {
-            settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-                settings.languages.insert(
-                    Arc::from("Rust"),
-                    LanguageSettingsContent {
-                        enable_language_server: Some(false),
-                        ..Default::default()
-                    },
-                );
-            });
-        })
-    });
-    fake_rust_server_1
-        .receive_notification::<lsp::notification::Exit>()
-        .await;
-
-    // Enable Rust and disable JavaScript language servers, ensuring that the
-    // former gets started again and that the latter stops.
-    cx.update(|cx| {
-        cx.update_global(|settings: &mut SettingsStore, cx| {
-            settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-                settings.languages.insert(
-                    Arc::from("Rust"),
-                    LanguageSettingsContent {
-                        enable_language_server: Some(true),
-                        ..Default::default()
-                    },
-                );
-                settings.languages.insert(
-                    Arc::from("JavaScript"),
-                    LanguageSettingsContent {
-                        enable_language_server: Some(false),
-                        ..Default::default()
-                    },
-                );
-            });
-        })
-    });
-    let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap();
-    assert_eq!(
-        fake_rust_server_2
-            .receive_notification::<lsp::notification::DidOpenTextDocument>()
-            .await
-            .text_document
-            .uri
-            .as_str(),
-        "file:///dir/a.rs"
-    );
-    fake_js_server
-        .receive_notification::<lsp::notification::Exit>()
-        .await;
-}
-
-#[gpui::test(iterations = 3)]
-async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    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_servers = language
-        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-            disk_based_diagnostics_sources: vec!["disk".into()],
-            ..Default::default()
-        }))
-        .await;
-
-    let text = "
-        fn a() { A }
-        fn b() { BB }
-        fn c() { CCC }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": text })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    let mut fake_server = fake_servers.next().await.unwrap();
-    let open_notification = fake_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await;
-
-    // Edit the buffer, moving the content down
-    buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], None, cx));
-    let change_notification_1 = fake_server
-        .receive_notification::<lsp::notification::DidChangeTextDocument>()
-        .await;
-    assert!(change_notification_1.text_document.version > open_notification.text_document.version);
-
-    // Report some diagnostics for the initial version of the buffer
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(open_notification.text_document.version),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'A'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'BB'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                source: Some("disk".to_string()),
-                message: "undefined variable 'CCC'".to_string(),
-                ..Default::default()
-            },
-        ],
-    });
-
-    // The diagnostics have moved down since they were created.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(3, 9)..Point::new(3, 11),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'BB'".to_string(),
-                        is_disk_based: true,
-                        group_id: 1,
-                        is_primary: true,
-                        ..Default::default()
-                    },
-                },
-                DiagnosticEntry {
-                    range: Point::new(4, 9)..Point::new(4, 12),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'CCC'".to_string(),
-                        is_disk_based: true,
-                        group_id: 2,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                }
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, 0..buffer.len()),
-            [
-                ("\n\nfn a() { ".to_string(), None),
-                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\nfn b() { ".to_string(), None),
-                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\nfn c() { ".to_string(), None),
-                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\n".to_string(), None),
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
-            [
-                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }\nfn c() { ".to_string(), None),
-                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
-            ]
-        );
-    });
-
-    // Ensure overlapping diagnostics are highlighted correctly.
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(open_notification.text_document.version),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'A'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
-                severity: Some(DiagnosticSeverity::WARNING),
-                message: "unreachable statement".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-        ],
-    });
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(2, 9)..Point::new(2, 12),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::WARNING,
-                        message: "unreachable statement".to_string(),
-                        is_disk_based: true,
-                        group_id: 4,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                },
-                DiagnosticEntry {
-                    range: Point::new(2, 9)..Point::new(2, 10),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'A'".to_string(),
-                        is_disk_based: true,
-                        group_id: 3,
-                        is_primary: true,
-                        ..Default::default()
-                    },
-                }
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
-            [
-                ("fn a() { ".to_string(), None),
-                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
-                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
-                ("\n".to_string(), None),
-            ]
-        );
-        assert_eq!(
-            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
-            [
-                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
-                ("\n".to_string(), None),
-            ]
-        );
-    });
-
-    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
-    // changes since the last save.
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "    ")], None, cx);
-        buffer.edit(
-            [(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")],
-            None,
-            cx,
-        );
-        buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx);
-    });
-    let change_notification_2 = fake_server
-        .receive_notification::<lsp::notification::DidChangeTextDocument>()
-        .await;
-    assert!(
-        change_notification_2.text_document.version > change_notification_1.text_document.version
-    );
-
-    // Handle out-of-order diagnostics
-    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
-        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-        version: Some(change_notification_2.text_document.version),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "undefined variable 'BB'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                severity: Some(DiagnosticSeverity::WARNING),
-                message: "undefined variable 'A'".to_string(),
-                source: Some("disk".to_string()),
-                ..Default::default()
-            },
-        ],
-    });
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(
-            buffer
-                .snapshot()
-                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-                .collect::<Vec<_>>(),
-            &[
-                DiagnosticEntry {
-                    range: Point::new(2, 21)..Point::new(2, 22),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::WARNING,
-                        message: "undefined variable 'A'".to_string(),
-                        is_disk_based: true,
-                        group_id: 6,
-                        is_primary: true,
-                        ..Default::default()
-                    }
-                },
-                DiagnosticEntry {
-                    range: Point::new(3, 9)..Point::new(3, 14),
-                    diagnostic: Diagnostic {
-                        source: Some("disk".into()),
-                        severity: DiagnosticSeverity::ERROR,
-                        message: "undefined variable 'BB'".to_string(),
-                        is_disk_based: true,
-                        group_id: 5,
-                        is_primary: true,
-                        ..Default::default()
-                    },
-                }
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let text = concat!(
-        "let one = ;\n", //
-        "let two = \n",
-        "let three = 3;\n",
-    );
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": text })).await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    project.update(cx, |project, cx| {
-        project
-            .update_buffer_diagnostics(
-                &buffer,
-                LanguageServerId(0),
-                None,
-                vec![
-                    DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
-                            message: "syntax error 1".to_string(),
-                            ..Default::default()
-                        },
-                    },
-                    DiagnosticEntry {
-                        range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)),
-                        diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
-                            message: "syntax error 2".to_string(),
-                            ..Default::default()
-                        },
-                    },
-                ],
-                cx,
-            )
-            .unwrap();
-    });
-
-    // An empty range is extended forward to include the following character.
-    // At the end of a line, an empty range is extended backward to include
-    // the preceding character.
-    buffer.update(cx, |buffer, _| {
-        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
-        assert_eq!(
-            chunks
-                .iter()
-                .map(|(s, d)| (s.as_str(), *d))
-                .collect::<Vec<_>>(),
-            &[
-                ("let one = ", None),
-                (";", Some(DiagnosticSeverity::ERROR)),
-                ("\nlet two =", None),
-                (" ", Some(DiagnosticSeverity::ERROR)),
-                ("\nlet three = 3;\n", None)
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
-        .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-
-    project.update(cx, |project, cx| {
-        project
-            .update_diagnostic_entries(
-                LanguageServerId(0),
-                Path::new("/dir/a.rs").to_owned(),
-                None,
-                vec![DiagnosticEntry {
-                    range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
-                    diagnostic: Diagnostic {
-                        severity: DiagnosticSeverity::ERROR,
-                        is_primary: true,
-                        message: "syntax error a1".to_string(),
-                        ..Default::default()
-                    },
-                }],
-                cx,
-            )
-            .unwrap();
-        project
-            .update_diagnostic_entries(
-                LanguageServerId(1),
-                Path::new("/dir/a.rs").to_owned(),
-                None,
-                vec![DiagnosticEntry {
-                    range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
-                    diagnostic: Diagnostic {
-                        severity: DiagnosticSeverity::ERROR,
-                        is_primary: true,
-                        message: "syntax error b1".to_string(),
-                        ..Default::default()
-                    },
-                }],
-                cx,
-            )
-            .unwrap();
-
-        assert_eq!(
-            project.diagnostic_summary(false, cx),
-            DiagnosticSummary {
-                error_count: 2,
-                warning_count: 0,
-            }
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    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_servers = language.set_fake_lsp_adapter(Default::default()).await;
-
-    let text = "
-        fn a() {
-            f1();
-        }
-        fn b() {
-            f2();
-        }
-        fn c() {
-            f3();
-        }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": text.clone(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    let mut fake_server = fake_servers.next().await.unwrap();
-    let lsp_document_version = fake_server
-        .receive_notification::<lsp::notification::DidOpenTextDocument>()
-        .await
-        .text_document
-        .version;
-
-    // Simulate editing the buffer after the language server computes some edits.
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit(
-            [(
-                Point::new(0, 0)..Point::new(0, 0),
-                "// above first function\n",
-            )],
-            None,
-            cx,
-        );
-        buffer.edit(
-            [(
-                Point::new(2, 0)..Point::new(2, 0),
-                "    // inside first function\n",
-            )],
-            None,
-            cx,
-        );
-        buffer.edit(
-            [(
-                Point::new(6, 4)..Point::new(6, 4),
-                "// inside second function ",
-            )],
-            None,
-            cx,
-        );
-
-        assert_eq!(
-            buffer.text(),
-            "
-                // above first function
-                fn a() {
-                    // inside first function
-                    f1();
-                }
-                fn b() {
-                    // inside second function f2();
-                }
-                fn c() {
-                    f3();
-                }
-            "
-            .unindent()
-        );
-    });
-
-    let edits = project
-        .update(cx, |project, cx| {
-            project.edits_from_lsp(
-                &buffer,
-                vec![
-                    // replace body of first function
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(3, 0)),
-                        new_text: "
-                            fn a() {
-                                f10();
-                            }
-                            "
-                        .unindent(),
-                    },
-                    // edit inside second function
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(4, 6), lsp::Position::new(4, 6)),
-                        new_text: "00".into(),
-                    },
-                    // edit inside third function via two distinct edits
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 5)),
-                        new_text: "4000".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 6)),
-                        new_text: "".into(),
-                    },
-                ],
-                LanguageServerId(0),
-                Some(lsp_document_version),
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, cx| {
-        for (range, new_text) in edits {
-            buffer.edit([(range, new_text)], None, cx);
-        }
-        assert_eq!(
-            buffer.text(),
-            "
-                // above first function
-                fn a() {
-                    // inside first function
-                    f10();
-                }
-                fn b() {
-                    // inside second function f200();
-                }
-                fn c() {
-                    f4000();
-                }
-                "
-            .unindent()
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let text = "
-        use a::b;
-        use a::c;
-
-        fn f() {
-            b();
-            c();
-        }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": text.clone(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Simulate the language server sending us a small edit in the form of a very large diff.
-    // Rust-analyzer does this when performing a merge-imports code action.
-    let edits = project
-        .update(cx, |project, cx| {
-            project.edits_from_lsp(
-                &buffer,
-                [
-                    // Replace the first use statement without editing the semicolon.
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 8)),
-                        new_text: "a::{b, c}".into(),
-                    },
-                    // Reinsert the remainder of the file between the semicolon and the final
-                    // newline of the file.
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "\n\n".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "
-                            fn f() {
-                                b();
-                                c();
-                            }"
-                        .unindent(),
-                    },
-                    // Delete everything after the first newline of the file.
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(7, 0)),
-                        new_text: "".into(),
-                    },
-                ],
-                LanguageServerId(0),
-                None,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, cx| {
-        let edits = edits
-            .into_iter()
-            .map(|(range, text)| {
-                (
-                    range.start.to_point(buffer)..range.end.to_point(buffer),
-                    text,
-                )
-            })
-            .collect::<Vec<_>>();
-
-        assert_eq!(
-            edits,
-            [
-                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
-                (Point::new(1, 0)..Point::new(2, 0), "".into())
-            ]
-        );
-
-        for (range, new_text) in edits {
-            buffer.edit([(range, new_text)], None, cx);
-        }
-        assert_eq!(
-            buffer.text(),
-            "
-                use a::{b, c};
-
-                fn f() {
-                    b();
-                    c();
-                }
-            "
-            .unindent()
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let text = "
-        use a::b;
-        use a::c;
-
-        fn f() {
-            b();
-            c();
-        }
-    "
-    .unindent();
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": text.clone(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    // Simulate the language server sending us edits in a non-ordered fashion,
-    // with ranges sometimes being inverted or pointing to invalid locations.
-    let edits = project
-        .update(cx, |project, cx| {
-            project.edits_from_lsp(
-                &buffer,
-                [
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "\n\n".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 4)),
-                        new_text: "a::{b, c}".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(99, 0)),
-                        new_text: "".into(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
-                        new_text: "
-                            fn f() {
-                                b();
-                                c();
-                            }"
-                        .unindent(),
-                    },
-                ],
-                LanguageServerId(0),
-                None,
-                cx,
-            )
-        })
-        .await
-        .unwrap();
-
-    buffer.update(cx, |buffer, cx| {
-        let edits = edits
-            .into_iter()
-            .map(|(range, text)| {
-                (
-                    range.start.to_point(buffer)..range.end.to_point(buffer),
-                    text,
-                )
-            })
-            .collect::<Vec<_>>();
-
-        assert_eq!(
-            edits,
-            [
-                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
-                (Point::new(1, 0)..Point::new(2, 0), "".into())
-            ]
-        );
-
-        for (range, new_text) in edits {
-            buffer.edit([(range, new_text)], None, cx);
-        }
-        assert_eq!(
-            buffer.text(),
-            "
-                use a::{b, c};
-
-                fn f() {
-                    b();
-                    c();
-                }
-            "
-            .unindent()
-        );
-    });
-}
-
-fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
-    buffer: &Buffer,
-    range: Range<T>,
-) -> Vec<(String, Option<DiagnosticSeverity>)> {
-    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
-    for chunk in buffer.snapshot().chunks(range, true) {
-        if chunks.last().map_or(false, |prev_chunk| {
-            prev_chunk.1 == chunk.diagnostic_severity
-        }) {
-            chunks.last_mut().unwrap().0.push_str(chunk.text);
-        } else {
-            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity));
-        }
-    }
-    chunks
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_definition(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    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_servers = language.set_fake_lsp_adapter(Default::default()).await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.rs": "const fn a() { A }",
-            "b.rs": "const y: i32 = crate::a()",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-
-    let buffer = project
-        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_servers.next().await.unwrap();
-    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
-        let params = params.text_document_position_params;
-        assert_eq!(
-            params.text_document.uri.to_file_path().unwrap(),
-            Path::new("/dir/b.rs"),
-        );
-        assert_eq!(params.position, lsp::Position::new(0, 22));
-
-        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
-            lsp::Location::new(
-                lsp::Url::from_file_path("/dir/a.rs").unwrap(),
-                lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-            ),
-        )))
-    });
-
-    let mut definitions = project
-        .update(cx, |project, cx| project.definition(&buffer, 22, cx))
-        .await
-        .unwrap();
-
-    // Assert no new language server started
-    cx.executor().run_until_parked();
-    assert!(fake_servers.try_next().is_err());
-
-    assert_eq!(definitions.len(), 1);
-    let definition = definitions.pop().unwrap();
-    cx.update(|cx| {
-        let target_buffer = definition.target.buffer.read(cx);
-        assert_eq!(
-            target_buffer
-                .file()
-                .unwrap()
-                .as_local()
-                .unwrap()
-                .abs_path(cx),
-            Path::new("/dir/a.rs"),
-        );
-        assert_eq!(definition.target.range.to_offset(target_buffer), 9..10);
-        assert_eq!(
-            list_worktrees(&project, cx),
-            [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
-        );
-
-        drop(definition);
-    });
-    cx.update(|cx| {
-        assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
-    });
-
-    fn list_worktrees<'a>(
-        project: &'a Model<Project>,
-        cx: &'a AppContext,
-    ) -> Vec<(&'a Path, bool)> {
-        project
-            .read(cx)
-            .worktrees()
-            .map(|worktree| {
-                let worktree = worktree.read(cx);
-                (
-                    worktree.as_local().unwrap().abs_path().as_ref(),
-                    worktree.is_visible(),
-                )
-            })
-            .collect::<Vec<_>>()
-    }
-}
-
-#[gpui::test]
-async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_typescript::language_typescript()),
-    );
-    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()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.ts": "",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_language_servers.next().await.unwrap();
-
-    let text = "let a = b.fqn";
-    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-    let completions = project.update(cx, |project, cx| {
-        project.completions(&buffer, text.len(), cx)
-    });
-
-    fake_server
-        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
-            Ok(Some(lsp::CompletionResponse::Array(vec![
-                lsp::CompletionItem {
-                    label: "fullyQualifiedName?".into(),
-                    insert_text: Some("fullyQualifiedName".into()),
-                    ..Default::default()
-                },
-            ])))
-        })
-        .next()
-        .await;
-    let completions = completions.await.unwrap();
-    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-    assert_eq!(completions.len(), 1);
-    assert_eq!(completions[0].new_text, "fullyQualifiedName");
-    assert_eq!(
-        completions[0].old_range.to_offset(&snapshot),
-        text.len() - 3..text.len()
-    );
-
-    let text = "let a = \"atoms/cmp\"";
-    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-    let completions = project.update(cx, |project, cx| {
-        project.completions(&buffer, text.len() - 1, cx)
-    });
-
-    fake_server
-        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
-            Ok(Some(lsp::CompletionResponse::Array(vec![
-                lsp::CompletionItem {
-                    label: "component".into(),
-                    ..Default::default()
-                },
-            ])))
-        })
-        .next()
-        .await;
-    let completions = completions.await.unwrap();
-    let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
-    assert_eq!(completions.len(), 1);
-    assert_eq!(completions[0].new_text, "component");
-    assert_eq!(
-        completions[0].old_range.to_offset(&snapshot),
-        text.len() - 4..text.len() - 1
-    );
-}
-
-#[gpui::test]
-async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
-            ..Default::default()
-        },
-        Some(tree_sitter_typescript::language_typescript()),
-    );
-    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()]),
-                    ..Default::default()
-                }),
-                ..Default::default()
-            },
-            ..Default::default()
-        }))
-        .await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.ts": "",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_language_servers.next().await.unwrap();
-
-    let text = "let a = b.fqn";
-    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
-    let completions = project.update(cx, |project, cx| {
-        project.completions(&buffer, text.len(), cx)
-    });
-
-    fake_server
-        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
-            Ok(Some(lsp::CompletionResponse::Array(vec![
-                lsp::CompletionItem {
-                    label: "fullyQualifiedName?".into(),
-                    insert_text: Some("fully\rQualified\r\nName".into()),
-                    ..Default::default()
-                },
-            ])))
-        })
-        .next()
-        .await;
-    let completions = completions.await.unwrap();
-    assert_eq!(completions.len(), 1);
-    assert_eq!(completions[0].new_text, "fully\nQualified\nName");
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let mut language = Language::new(
-        LanguageConfig {
-            name: "TypeScript".into(),
-            path_suffixes: vec!["ts".to_string()],
-            ..Default::default()
-        },
-        None,
-    );
-    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.ts": "a",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
-        .await
-        .unwrap();
-
-    let fake_server = fake_language_servers.next().await.unwrap();
-
-    // Language server returns code actions that contain commands, and not edits.
-    let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
-    fake_server
-        .handle_request::<lsp::request::CodeActionRequest, _, _>(|_, _| async move {
-            Ok(Some(vec![
-                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
-                    title: "The code action".into(),
-                    command: Some(lsp::Command {
-                        title: "The command".into(),
-                        command: "_the/command".into(),
-                        arguments: Some(vec![json!("the-argument")]),
-                    }),
-                    ..Default::default()
-                }),
-                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
-                    title: "two".into(),
-                    ..Default::default()
-                }),
-            ]))
-        })
-        .next()
-        .await;
-
-    let action = actions.await.unwrap()[0].clone();
-    let apply = project.update(cx, |project, cx| {
-        project.apply_code_action(buffer.clone(), action, true, cx)
-    });
-
-    // Resolving the code action does not populate its edits. In absence of
-    // edits, we must execute the given command.
-    fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
-        |action, _| async move { Ok(action) },
-    );
-
-    // While executing the command, the language server sends the editor
-    // a `workspaceEdit` request.
-    fake_server
-        .handle_request::<lsp::request::ExecuteCommand, _, _>({
-            let fake = fake_server.clone();
-            move |params, _| {
-                assert_eq!(params.command, "_the/command");
-                let fake = fake.clone();
-                async move {
-                    fake.server
-                        .request::<lsp::request::ApplyWorkspaceEdit>(
-                            lsp::ApplyWorkspaceEditParams {
-                                label: None,
-                                edit: lsp::WorkspaceEdit {
-                                    changes: Some(
-                                        [(
-                                            lsp::Url::from_file_path("/dir/a.ts").unwrap(),
-                                            vec![lsp::TextEdit {
-                                                range: lsp::Range::new(
-                                                    lsp::Position::new(0, 0),
-                                                    lsp::Position::new(0, 0),
-                                                ),
-                                                new_text: "X".into(),
-                                            }],
-                                        )]
-                                        .into_iter()
-                                        .collect(),
-                                    ),
-                                    ..Default::default()
-                                },
-                            },
-                        )
-                        .await
-                        .unwrap();
-                    Ok(Some(json!(null)))
-                }
-            }
-        })
-        .next()
-        .await;
-
-    // Applying the code action returns a project transaction containing the edits
-    // sent by the language server in its `workspaceEdit` request.
-    let transaction = apply.await.unwrap();
-    assert!(transaction.0.contains_key(&buffer));
-    buffer.update(cx, |buffer, cx| {
-        assert_eq!(buffer.text(), "Xa");
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "a");
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_save_file(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the old contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    buffer.update(cx, |buffer, cx| {
-        assert_eq!(buffer.text(), "the old contents");
-        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
-    });
-
-    project
-        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
-        .await
-        .unwrap();
-
-    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
-}
-
-#[gpui::test(iterations = 30)]
-async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor().clone());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the original contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-
-    // Simulate buffer diffs being slow, so that they don't complete before
-    // the next file change occurs.
-    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
-
-    // Change the buffer's file on disk, and then wait for the file change
-    // to be detected by the worktree, so that the buffer starts reloading.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"the first contents".into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    worktree.next_event(cx);
-
-    // Change the buffer's file again. Depending on the random seed, the
-    // previous file change may still be in progress.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"the second contents".into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    worktree.next_event(cx);
-
-    cx.executor().run_until_parked();
-    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    buffer.read_with(cx, |buffer, _| {
-        assert_eq!(buffer.text(), on_disk_text);
-        assert!(!buffer.is_dirty(), "buffer should not be dirty");
-        assert!(!buffer.has_conflict(), "buffer should not be dirty");
-    });
-}
-
-#[gpui::test(iterations = 30)]
-async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor().clone());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the original contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let worktree = project.read_with(cx, |project, _| project.worktrees().next().unwrap());
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-
-    // Simulate buffer diffs being slow, so that they don't complete before
-    // the next file change occurs.
-    cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
-
-    // Change the buffer's file on disk, and then wait for the file change
-    // to be detected by the worktree, so that the buffer starts reloading.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"the first contents".into(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    worktree.next_event(cx);
-
-    cx.executor()
-        .spawn(cx.executor().simulate_random_delay())
-        .await;
-
-    // Perform a noop edit, causing the buffer's version to increase.
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, " ")], None, cx);
-        buffer.undo(cx);
-    });
-
-    cx.executor().run_until_parked();
-    let on_disk_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    buffer.read_with(cx, |buffer, _| {
-        let buffer_text = buffer.text();
-        if buffer_text == on_disk_text {
-            assert!(
-                !buffer.is_dirty() && !buffer.has_conflict(),
-                "buffer shouldn't be dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}",
-            );
-        }
-        // If the file change occurred while the buffer was processing the first
-        // change, the buffer will be in a conflicting state.
-        else {
-            assert!(buffer.is_dirty(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
-            assert!(buffer.has_conflict(), "buffer should report that it is dirty. text: {buffer_text:?}, disk text: {on_disk_text:?}");
-        }
-    });
-}
-
-#[gpui::test]
-async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "the old contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
-    });
-
-    project
-        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
-        .await
-        .unwrap();
-
-    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
-    assert_eq!(new_text, buffer.update(cx, |buffer, _| buffer.text()));
-}
-
-#[gpui::test]
-async fn test_save_as(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree("/dir", json!({})).await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    let languages = project.update(cx, |project, _| project.languages().clone());
-    languages.register(
-        "/some/path",
-        LanguageConfig {
-            name: "Rust".into(),
-            path_suffixes: vec!["rs".into()],
-            ..Default::default()
-        },
-        tree_sitter_rust::language(),
-        vec![],
-        |_| Default::default(),
-    );
-
-    let buffer = project.update(cx, |project, cx| {
-        project.create_buffer("", None, cx).unwrap()
-    });
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "abc")], None, cx);
-        assert!(buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
-    });
-    project
-        .update(cx, |project, cx| {
-            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
-        })
-        .await
-        .unwrap();
-    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
-
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, cx| {
-        assert_eq!(
-            buffer.file().unwrap().full_path(cx),
-            Path::new("dir/file1.rs")
-        );
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
-    });
-
-    let opened_buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/dir/file1.rs", cx)
-        })
-        .await
-        .unwrap();
-    assert_eq!(opened_buffer, buffer);
-}
-
-#[gpui::test(retries = 5)]
-async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-
-    let dir = temp_tree(json!({
-        "a": {
-            "file1": "",
-            "file2": "",
-            "file3": "",
-        },
-        "b": {
-            "c": {
-                "file4": "",
-                "file5": "",
-            }
-        }
-    }));
-
-    let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
-    let rpc = project.update(cx, |p, _| p.client.clone());
-
-    let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
-        let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
-        async move { buffer.await.unwrap() }
-    };
-    let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
-        project.update(cx, |project, cx| {
-            let tree = project.worktrees().next().unwrap();
-            tree.read(cx)
-                .entry_for_path(path)
-                .unwrap_or_else(|| panic!("no entry for path {}", path))
-                .id
-        })
-    };
-
-    let buffer2 = buffer_for_path("a/file2", cx).await;
-    let buffer3 = buffer_for_path("a/file3", cx).await;
-    let buffer4 = buffer_for_path("b/c/file4", cx).await;
-    let buffer5 = buffer_for_path("b/c/file5", cx).await;
-
-    let file2_id = id_for_path("a/file2", cx);
-    let file3_id = id_for_path("a/file3", cx);
-    let file4_id = id_for_path("b/c/file4", cx);
-
-    // Create a remote copy of this worktree.
-    let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-
-    let metadata = tree.update(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
-
-    let updates = Arc::new(Mutex::new(Vec::new()));
-    tree.update(cx, |tree, cx| {
-        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-            let updates = updates.clone();
-            move |update| {
-                updates.lock().push(update);
-                async { true }
-            }
-        });
-    });
-
-    let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
-
-    cx.executor().run_until_parked();
-
-    cx.update(|cx| {
-        assert!(!buffer2.read(cx).is_dirty());
-        assert!(!buffer3.read(cx).is_dirty());
-        assert!(!buffer4.read(cx).is_dirty());
-        assert!(!buffer5.read(cx).is_dirty());
-    });
-
-    // Rename and delete files and directories.
-    tree.flush_fs_events(cx).await;
-    std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
-    std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
-    std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
-    std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
-    tree.flush_fs_events(cx).await;
-
-    let expected_paths = vec![
-        "a",
-        "a/file1",
-        "a/file2.new",
-        "b",
-        "d",
-        "d/file3",
-        "d/file4",
-    ];
-
-    cx.update(|app| {
-        assert_eq!(
-            tree.read(app)
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            expected_paths
-        );
-    });
-
-    assert_eq!(id_for_path("a/file2.new", cx), file2_id);
-    assert_eq!(id_for_path("d/file3", cx), file3_id);
-    assert_eq!(id_for_path("d/file4", cx), file4_id);
-
-    cx.update(|cx| {
-        assert_eq!(
-            buffer2.read(cx).file().unwrap().path().as_ref(),
-            Path::new("a/file2.new")
-        );
-        assert_eq!(
-            buffer3.read(cx).file().unwrap().path().as_ref(),
-            Path::new("d/file3")
-        );
-        assert_eq!(
-            buffer4.read(cx).file().unwrap().path().as_ref(),
-            Path::new("d/file4")
-        );
-        assert_eq!(
-            buffer5.read(cx).file().unwrap().path().as_ref(),
-            Path::new("b/c/file5")
-        );
-
-        assert!(!buffer2.read(cx).file().unwrap().is_deleted());
-        assert!(!buffer3.read(cx).file().unwrap().is_deleted());
-        assert!(!buffer4.read(cx).file().unwrap().is_deleted());
-        assert!(buffer5.read(cx).file().unwrap().is_deleted());
-    });
-
-    // Update the remote worktree. Check that it becomes consistent with the
-    // local worktree.
-    cx.executor().run_until_parked();
-
-    remote.update(cx, |remote, _| {
-        for update in updates.lock().drain(..) {
-            remote.as_remote_mut().unwrap().update_from_remote(update);
-        }
-    });
-    cx.executor().run_until_parked();
-    remote.update(cx, |remote, _| {
-        assert_eq!(
-            remote
-                .paths()
-                .map(|p| p.to_str().unwrap())
-                .collect::<Vec<_>>(),
-            expected_paths
-        );
-    });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a": {
-                "file1": "",
-            }
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, [Path::new("/dir")], cx).await;
-    let tree = project.update(cx, |project, _| project.worktrees().next().unwrap());
-    let tree_id = tree.update(cx, |tree, _| tree.id());
-
-    let id_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
-        project.update(cx, |project, cx| {
-            let tree = project.worktrees().next().unwrap();
-            tree.read(cx)
-                .entry_for_path(path)
-                .unwrap_or_else(|| panic!("no entry for path {}", path))
-                .id
-        })
-    };
-
-    let dir_id = id_for_path("a", cx);
-    let file_id = id_for_path("a/file1", cx);
-    let buffer = project
-        .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx))
-        .await
-        .unwrap();
-    buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
-
-    project
-        .update(cx, |project, cx| {
-            project.rename_entry(dir_id, Path::new("b"), cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-
-    assert_eq!(id_for_path("b", cx), dir_id);
-    assert_eq!(id_for_path("b/file1", cx), file_id);
-    buffer.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
-}
-
-#[gpui::test]
-async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "a.txt": "a-contents",
-            "b.txt": "b-contents",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    // Spawn multiple tasks to open paths, repeating some paths.
-    let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
-        (
-            p.open_local_buffer("/dir/a.txt", cx),
-            p.open_local_buffer("/dir/b.txt", cx),
-            p.open_local_buffer("/dir/a.txt", cx),
-        )
-    });
-
-    let buffer_a_1 = buffer_a_1.await.unwrap();
-    let buffer_a_2 = buffer_a_2.await.unwrap();
-    let buffer_b = buffer_b.await.unwrap();
-    assert_eq!(buffer_a_1.update(cx, |b, _| b.text()), "a-contents");
-    assert_eq!(buffer_b.update(cx, |b, _| b.text()), "b-contents");
-
-    // There is only one buffer per path.
-    let buffer_a_id = buffer_a_1.entity_id();
-    assert_eq!(buffer_a_2.entity_id(), buffer_a_id);
-
-    // Open the same path again while it is still open.
-    drop(buffer_a_1);
-    let buffer_a_3 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx))
-        .await
-        .unwrap();
-
-    // There's still only one buffer per path.
-    assert_eq!(buffer_a_3.entity_id(), buffer_a_id);
-}
-
-#[gpui::test]
-async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "abc",
-            "file2": "def",
-            "file3": "ghi",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    let buffer1 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    let events = Arc::new(Mutex::new(Vec::new()));
-
-    // initially, the buffer isn't dirty.
-    buffer1.update(cx, |buffer, cx| {
-        cx.subscribe(&buffer1, {
-            let events = events.clone();
-            move |_, _, event, _| match event {
-                BufferEvent::Operation(_) => {}
-                _ => events.lock().push(event.clone()),
-            }
-        })
-        .detach();
-
-        assert!(!buffer.is_dirty());
-        assert!(events.lock().is_empty());
-
-        buffer.edit([(1..2, "")], None, cx);
-    });
-
-    // after the first edit, the buffer is dirty, and emits a dirtied event.
-    buffer1.update(cx, |buffer, cx| {
-        assert!(buffer.text() == "ac");
-        assert!(buffer.is_dirty());
-        assert_eq!(
-            *events.lock(),
-            &[language::Event::Edited, language::Event::DirtyChanged]
-        );
-        events.lock().clear();
-        buffer.did_save(
-            buffer.version(),
-            buffer.as_rope().fingerprint(),
-            buffer.file().unwrap().mtime(),
-            cx,
-        );
-    });
-
-    // after saving, the buffer is not dirty, and emits a saved event.
-    buffer1.update(cx, |buffer, cx| {
-        assert!(!buffer.is_dirty());
-        assert_eq!(*events.lock(), &[language::Event::Saved]);
-        events.lock().clear();
-
-        buffer.edit([(1..1, "B")], None, cx);
-        buffer.edit([(2..2, "D")], None, cx);
-    });
-
-    // after editing again, the buffer is dirty, and emits another dirty event.
-    buffer1.update(cx, |buffer, cx| {
-        assert!(buffer.text() == "aBDc");
-        assert!(buffer.is_dirty());
-        assert_eq!(
-            *events.lock(),
-            &[
-                language::Event::Edited,
-                language::Event::DirtyChanged,
-                language::Event::Edited,
-            ],
-        );
-        events.lock().clear();
-
-        // After restoring the buffer to its previously-saved state,
-        // the buffer is not considered dirty anymore.
-        buffer.edit([(1..3, "")], None, cx);
-        assert!(buffer.text() == "ac");
-        assert!(!buffer.is_dirty());
-    });
-
-    assert_eq!(
-        *events.lock(),
-        &[language::Event::Edited, language::Event::DirtyChanged]
-    );
-
-    // When a file is deleted, the buffer is considered dirty.
-    let events = Arc::new(Mutex::new(Vec::new()));
-    let buffer2 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
-        .await
-        .unwrap();
-    buffer2.update(cx, |_, cx| {
-        cx.subscribe(&buffer2, {
-            let events = events.clone();
-            move |_, _, event, _| events.lock().push(event.clone())
-        })
-        .detach();
-    });
-
-    fs.remove_file("/dir/file2".as_ref(), Default::default())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    buffer2.update(cx, |buffer, _| assert!(buffer.is_dirty()));
-    assert_eq!(
-        *events.lock(),
-        &[
-            language::Event::DirtyChanged,
-            language::Event::FileHandleChanged
-        ]
-    );
-
-    // When a file is already dirty when deleted, we don't emit a Dirtied event.
-    let events = Arc::new(Mutex::new(Vec::new()));
-    let buffer3 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx))
-        .await
-        .unwrap();
-    buffer3.update(cx, |_, cx| {
-        cx.subscribe(&buffer3, {
-            let events = events.clone();
-            move |_, _, event, _| events.lock().push(event.clone())
-        })
-        .detach();
-    });
-
-    buffer3.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, "x")], None, cx);
-    });
-    events.lock().clear();
-    fs.remove_file("/dir/file3".as_ref(), Default::default())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    assert_eq!(*events.lock(), &[language::Event::FileHandleChanged]);
-    cx.update(|cx| assert!(buffer3.read(cx).is_dirty()));
-}
-
-#[gpui::test]
-async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let initial_contents = "aaa\nbbbbb\nc\n";
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "the-file": initial_contents,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
-        .await
-        .unwrap();
-
-    let anchors = (0..3)
-        .map(|row| buffer.update(cx, |b, _| b.anchor_before(Point::new(row, 1))))
-        .collect::<Vec<_>>();
-
-    // Change the file on disk, adding two new lines of text, and removing
-    // one line.
-    buffer.update(cx, |buffer, _| {
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-    let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
-    fs.save(
-        "/dir/the-file".as_ref(),
-        &new_contents.into(),
-        LineEnding::Unix,
-    )
-    .await
-    .unwrap();
-
-    // Because the buffer was not modified, it is reloaded from disk. Its
-    // contents are edited according to the diff between the old and new
-    // file contents.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), new_contents);
-        assert!(!buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-
-        let anchor_positions = anchors
-            .iter()
-            .map(|anchor| anchor.to_point(&*buffer))
-            .collect::<Vec<_>>();
-        assert_eq!(
-            anchor_positions,
-            [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)]
-        );
-    });
-
-    // Modify the buffer
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(0..0, " ")], None, cx);
-        assert!(buffer.is_dirty());
-        assert!(!buffer.has_conflict());
-    });
-
-    // Change the file on disk again, adding blank lines to the beginning.
-    fs.save(
-        "/dir/the-file".as_ref(),
-        &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
-        LineEnding::Unix,
-    )
-    .await
-    .unwrap();
-
-    // Because the buffer is modified, it doesn't reload from disk, but is
-    // marked as having a conflict.
-    cx.executor().run_until_parked();
-    buffer.update(cx, |buffer, _| {
-        assert!(buffer.has_conflict());
-    });
-}
-
-#[gpui::test]
-async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "file1": "a\nb\nc\n",
-            "file2": "one\r\ntwo\r\nthree\r\n",
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    let buffer1 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
-        .await
-        .unwrap();
-    let buffer2 = project
-        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
-        .await
-        .unwrap();
-
-    buffer1.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), "a\nb\nc\n");
-        assert_eq!(buffer.line_ending(), LineEnding::Unix);
-    });
-    buffer2.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), "one\ntwo\nthree\n");
-        assert_eq!(buffer.line_ending(), LineEnding::Windows);
-    });
-
-    // Change a file's line endings on disk from unix to windows. The buffer's
-    // state updates correctly.
-    fs.save(
-        "/dir/file1".as_ref(),
-        &"aaa\nb\nc\n".into(),
-        LineEnding::Windows,
-    )
-    .await
-    .unwrap();
-    cx.executor().run_until_parked();
-    buffer1.update(cx, |buffer, _| {
-        assert_eq!(buffer.text(), "aaa\nb\nc\n");
-        assert_eq!(buffer.line_ending(), LineEnding::Windows);
-    });
-
-    // Save a file with windows line endings. The file is written correctly.
-    buffer2.update(cx, |buffer, cx| {
-        buffer.set_text("one\ntwo\nthree\nfour\n", cx);
-    });
-    project
-        .update(cx, |project, cx| project.save_buffer(buffer2, cx))
-        .await
-        .unwrap();
-    assert_eq!(
-        fs.load("/dir/file2".as_ref()).await.unwrap(),
-        "one\r\ntwo\r\nthree\r\nfour\r\n",
-    );
-}
-
-#[gpui::test]
-async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/the-dir",
-        json!({
-            "a.rs": "
-                fn foo(mut v: Vec<usize>) {
-                    for x in &v {
-                        v.push(1);
-                    }
-                }
-            "
-            .unindent(),
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
-    let buffer = project
-        .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
-        .await
-        .unwrap();
-
-    let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
-    let message = lsp::PublishDiagnosticsParams {
-        uri: buffer_uri.clone(),
-        diagnostics: vec![
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                severity: Some(DiagnosticSeverity::WARNING),
-                message: "error 1".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri.clone(),
-                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                    },
-                    message: "error 1 hint 1".to_string(),
-                }]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                severity: Some(DiagnosticSeverity::HINT),
-                message: "error 1 hint 1".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri.clone(),
-                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
-                    },
-                    message: "original diagnostic".to_string(),
-                }]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
-                severity: Some(DiagnosticSeverity::ERROR),
-                message: "error 2".to_string(),
-                related_information: Some(vec![
-                    lsp::DiagnosticRelatedInformation {
-                        location: lsp::Location {
-                            uri: buffer_uri.clone(),
-                            range: lsp::Range::new(
-                                lsp::Position::new(1, 13),
-                                lsp::Position::new(1, 15),
-                            ),
-                        },
-                        message: "error 2 hint 1".to_string(),
-                    },
-                    lsp::DiagnosticRelatedInformation {
-                        location: lsp::Location {
-                            uri: buffer_uri.clone(),
-                            range: lsp::Range::new(
-                                lsp::Position::new(1, 13),
-                                lsp::Position::new(1, 15),
-                            ),
-                        },
-                        message: "error 2 hint 2".to_string(),
-                    },
-                ]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
-                severity: Some(DiagnosticSeverity::HINT),
-                message: "error 2 hint 1".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri.clone(),
-                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
-                    },
-                    message: "original diagnostic".to_string(),
-                }]),
-                ..Default::default()
-            },
-            lsp::Diagnostic {
-                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
-                severity: Some(DiagnosticSeverity::HINT),
-                message: "error 2 hint 2".to_string(),
-                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
-                    location: lsp::Location {
-                        uri: buffer_uri,
-                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
-                    },
-                    message: "original diagnostic".to_string(),
-                }]),
-                ..Default::default()
-            },
-        ],
-        version: None,
-    };
-
-    project
-        .update(cx, |p, cx| {
-            p.update_diagnostics(LanguageServerId(0), message, &[], cx)
-        })
-        .unwrap();
-    let buffer = buffer.update(cx, |buffer, _| buffer.snapshot());
-
-    assert_eq!(
-        buffer
-            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
-            .collect::<Vec<_>>(),
-        &[
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::WARNING,
-                    message: "error 1".to_string(),
-                    group_id: 1,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 1 hint 1".to_string(),
-                    group_id: 1,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 1".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 2".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(2, 8)..Point::new(2, 17),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::ERROR,
-                    message: "error 2".to_string(),
-                    group_id: 0,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            }
-        ]
-    );
-
-    assert_eq!(
-        buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
-        &[
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 1".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 13)..Point::new(1, 15),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 2 hint 2".to_string(),
-                    group_id: 0,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(2, 8)..Point::new(2, 17),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::ERROR,
-                    message: "error 2".to_string(),
-                    group_id: 0,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            }
-        ]
-    );
-
-    assert_eq!(
-        buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
-        &[
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::WARNING,
-                    message: "error 1".to_string(),
-                    group_id: 1,
-                    is_primary: true,
-                    ..Default::default()
-                }
-            },
-            DiagnosticEntry {
-                range: Point::new(1, 8)..Point::new(1, 9),
-                diagnostic: Diagnostic {
-                    severity: DiagnosticSeverity::HINT,
-                    message: "error 1 hint 1".to_string(),
-                    group_id: 1,
-                    is_primary: false,
-                    ..Default::default()
-                }
-            },
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_rename(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    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_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;
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": "const ONE: usize = 1;",
-            "two.rs": "const TWO: usize = one::ONE + one::ONE;"
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
-    let buffer = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/dir/one.rs", cx)
-        })
-        .await
-        .unwrap();
-
-    let fake_server = fake_servers.next().await.unwrap();
-
-    let response = project.update(cx, |project, cx| {
-        project.prepare_rename(buffer.clone(), 7, cx)
-    });
-    fake_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();
-    let range = response.await.unwrap().unwrap();
-    let range = buffer.update(cx, |buffer, _| range.to_offset(buffer));
-    assert_eq!(range, 6..9);
-
-    let response = project.update(cx, |project, cx| {
-        project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
-    });
-    fake_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, 7)
-            );
-            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();
-    let mut transaction = response.await.unwrap().0;
-    assert_eq!(transaction.len(), 2);
-    assert_eq!(
-        transaction
-            .remove_entry(&buffer)
-            .unwrap()
-            .0
-            .update(cx, |buffer, _| buffer.text()),
-        "const THREE: usize = 1;"
-    );
-    assert_eq!(
-        transaction
-            .into_keys()
-            .next()
-            .unwrap()
-            .update(cx, |buffer, _| buffer.text()),
-        "const TWO: usize = one::THREE + one::THREE;"
-    );
-}
-
-#[gpui::test]
-async fn test_search(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.executor());
-    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;
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text("TWO", false, true, false, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("two.rs".to_string(), vec![6..9]),
-            ("three.rs".to_string(), vec![37..40])
-        ])
-    );
-
-    let buffer_4 = project
-        .update(cx, |project, cx| {
-            project.open_local_buffer("/dir/four.rs", cx)
-        })
-        .await
-        .unwrap();
-    buffer_4.update(cx, |buffer, cx| {
-        let text = "two::TWO";
-        buffer.edit([(20..28, text), (31..43, text)], None, cx);
-    });
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text("TWO", false, true, false, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("two.rs".to_string(), vec![6..9]),
-            ("three.rs".to_string(), vec![37..40]),
-            ("four.rs".to_string(), vec![25..28, 36..39])
-        ])
-    );
-}
-
-#[gpui::test]
-async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let search_query = "file";
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": r#"// Rust file one"#,
-            "one.ts": r#"// TypeScript file one"#,
-            "two.rs": r#"// Rust file two"#,
-            "two.ts": r#"// TypeScript file two"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.odd").unwrap()],
-                Vec::new()
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "If no inclusions match, no files should be returned"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.rs").unwrap()],
-                Vec::new()
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("two.rs".to_string(), vec![8..12]),
-        ]),
-        "Rust only search should give only Rust files"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-                Vec::new()
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "TypeScript only search should give only TypeScript files, even if other inclusions don't match anything"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.rs").unwrap(),
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-                Vec::new()
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.rs".to_string(), vec![8..12]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "Rust and typescript search should give both Rust and TypeScript files, even if other inclusions don't match anything"
-    );
-}
-
-#[gpui::test]
-async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let search_query = "file";
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": r#"// Rust file one"#,
-            "one.ts": r#"// TypeScript file one"#,
-            "two.rs": r#"// Rust file two"#,
-            "two.ts": r#"// TypeScript file two"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![PathMatcher::new("*.odd").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.rs".to_string(), vec![8..12]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "If no exclusions match, all files should be returned"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![PathMatcher::new("*.rs").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "Rust exclusion search should give only TypeScript files"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.rs".to_string(), vec![8..12]),
-            ("two.rs".to_string(), vec![8..12]),
-        ]),
-        "TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything"
-    );
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                Vec::new(),
-                vec![
-                    PathMatcher::new("*.rs").unwrap(),
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap(),
-                ],
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap().is_empty(),
-        "Rust and typescript exclusion should give no files, even if other exclusions don't match anything"
-    );
-}
-
-#[gpui::test]
-async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let search_query = "file";
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            "one.rs": r#"// Rust file one"#,
-            "one.ts": r#"// TypeScript file one"#,
-            "two.rs": r#"// Rust file two"#,
-            "two.ts": r#"// TypeScript file two"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.odd").unwrap()],
-                vec![PathMatcher::new("*.odd").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "If both no exclusions and inclusions match, exclusions should win and return nothing"
-    );
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![PathMatcher::new("*.ts").unwrap()],
-                vec![PathMatcher::new("*.ts").unwrap()],
-            ).unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "If both TypeScript exclusions and inclusions match, exclusions should win and return nothing files."
-    );
-
-    assert!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap()
-        .is_empty(),
-        "Non-matching inclusions and exclusions should not change that."
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                search_query,
-                false,
-                true,
-                false,
-                vec![
-                    PathMatcher::new("*.ts").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-                vec![
-                    PathMatcher::new("*.rs").unwrap(),
-                    PathMatcher::new("*.odd").unwrap()
-                ],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("one.ts".to_string(), vec![14..18]),
-            ("two.ts".to_string(), vec![14..18]),
-        ]),
-        "Non-intersecting TypeScript inclusions and Rust exclusions should return TypeScript files"
-    );
-}
-
-#[gpui::test]
-async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
-    init_test(cx);
-
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/dir",
-        json!({
-            ".git": {},
-            ".gitignore": "**/target\n/node_modules\n",
-            "target": {
-                "index.txt": "index_key:index_value"
-            },
-            "node_modules": {
-                "eslint": {
-                    "index.ts": "const eslint_key = 'eslint value'",
-                    "package.json": r#"{ "some_key": "some value" }"#,
-                },
-                "prettier": {
-                    "index.ts": "const prettier_key = 'prettier value'",
-                    "package.json": r#"{ "other_key": "other value" }"#,
-                },
-            },
-            "package.json": r#"{ "main_key": "main value" }"#,
-        }),
-    )
-    .await;
-    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-
-    let query = "key";
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(query, false, false, false, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([("package.json".to_string(), vec![8..11])]),
-        "Only one non-ignored file should have the query"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(query, false, false, true, Vec::new(), Vec::new()).unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([
-            ("package.json".to_string(), vec![8..11]),
-            ("target/index.txt".to_string(), vec![6..9]),
-            (
-                "node_modules/prettier/package.json".to_string(),
-                vec![9..12]
-            ),
-            ("node_modules/prettier/index.ts".to_string(), vec![15..18]),
-            ("node_modules/eslint/index.ts".to_string(), vec![13..16]),
-            ("node_modules/eslint/package.json".to_string(), vec![8..11]),
-        ]),
-        "Unrestricted search with ignored directories should find every file with the query"
-    );
-
-    assert_eq!(
-        search(
-            &project,
-            SearchQuery::text(
-                query,
-                false,
-                false,
-                true,
-                vec![PathMatcher::new("node_modules/prettier/**").unwrap()],
-                vec![PathMatcher::new("*.ts").unwrap()],
-            )
-            .unwrap(),
-            cx
-        )
-        .await
-        .unwrap(),
-        HashMap::from_iter([(
-            "node_modules/prettier/package.json".to_string(),
-            vec![9..12]
-        )]),
-        "With search including ignored prettier directory and excluding TS files, only one file should be found"
-    );
-}
-
-#[test]
-fn test_glob_literal_prefix() {
-    assert_eq!(glob_literal_prefix("**/*.js"), "");
-    assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules");
-    assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo");
-    assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js");
-}
-
-async fn search(
-    project: &Model<Project>,
-    query: SearchQuery,
-    cx: &mut gpui::TestAppContext,
-) -> Result<HashMap<String, Vec<Range<usize>>>> {
-    let mut search_rx = project.update(cx, |project, cx| project.search(query, cx));
-    let mut result = HashMap::default();
-    while let Some((buffer, range)) = search_rx.next().await {
-        result.entry(buffer).or_insert(range);
-    }
-    Ok(result
-        .into_iter()
-        .map(|(buffer, ranges)| {
-            buffer.update(cx, |buffer, _| {
-                let path = buffer.file().unwrap().path().to_string_lossy().to_string();
-                let ranges = ranges
-                    .into_iter()
-                    .map(|range| range.to_offset(buffer))
-                    .collect::<Vec<_>>();
-                (path, ranges)
-            })
-        })
-        .collect())
-}
-
-fn init_test(cx: &mut gpui::TestAppContext) {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::try_init().ok();
-    }
-
-    cx.update(|cx| {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        language::init(cx);
-        Project::init_settings(cx);
-    });
-}

crates/project2/src/search.rs 🔗

@@ -1,463 +0,0 @@
-use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
-use anyhow::{Context, Result};
-use client::proto;
-use itertools::Itertools;
-use language::{char_kind, BufferSnapshot};
-use regex::{Regex, RegexBuilder};
-use smol::future::yield_now;
-use std::{
-    borrow::Cow,
-    io::{BufRead, BufReader, Read},
-    ops::Range,
-    path::Path,
-    sync::Arc,
-};
-use util::paths::PathMatcher;
-
-#[derive(Clone, Debug)]
-pub struct SearchInputs {
-    query: Arc<str>,
-    files_to_include: Vec<PathMatcher>,
-    files_to_exclude: Vec<PathMatcher>,
-}
-
-impl SearchInputs {
-    pub fn as_str(&self) -> &str {
-        self.query.as_ref()
-    }
-    pub fn files_to_include(&self) -> &[PathMatcher] {
-        &self.files_to_include
-    }
-    pub fn files_to_exclude(&self) -> &[PathMatcher] {
-        &self.files_to_exclude
-    }
-}
-#[derive(Clone, Debug)]
-pub enum SearchQuery {
-    Text {
-        search: Arc<AhoCorasick>,
-        replacement: Option<String>,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        inner: SearchInputs,
-    },
-
-    Regex {
-        regex: Regex,
-        replacement: Option<String>,
-        multiline: bool,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        inner: SearchInputs,
-    },
-}
-
-impl SearchQuery {
-    pub fn text(
-        query: impl ToString,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        files_to_include: Vec<PathMatcher>,
-        files_to_exclude: Vec<PathMatcher>,
-    ) -> Result<Self> {
-        let query = query.to_string();
-        let search = AhoCorasickBuilder::new()
-            .ascii_case_insensitive(!case_sensitive)
-            .build(&[&query])?;
-        let inner = SearchInputs {
-            query: query.into(),
-            files_to_exclude,
-            files_to_include,
-        };
-        Ok(Self::Text {
-            search: Arc::new(search),
-            replacement: None,
-            whole_word,
-            case_sensitive,
-            include_ignored,
-            inner,
-        })
-    }
-
-    pub fn regex(
-        query: impl ToString,
-        whole_word: bool,
-        case_sensitive: bool,
-        include_ignored: bool,
-        files_to_include: Vec<PathMatcher>,
-        files_to_exclude: Vec<PathMatcher>,
-    ) -> Result<Self> {
-        let mut query = query.to_string();
-        let initial_query = Arc::from(query.as_str());
-        if whole_word {
-            let mut word_query = String::new();
-            word_query.push_str("\\b");
-            word_query.push_str(&query);
-            word_query.push_str("\\b");
-            query = word_query
-        }
-
-        let multiline = query.contains('\n') || query.contains("\\n");
-        let regex = RegexBuilder::new(&query)
-            .case_insensitive(!case_sensitive)
-            .multi_line(multiline)
-            .build()?;
-        let inner = SearchInputs {
-            query: initial_query,
-            files_to_exclude,
-            files_to_include,
-        };
-        Ok(Self::Regex {
-            regex,
-            replacement: None,
-            multiline,
-            whole_word,
-            case_sensitive,
-            include_ignored,
-            inner,
-        })
-    }
-
-    pub fn from_proto(message: proto::SearchProject) -> Result<Self> {
-        if message.regex {
-            Self::regex(
-                message.query,
-                message.whole_word,
-                message.case_sensitive,
-                message.include_ignored,
-                deserialize_path_matches(&message.files_to_include)?,
-                deserialize_path_matches(&message.files_to_exclude)?,
-            )
-        } else {
-            Self::text(
-                message.query,
-                message.whole_word,
-                message.case_sensitive,
-                message.include_ignored,
-                deserialize_path_matches(&message.files_to_include)?,
-                deserialize_path_matches(&message.files_to_exclude)?,
-            )
-        }
-    }
-    pub fn with_replacement(mut self, new_replacement: String) -> Self {
-        match self {
-            Self::Text {
-                ref mut replacement,
-                ..
-            }
-            | Self::Regex {
-                ref mut replacement,
-                ..
-            } => {
-                *replacement = Some(new_replacement);
-                self
-            }
-        }
-    }
-    pub fn to_proto(&self, project_id: u64) -> proto::SearchProject {
-        proto::SearchProject {
-            project_id,
-            query: self.as_str().to_string(),
-            regex: self.is_regex(),
-            whole_word: self.whole_word(),
-            case_sensitive: self.case_sensitive(),
-            include_ignored: self.include_ignored(),
-            files_to_include: self
-                .files_to_include()
-                .iter()
-                .map(|matcher| matcher.to_string())
-                .join(","),
-            files_to_exclude: self
-                .files_to_exclude()
-                .iter()
-                .map(|matcher| matcher.to_string())
-                .join(","),
-        }
-    }
-
-    pub fn detect<T: Read>(&self, stream: T) -> Result<bool> {
-        if self.as_str().is_empty() {
-            return Ok(false);
-        }
-
-        match self {
-            Self::Text { search, .. } => {
-                let mat = search.stream_find_iter(stream).next();
-                match mat {
-                    Some(Ok(_)) => Ok(true),
-                    Some(Err(err)) => Err(err.into()),
-                    None => Ok(false),
-                }
-            }
-            Self::Regex {
-                regex, multiline, ..
-            } => {
-                let mut reader = BufReader::new(stream);
-                if *multiline {
-                    let mut text = String::new();
-                    if let Err(err) = reader.read_to_string(&mut text) {
-                        Err(err.into())
-                    } else {
-                        Ok(regex.find(&text).is_some())
-                    }
-                } else {
-                    for line in reader.lines() {
-                        let line = line?;
-                        if regex.find(&line).is_some() {
-                            return Ok(true);
-                        }
-                    }
-                    Ok(false)
-                }
-            }
-        }
-    }
-    /// Returns the replacement text for this `SearchQuery`.
-    pub fn replacement(&self) -> Option<&str> {
-        match self {
-            SearchQuery::Text { replacement, .. } | SearchQuery::Regex { replacement, .. } => {
-                replacement.as_deref()
-            }
-        }
-    }
-    /// Replaces search hits if replacement is set. `text` is assumed to be a string that matches this `SearchQuery` exactly, without any leftovers on either side.
-    pub fn replacement_for<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
-        match self {
-            SearchQuery::Text { replacement, .. } => replacement.clone().map(Cow::from),
-            SearchQuery::Regex {
-                regex, replacement, ..
-            } => {
-                if let Some(replacement) = replacement {
-                    Some(regex.replace(text, replacement))
-                } else {
-                    None
-                }
-            }
-        }
-    }
-    pub async fn search(
-        &self,
-        buffer: &BufferSnapshot,
-        subrange: Option<Range<usize>>,
-    ) -> Vec<Range<usize>> {
-        const YIELD_INTERVAL: usize = 20000;
-
-        if self.as_str().is_empty() {
-            return Default::default();
-        }
-
-        let range_offset = subrange.as_ref().map(|r| r.start).unwrap_or(0);
-        let rope = if let Some(range) = subrange {
-            buffer.as_rope().slice(range)
-        } else {
-            buffer.as_rope().clone()
-        };
-
-        let mut matches = Vec::new();
-        match self {
-            Self::Text {
-                search, whole_word, ..
-            } => {
-                for (ix, mat) in search
-                    .stream_find_iter(rope.bytes_in_range(0..rope.len()))
-                    .enumerate()
-                {
-                    if (ix + 1) % YIELD_INTERVAL == 0 {
-                        yield_now().await;
-                    }
-
-                    let mat = mat.unwrap();
-                    if *whole_word {
-                        let scope = buffer.language_scope_at(range_offset + mat.start());
-                        let kind = |c| char_kind(&scope, c);
-
-                        let prev_kind = rope.reversed_chars_at(mat.start()).next().map(kind);
-                        let start_kind = kind(rope.chars_at(mat.start()).next().unwrap());
-                        let end_kind = kind(rope.reversed_chars_at(mat.end()).next().unwrap());
-                        let next_kind = rope.chars_at(mat.end()).next().map(kind);
-                        if Some(start_kind) == prev_kind || Some(end_kind) == next_kind {
-                            continue;
-                        }
-                    }
-                    matches.push(mat.start()..mat.end())
-                }
-            }
-
-            Self::Regex {
-                regex, multiline, ..
-            } => {
-                if *multiline {
-                    let text = rope.to_string();
-                    for (ix, mat) in regex.find_iter(&text).enumerate() {
-                        if (ix + 1) % YIELD_INTERVAL == 0 {
-                            yield_now().await;
-                        }
-
-                        matches.push(mat.start()..mat.end());
-                    }
-                } else {
-                    let mut line = String::new();
-                    let mut line_offset = 0;
-                    for (chunk_ix, chunk) in rope.chunks().chain(["\n"]).enumerate() {
-                        if (chunk_ix + 1) % YIELD_INTERVAL == 0 {
-                            yield_now().await;
-                        }
-
-                        for (newline_ix, text) in chunk.split('\n').enumerate() {
-                            if newline_ix > 0 {
-                                for mat in regex.find_iter(&line) {
-                                    let start = line_offset + mat.start();
-                                    let end = line_offset + mat.end();
-                                    matches.push(start..end);
-                                }
-
-                                line_offset += line.len() + 1;
-                                line.clear();
-                            }
-                            line.push_str(text);
-                        }
-                    }
-                }
-            }
-        }
-
-        matches
-    }
-
-    pub fn as_str(&self) -> &str {
-        self.as_inner().as_str()
-    }
-
-    pub fn whole_word(&self) -> bool {
-        match self {
-            Self::Text { whole_word, .. } => *whole_word,
-            Self::Regex { whole_word, .. } => *whole_word,
-        }
-    }
-
-    pub fn case_sensitive(&self) -> bool {
-        match self {
-            Self::Text { case_sensitive, .. } => *case_sensitive,
-            Self::Regex { case_sensitive, .. } => *case_sensitive,
-        }
-    }
-
-    pub fn include_ignored(&self) -> bool {
-        match self {
-            Self::Text {
-                include_ignored, ..
-            } => *include_ignored,
-            Self::Regex {
-                include_ignored, ..
-            } => *include_ignored,
-        }
-    }
-
-    pub fn is_regex(&self) -> bool {
-        matches!(self, Self::Regex { .. })
-    }
-
-    pub fn files_to_include(&self) -> &[PathMatcher] {
-        self.as_inner().files_to_include()
-    }
-
-    pub fn files_to_exclude(&self) -> &[PathMatcher] {
-        self.as_inner().files_to_exclude()
-    }
-
-    pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
-        match file_path {
-            Some(file_path) => {
-                let mut path = file_path.to_path_buf();
-                loop {
-                    if self
-                        .files_to_exclude()
-                        .iter()
-                        .any(|exclude_glob| exclude_glob.is_match(&path))
-                    {
-                        return false;
-                    } else if self.files_to_include().is_empty()
-                        || self
-                            .files_to_include()
-                            .iter()
-                            .any(|include_glob| include_glob.is_match(&path))
-                    {
-                        return true;
-                    } else if !path.pop() {
-                        return false;
-                    }
-                }
-            }
-            None => self.files_to_include().is_empty(),
-        }
-    }
-    pub fn as_inner(&self) -> &SearchInputs {
-        match self {
-            Self::Regex { inner, .. } | Self::Text { inner, .. } => inner,
-        }
-    }
-}
-
-fn deserialize_path_matches(glob_set: &str) -> anyhow::Result<Vec<PathMatcher>> {
-    glob_set
-        .split(',')
-        .map(str::trim)
-        .filter(|glob_str| !glob_str.is_empty())
-        .map(|glob_str| {
-            PathMatcher::new(glob_str)
-                .with_context(|| format!("deserializing path match glob {glob_str}"))
-        })
-        .collect()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn path_matcher_creation_for_valid_paths() {
-        for valid_path in [
-            "file",
-            "Cargo.toml",
-            ".DS_Store",
-            "~/dir/another_dir/",
-            "./dir/file",
-            "dir/[a-z].txt",
-            "../dir/filé",
-        ] {
-            let path_matcher = PathMatcher::new(valid_path).unwrap_or_else(|e| {
-                panic!("Valid path {valid_path} should be accepted, but got: {e}")
-            });
-            assert!(
-                path_matcher.is_match(valid_path),
-                "Path matcher for valid path {valid_path} should match itself"
-            )
-        }
-    }
-
-    #[test]
-    fn path_matcher_creation_for_globs() {
-        for invalid_glob in ["dir/[].txt", "dir/[a-z.txt", "dir/{file"] {
-            match PathMatcher::new(invalid_glob) {
-                Ok(_) => panic!("Invalid glob {invalid_glob} should not be accepted"),
-                Err(_expected) => {}
-            }
-        }
-
-        for valid_glob in [
-            "dir/?ile",
-            "dir/*.txt",
-            "dir/**/file",
-            "dir/[a-z].txt",
-            "{dir,file}",
-        ] {
-            match PathMatcher::new(valid_glob) {
-                Ok(_expected) => {}
-                Err(e) => panic!("Valid glob {valid_glob} should be accepted, but got: {e}"),
-            }
-        }
-    }
-}

crates/project2/src/terminals.rs 🔗

@@ -1,128 +0,0 @@
-use crate::Project;
-use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
-use settings::Settings;
-use std::path::{Path, PathBuf};
-use terminal::{
-    terminal_settings::{self, TerminalSettings, VenvSettingsContent},
-    Terminal, TerminalBuilder,
-};
-
-#[cfg(target_os = "macos")]
-use std::os::unix::ffi::OsStrExt;
-
-pub struct Terminals {
-    pub(crate) local_handles: Vec<WeakModel<terminal::Terminal>>,
-}
-
-impl Project {
-    pub fn create_terminal(
-        &mut self,
-        working_directory: Option<PathBuf>,
-        window: AnyWindowHandle,
-        cx: &mut ModelContext<Self>,
-    ) -> anyhow::Result<Model<Terminal>> {
-        if self.is_remote() {
-            return Err(anyhow::anyhow!(
-                "creating terminals as a guest is not supported yet"
-            ));
-        } else {
-            let settings = TerminalSettings::get_global(cx);
-            let python_settings = settings.detect_venv.clone();
-            let shell = settings.shell.clone();
-
-            let terminal = TerminalBuilder::new(
-                working_directory.clone(),
-                shell.clone(),
-                settings.env.clone(),
-                Some(settings.blinking.clone()),
-                settings.alternate_scroll,
-                window,
-            )
-            .map(|builder| {
-                let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
-
-                self.terminals
-                    .local_handles
-                    .push(terminal_handle.downgrade());
-
-                let id = terminal_handle.entity_id();
-                cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
-                    let handles = &mut project.terminals.local_handles;
-
-                    if let Some(index) = handles
-                        .iter()
-                        .position(|terminal| terminal.entity_id() == id)
-                    {
-                        handles.remove(index);
-                        cx.notify();
-                    }
-                })
-                .detach();
-
-                if let Some(python_settings) = &python_settings.as_option() {
-                    let activate_script_path =
-                        self.find_activate_script_path(&python_settings, working_directory);
-                    self.activate_python_virtual_environment(
-                        activate_script_path,
-                        &terminal_handle,
-                        cx,
-                    );
-                }
-                terminal_handle
-            });
-
-            terminal
-        }
-    }
-
-    pub fn find_activate_script_path(
-        &mut self,
-        settings: &VenvSettingsContent,
-        working_directory: Option<PathBuf>,
-    ) -> Option<PathBuf> {
-        // When we are unable to resolve the working directory, the terminal builder
-        // defaults to '/'. We should probably encode this directly somewhere, but for
-        // now, let's just hard code it here.
-        let working_directory = working_directory.unwrap_or_else(|| Path::new("/").to_path_buf());
-        let activate_script_name = match settings.activate_script {
-            terminal_settings::ActivateScript::Default => "activate",
-            terminal_settings::ActivateScript::Csh => "activate.csh",
-            terminal_settings::ActivateScript::Fish => "activate.fish",
-            terminal_settings::ActivateScript::Nushell => "activate.nu",
-        };
-
-        for virtual_environment_name in settings.directories {
-            let mut path = working_directory.join(virtual_environment_name);
-            path.push("bin/");
-            path.push(activate_script_name);
-
-            if path.exists() {
-                return Some(path);
-            }
-        }
-
-        None
-    }
-
-    fn activate_python_virtual_environment(
-        &mut self,
-        activate_script: Option<PathBuf>,
-        terminal_handle: &Model<Terminal>,
-        cx: &mut ModelContext<Project>,
-    ) {
-        if let Some(activate_script) = activate_script {
-            // Paths are not strings so we need to jump through some hoops to format the command without `format!`
-            let mut command = Vec::from("source ".as_bytes());
-            command.extend_from_slice(activate_script.as_os_str().as_bytes());
-            command.push(b'\n');
-
-            terminal_handle.update(cx, |this, _| this.input_bytes(command));
-        }
-    }
-
-    pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
-        &self.terminals.local_handles
-    }
-}
-
-// TODO: Add a few tests for adding and removing terminal tabs

crates/project2/src/worktree.rs 🔗

@@ -1,4576 +0,0 @@
-use crate::{
-    copy_recursive, ignore::IgnoreStack, project_settings::ProjectSettings, DiagnosticSummary,
-    ProjectEntryId, RemoveOptions,
-};
-use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
-use anyhow::{anyhow, Context as _, Result};
-use client::{proto, Client};
-use clock::ReplicaId;
-use collections::{HashMap, HashSet, VecDeque};
-use fs::{
-    repository::{GitFileStatus, GitRepository, RepoPath},
-    Fs,
-};
-use futures::{
-    channel::{
-        mpsc::{self, UnboundedSender},
-        oneshot,
-    },
-    select_biased,
-    task::Poll,
-    FutureExt as _, Stream, StreamExt,
-};
-use fuzzy::CharBag;
-use git::{DOT_GIT, GITIGNORE};
-use gpui::{
-    AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
-    Task,
-};
-use itertools::Itertools;
-use language::{
-    proto::{
-        deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending,
-        serialize_version,
-    },
-    Buffer, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint, Unclipped,
-};
-use lsp::LanguageServerId;
-use parking_lot::Mutex;
-use postage::{
-    barrier,
-    prelude::{Sink as _, Stream as _},
-    watch,
-};
-use settings::{Settings, SettingsStore};
-use smol::channel::{self, Sender};
-use std::{
-    any::Any,
-    cmp::{self, Ordering},
-    convert::TryFrom,
-    ffi::OsStr,
-    fmt,
-    future::Future,
-    mem,
-    ops::{AddAssign, Deref, DerefMut, Sub},
-    path::{Path, PathBuf},
-    pin::Pin,
-    sync::{
-        atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
-    },
-    time::{Duration, SystemTime},
-};
-use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
-use util::{
-    paths::{PathMatcher, HOME},
-    ResultExt,
-};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
-pub struct WorktreeId(usize);
-
-pub enum Worktree {
-    Local(LocalWorktree),
-    Remote(RemoteWorktree),
-}
-
-pub struct LocalWorktree {
-    snapshot: LocalSnapshot,
-    scan_requests_tx: channel::Sender<ScanRequest>,
-    path_prefixes_to_scan_tx: channel::Sender<Arc<Path>>,
-    is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
-    _background_scanner_tasks: Vec<Task<()>>,
-    share: Option<ShareState>,
-    diagnostics: HashMap<
-        Arc<Path>,
-        Vec<(
-            LanguageServerId,
-            Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        )>,
-    >,
-    diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
-    client: Arc<Client>,
-    fs: Arc<dyn Fs>,
-    visible: bool,
-}
-
-struct ScanRequest {
-    relative_paths: Vec<Arc<Path>>,
-    done: barrier::Sender,
-}
-
-pub struct RemoteWorktree {
-    snapshot: Snapshot,
-    background_snapshot: Arc<Mutex<Snapshot>>,
-    project_id: u64,
-    client: Arc<Client>,
-    updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
-    snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
-    replica_id: ReplicaId,
-    diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
-    visible: bool,
-    disconnected: bool,
-}
-
-#[derive(Clone)]
-pub struct Snapshot {
-    id: WorktreeId,
-    abs_path: Arc<Path>,
-    root_name: String,
-    root_char_bag: CharBag,
-    entries_by_path: SumTree<Entry>,
-    entries_by_id: SumTree<PathEntry>,
-    repository_entries: TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
-
-    /// A number that increases every time the worktree begins scanning
-    /// a set of paths from the filesystem. This scanning could be caused
-    /// by some operation performed on the worktree, such as reading or
-    /// writing a file, or by an event reported by the filesystem.
-    scan_id: usize,
-
-    /// The latest scan id that has completed, and whose preceding scans
-    /// have all completed. The current `scan_id` could be more than one
-    /// greater than the `completed_scan_id` if operations are performed
-    /// on the worktree while it is processing a file-system event.
-    completed_scan_id: usize,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RepositoryEntry {
-    pub(crate) work_directory: WorkDirectoryEntry,
-    pub(crate) branch: Option<Arc<str>>,
-}
-
-impl RepositoryEntry {
-    pub fn branch(&self) -> Option<Arc<str>> {
-        self.branch.clone()
-    }
-
-    pub fn work_directory_id(&self) -> ProjectEntryId {
-        *self.work_directory
-    }
-
-    pub fn work_directory(&self, snapshot: &Snapshot) -> Option<RepositoryWorkDirectory> {
-        snapshot
-            .entry_for_id(self.work_directory_id())
-            .map(|entry| RepositoryWorkDirectory(entry.path.clone()))
-    }
-
-    pub fn build_update(&self, _: &Self) -> proto::RepositoryEntry {
-        proto::RepositoryEntry {
-            work_directory_id: self.work_directory_id().to_proto(),
-            branch: self.branch.as_ref().map(|str| str.to_string()),
-        }
-    }
-}
-
-impl From<&RepositoryEntry> for proto::RepositoryEntry {
-    fn from(value: &RepositoryEntry) -> Self {
-        proto::RepositoryEntry {
-            work_directory_id: value.work_directory.to_proto(),
-            branch: value.branch.as_ref().map(|str| str.to_string()),
-        }
-    }
-}
-
-/// This path corresponds to the 'content path' (the folder that contains the .git)
-#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
-pub struct RepositoryWorkDirectory(pub(crate) Arc<Path>);
-
-impl Default for RepositoryWorkDirectory {
-    fn default() -> Self {
-        RepositoryWorkDirectory(Arc::from(Path::new("")))
-    }
-}
-
-impl AsRef<Path> for RepositoryWorkDirectory {
-    fn as_ref(&self) -> &Path {
-        self.0.as_ref()
-    }
-}
-
-#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
-pub struct WorkDirectoryEntry(ProjectEntryId);
-
-impl WorkDirectoryEntry {
-    pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Option<RepoPath> {
-        worktree.entry_for_id(self.0).and_then(|entry| {
-            path.strip_prefix(&entry.path)
-                .ok()
-                .map(move |path| path.into())
-        })
-    }
-}
-
-impl Deref for WorkDirectoryEntry {
-    type Target = ProjectEntryId;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl<'a> From<ProjectEntryId> for WorkDirectoryEntry {
-    fn from(value: ProjectEntryId) -> Self {
-        WorkDirectoryEntry(value)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct LocalSnapshot {
-    snapshot: Snapshot,
-    /// All of the gitignore files in the worktree, indexed by their relative path.
-    /// The boolean indicates whether the gitignore needs to be updated.
-    ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, bool)>,
-    /// All of the git repositories in the worktree, indexed by the project entry
-    /// id of their parent directory.
-    git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>,
-    file_scan_exclusions: Vec<PathMatcher>,
-}
-
-struct BackgroundScannerState {
-    snapshot: LocalSnapshot,
-    scanned_dirs: HashSet<ProjectEntryId>,
-    path_prefixes_to_scan: HashSet<Arc<Path>>,
-    paths_to_scan: HashSet<Arc<Path>>,
-    /// The ids of all of the entries that were removed from the snapshot
-    /// as part of the current update. These entry ids may be re-used
-    /// if the same inode is discovered at a new path, or if the given
-    /// path is re-created after being deleted.
-    removed_entry_ids: HashMap<u64, ProjectEntryId>,
-    changed_paths: Vec<Arc<Path>>,
-    prev_snapshot: Snapshot,
-}
-
-#[derive(Debug, Clone)]
-pub struct LocalRepositoryEntry {
-    pub(crate) git_dir_scan_id: usize,
-    pub(crate) repo_ptr: Arc<Mutex<dyn GitRepository>>,
-    /// Path to the actual .git folder.
-    /// Note: if .git is a file, this points to the folder indicated by the .git file
-    pub(crate) git_dir_path: Arc<Path>,
-}
-
-impl Deref for LocalSnapshot {
-    type Target = Snapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl DerefMut for LocalSnapshot {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.snapshot
-    }
-}
-
-enum ScanState {
-    Started,
-    Updated {
-        snapshot: LocalSnapshot,
-        changes: UpdatedEntriesSet,
-        barrier: Option<barrier::Sender>,
-        scanning: bool,
-    },
-}
-
-struct ShareState {
-    project_id: u64,
-    snapshots_tx:
-        mpsc::UnboundedSender<(LocalSnapshot, UpdatedEntriesSet, UpdatedGitRepositoriesSet)>,
-    resume_updates: watch::Sender<()>,
-    _maintain_remote_snapshot: Task<Option<()>>,
-}
-
-#[derive(Clone)]
-pub enum Event {
-    UpdatedEntries(UpdatedEntriesSet),
-    UpdatedGitRepositories(UpdatedGitRepositoriesSet),
-}
-
-impl EventEmitter<Event> for Worktree {}
-
-impl Worktree {
-    pub async fn local(
-        client: Arc<Client>,
-        path: impl Into<Arc<Path>>,
-        visible: bool,
-        fs: Arc<dyn Fs>,
-        next_entry_id: Arc<AtomicUsize>,
-        cx: &mut AsyncAppContext,
-    ) -> Result<Model<Self>> {
-        // After determining whether the root entry is a file or a directory, populate the
-        // snapshot's "root name", which will be used for the purpose of fuzzy matching.
-        let abs_path = path.into();
-
-        let metadata = fs
-            .metadata(&abs_path)
-            .await
-            .context("failed to stat worktree path")?;
-
-        let closure_fs = Arc::clone(&fs);
-        let closure_next_entry_id = Arc::clone(&next_entry_id);
-        let closure_abs_path = abs_path.to_path_buf();
-        cx.new_model(move |cx: &mut ModelContext<Worktree>| {
-            cx.observe_global::<SettingsStore>(move |this, cx| {
-                if let Self::Local(this) = this {
-                    let new_file_scan_exclusions =
-                        file_scan_exclusions(ProjectSettings::get_global(cx));
-                    if new_file_scan_exclusions != this.snapshot.file_scan_exclusions {
-                        this.snapshot.file_scan_exclusions = new_file_scan_exclusions;
-                        log::info!(
-                            "Re-scanning directories, new scan exclude files: {:?}",
-                            this.snapshot
-                                .file_scan_exclusions
-                                .iter()
-                                .map(ToString::to_string)
-                                .collect::<Vec<_>>()
-                        );
-
-                        let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
-                        let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) =
-                            channel::unbounded();
-                        this.scan_requests_tx = scan_requests_tx;
-                        this.path_prefixes_to_scan_tx = path_prefixes_to_scan_tx;
-                        this._background_scanner_tasks = start_background_scan_tasks(
-                            &closure_abs_path,
-                            this.snapshot(),
-                            scan_requests_rx,
-                            path_prefixes_to_scan_rx,
-                            Arc::clone(&closure_next_entry_id),
-                            Arc::clone(&closure_fs),
-                            cx,
-                        );
-                        this.is_scanning = watch::channel_with(true);
-                    }
-                }
-            })
-            .detach();
-
-            let root_name = abs_path
-                .file_name()
-                .map_or(String::new(), |f| f.to_string_lossy().to_string());
-
-            let mut snapshot = LocalSnapshot {
-                file_scan_exclusions: file_scan_exclusions(ProjectSettings::get_global(cx)),
-                ignores_by_parent_abs_path: Default::default(),
-                git_repositories: Default::default(),
-                snapshot: Snapshot {
-                    id: WorktreeId::from_usize(cx.entity_id().as_u64() as usize),
-                    abs_path: abs_path.to_path_buf().into(),
-                    root_name: root_name.clone(),
-                    root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
-                    entries_by_path: Default::default(),
-                    entries_by_id: Default::default(),
-                    repository_entries: Default::default(),
-                    scan_id: 1,
-                    completed_scan_id: 0,
-                },
-            };
-
-            if let Some(metadata) = metadata {
-                snapshot.insert_entry(
-                    Entry::new(
-                        Arc::from(Path::new("")),
-                        &metadata,
-                        &next_entry_id,
-                        snapshot.root_char_bag,
-                    ),
-                    fs.as_ref(),
-                );
-            }
-
-            let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
-            let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
-            let task_snapshot = snapshot.clone();
-            Worktree::Local(LocalWorktree {
-                snapshot,
-                is_scanning: watch::channel_with(true),
-                share: None,
-                scan_requests_tx,
-                path_prefixes_to_scan_tx,
-                _background_scanner_tasks: start_background_scan_tasks(
-                    &abs_path,
-                    task_snapshot,
-                    scan_requests_rx,
-                    path_prefixes_to_scan_rx,
-                    Arc::clone(&next_entry_id),
-                    Arc::clone(&fs),
-                    cx,
-                ),
-                diagnostics: Default::default(),
-                diagnostic_summaries: Default::default(),
-                client,
-                fs,
-                visible,
-            })
-        })
-    }
-
-    pub fn remote(
-        project_remote_id: u64,
-        replica_id: ReplicaId,
-        worktree: proto::WorktreeMetadata,
-        client: Arc<Client>,
-        cx: &mut AppContext,
-    ) -> Model<Self> {
-        cx.new_model(|cx: &mut ModelContext<Self>| {
-            let snapshot = Snapshot {
-                id: WorktreeId(worktree.id as usize),
-                abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
-                root_name: worktree.root_name.clone(),
-                root_char_bag: worktree
-                    .root_name
-                    .chars()
-                    .map(|c| c.to_ascii_lowercase())
-                    .collect(),
-                entries_by_path: Default::default(),
-                entries_by_id: Default::default(),
-                repository_entries: Default::default(),
-                scan_id: 1,
-                completed_scan_id: 0,
-            };
-
-            let (updates_tx, mut updates_rx) = mpsc::unbounded();
-            let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
-            let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
-
-            cx.background_executor()
-                .spawn({
-                    let background_snapshot = background_snapshot.clone();
-                    async move {
-                        while let Some(update) = updates_rx.next().await {
-                            if let Err(error) =
-                                background_snapshot.lock().apply_remote_update(update)
-                            {
-                                log::error!("error applying worktree update: {}", error);
-                            }
-                            snapshot_updated_tx.send(()).await.ok();
-                        }
-                    }
-                })
-                .detach();
-
-            cx.spawn(|this, mut cx| async move {
-                while (snapshot_updated_rx.recv().await).is_some() {
-                    this.update(&mut cx, |this, cx| {
-                        let this = this.as_remote_mut().unwrap();
-                        this.snapshot = this.background_snapshot.lock().clone();
-                        cx.emit(Event::UpdatedEntries(Arc::from([])));
-                        cx.notify();
-                        while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
-                            if this.observed_snapshot(*scan_id) {
-                                let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
-                                let _ = tx.send(());
-                            } else {
-                                break;
-                            }
-                        }
-                    })?;
-                }
-                anyhow::Ok(())
-            })
-            .detach();
-
-            Worktree::Remote(RemoteWorktree {
-                project_id: project_remote_id,
-                replica_id,
-                snapshot: snapshot.clone(),
-                background_snapshot,
-                updates_tx: Some(updates_tx),
-                snapshot_subscriptions: Default::default(),
-                client: client.clone(),
-                diagnostic_summaries: Default::default(),
-                visible: worktree.visible,
-                disconnected: false,
-            })
-        })
-    }
-
-    pub fn as_local(&self) -> Option<&LocalWorktree> {
-        if let Worktree::Local(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn as_remote(&self) -> Option<&RemoteWorktree> {
-        if let Worktree::Remote(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> {
-        if let Worktree::Local(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn as_remote_mut(&mut self) -> Option<&mut RemoteWorktree> {
-        if let Worktree::Remote(worktree) = self {
-            Some(worktree)
-        } else {
-            None
-        }
-    }
-
-    pub fn is_local(&self) -> bool {
-        matches!(self, Worktree::Local(_))
-    }
-
-    pub fn is_remote(&self) -> bool {
-        !self.is_local()
-    }
-
-    pub fn snapshot(&self) -> Snapshot {
-        match self {
-            Worktree::Local(worktree) => worktree.snapshot().snapshot,
-            Worktree::Remote(worktree) => worktree.snapshot(),
-        }
-    }
-
-    pub fn scan_id(&self) -> usize {
-        match self {
-            Worktree::Local(worktree) => worktree.snapshot.scan_id,
-            Worktree::Remote(worktree) => worktree.snapshot.scan_id,
-        }
-    }
-
-    pub fn completed_scan_id(&self) -> usize {
-        match self {
-            Worktree::Local(worktree) => worktree.snapshot.completed_scan_id,
-            Worktree::Remote(worktree) => worktree.snapshot.completed_scan_id,
-        }
-    }
-
-    pub fn is_visible(&self) -> bool {
-        match self {
-            Worktree::Local(worktree) => worktree.visible,
-            Worktree::Remote(worktree) => worktree.visible,
-        }
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        match self {
-            Worktree::Local(_) => 0,
-            Worktree::Remote(worktree) => worktree.replica_id,
-        }
-    }
-
-    pub fn diagnostic_summaries(
-        &self,
-    ) -> impl Iterator<Item = (Arc<Path>, LanguageServerId, DiagnosticSummary)> + '_ {
-        match self {
-            Worktree::Local(worktree) => &worktree.diagnostic_summaries,
-            Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
-        }
-        .iter()
-        .flat_map(|(path, summaries)| {
-            summaries
-                .iter()
-                .map(move |(&server_id, &summary)| (path.clone(), server_id, summary))
-        })
-    }
-
-    pub fn abs_path(&self) -> Arc<Path> {
-        match self {
-            Worktree::Local(worktree) => worktree.abs_path.clone(),
-            Worktree::Remote(worktree) => worktree.abs_path.clone(),
-        }
-    }
-
-    pub fn root_file(&self, cx: &mut ModelContext<Self>) -> Option<Arc<File>> {
-        let entry = self.root_entry()?;
-        Some(File::for_entry(entry.clone(), cx.handle()))
-    }
-}
-
-fn start_background_scan_tasks(
-    abs_path: &Path,
-    snapshot: LocalSnapshot,
-    scan_requests_rx: channel::Receiver<ScanRequest>,
-    path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
-    next_entry_id: Arc<AtomicUsize>,
-    fs: Arc<dyn Fs>,
-    cx: &mut ModelContext<'_, Worktree>,
-) -> Vec<Task<()>> {
-    let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
-    let background_scanner = cx.background_executor().spawn({
-        let abs_path = abs_path.to_path_buf();
-        let background = cx.background_executor().clone();
-        async move {
-            let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
-            BackgroundScanner::new(
-                snapshot,
-                next_entry_id,
-                fs,
-                scan_states_tx,
-                background,
-                scan_requests_rx,
-                path_prefixes_to_scan_rx,
-            )
-            .run(events)
-            .await;
-        }
-    });
-    let scan_state_updater = cx.spawn(|this, mut cx| async move {
-        while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade()) {
-            this.update(&mut cx, |this, cx| {
-                let this = this.as_local_mut().unwrap();
-                match state {
-                    ScanState::Started => {
-                        *this.is_scanning.0.borrow_mut() = true;
-                    }
-                    ScanState::Updated {
-                        snapshot,
-                        changes,
-                        barrier,
-                        scanning,
-                    } => {
-                        *this.is_scanning.0.borrow_mut() = scanning;
-                        this.set_snapshot(snapshot, changes, cx);
-                        drop(barrier);
-                    }
-                }
-                cx.notify();
-            })
-            .ok();
-        }
-    });
-    vec![background_scanner, scan_state_updater]
-}
-
-fn file_scan_exclusions(project_settings: &ProjectSettings) -> Vec<PathMatcher> {
-    project_settings.file_scan_exclusions.as_deref().unwrap_or(&[]).iter()
-    .sorted()
-    .filter_map(|pattern| {
-        PathMatcher::new(pattern)
-            .map(Some)
-            .unwrap_or_else(|e| {
-                log::error!(
-                    "Skipping pattern {pattern} in `file_scan_exclusions` project settings due to parsing error: {e:#}"
-                );
-                None
-            })
-    })
-    .collect()
-}
-
-impl LocalWorktree {
-    pub fn contains_abs_path(&self, path: &Path) -> bool {
-        path.starts_with(&self.abs_path)
-    }
-
-    pub(crate) fn load_buffer(
-        &mut self,
-        id: u64,
-        path: &Path,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Model<Buffer>>> {
-        let path = Arc::from(path);
-        cx.spawn(move |this, mut cx| async move {
-            let (file, contents, diff_base) = this
-                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))?
-                .await?;
-            let text_buffer = cx
-                .background_executor()
-                .spawn(async move { text::Buffer::new(0, id, contents) })
-                .await;
-            cx.new_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
-        })
-    }
-
-    pub fn diagnostics_for_path(
-        &self,
-        path: &Path,
-    ) -> Vec<(
-        LanguageServerId,
-        Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-    )> {
-        self.diagnostics.get(path).cloned().unwrap_or_default()
-    }
-
-    pub fn clear_diagnostics_for_language_server(
-        &mut self,
-        server_id: LanguageServerId,
-        _: &mut ModelContext<Worktree>,
-    ) {
-        let worktree_id = self.id().to_proto();
-        self.diagnostic_summaries
-            .retain(|path, summaries_by_server_id| {
-                if summaries_by_server_id.remove(&server_id).is_some() {
-                    if let Some(share) = self.share.as_ref() {
-                        self.client
-                            .send(proto::UpdateDiagnosticSummary {
-                                project_id: share.project_id,
-                                worktree_id,
-                                summary: Some(proto::DiagnosticSummary {
-                                    path: path.to_string_lossy().to_string(),
-                                    language_server_id: server_id.0 as u64,
-                                    error_count: 0,
-                                    warning_count: 0,
-                                }),
-                            })
-                            .log_err();
-                    }
-                    !summaries_by_server_id.is_empty()
-                } else {
-                    true
-                }
-            });
-
-        self.diagnostics.retain(|_, diagnostics_by_server_id| {
-            if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
-                diagnostics_by_server_id.remove(ix);
-                !diagnostics_by_server_id.is_empty()
-            } else {
-                true
-            }
-        });
-    }
-
-    pub fn update_diagnostics(
-        &mut self,
-        server_id: LanguageServerId,
-        worktree_path: Arc<Path>,
-        diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
-        _: &mut ModelContext<Worktree>,
-    ) -> Result<bool> {
-        let summaries_by_server_id = self
-            .diagnostic_summaries
-            .entry(worktree_path.clone())
-            .or_default();
-
-        let old_summary = summaries_by_server_id
-            .remove(&server_id)
-            .unwrap_or_default();
-
-        let new_summary = DiagnosticSummary::new(&diagnostics);
-        if new_summary.is_empty() {
-            if let Some(diagnostics_by_server_id) = self.diagnostics.get_mut(&worktree_path) {
-                if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
-                    diagnostics_by_server_id.remove(ix);
-                }
-                if diagnostics_by_server_id.is_empty() {
-                    self.diagnostics.remove(&worktree_path);
-                }
-            }
-        } else {
-            summaries_by_server_id.insert(server_id, new_summary);
-            let diagnostics_by_server_id =
-                self.diagnostics.entry(worktree_path.clone()).or_default();
-            match diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
-                Ok(ix) => {
-                    diagnostics_by_server_id[ix] = (server_id, diagnostics);
-                }
-                Err(ix) => {
-                    diagnostics_by_server_id.insert(ix, (server_id, diagnostics));
-                }
-            }
-        }
-
-        if !old_summary.is_empty() || !new_summary.is_empty() {
-            if let Some(share) = self.share.as_ref() {
-                self.client
-                    .send(proto::UpdateDiagnosticSummary {
-                        project_id: share.project_id,
-                        worktree_id: self.id().to_proto(),
-                        summary: Some(proto::DiagnosticSummary {
-                            path: worktree_path.to_string_lossy().to_string(),
-                            language_server_id: server_id.0 as u64,
-                            error_count: new_summary.error_count as u32,
-                            warning_count: new_summary.warning_count as u32,
-                        }),
-                    })
-                    .log_err();
-            }
-        }
-
-        Ok(!old_summary.is_empty() || !new_summary.is_empty())
-    }
-
-    fn set_snapshot(
-        &mut self,
-        new_snapshot: LocalSnapshot,
-        entry_changes: UpdatedEntriesSet,
-        cx: &mut ModelContext<Worktree>,
-    ) {
-        let repo_changes = self.changed_repos(&self.snapshot, &new_snapshot);
-
-        self.snapshot = new_snapshot;
-
-        if let Some(share) = self.share.as_mut() {
-            share
-                .snapshots_tx
-                .unbounded_send((
-                    self.snapshot.clone(),
-                    entry_changes.clone(),
-                    repo_changes.clone(),
-                ))
-                .ok();
-        }
-
-        if !entry_changes.is_empty() {
-            cx.emit(Event::UpdatedEntries(entry_changes));
-        }
-        if !repo_changes.is_empty() {
-            cx.emit(Event::UpdatedGitRepositories(repo_changes));
-        }
-    }
-
-    fn changed_repos(
-        &self,
-        old_snapshot: &LocalSnapshot,
-        new_snapshot: &LocalSnapshot,
-    ) -> UpdatedGitRepositoriesSet {
-        let mut changes = Vec::new();
-        let mut old_repos = old_snapshot.git_repositories.iter().peekable();
-        let mut new_repos = new_snapshot.git_repositories.iter().peekable();
-        loop {
-            match (new_repos.peek().map(clone), old_repos.peek().map(clone)) {
-                (Some((new_entry_id, new_repo)), Some((old_entry_id, old_repo))) => {
-                    match Ord::cmp(&new_entry_id, &old_entry_id) {
-                        Ordering::Less => {
-                            if let Some(entry) = new_snapshot.entry_for_id(new_entry_id) {
-                                changes.push((
-                                    entry.path.clone(),
-                                    GitRepositoryChange {
-                                        old_repository: None,
-                                    },
-                                ));
-                            }
-                            new_repos.next();
-                        }
-                        Ordering::Equal => {
-                            if new_repo.git_dir_scan_id != old_repo.git_dir_scan_id {
-                                if let Some(entry) = new_snapshot.entry_for_id(new_entry_id) {
-                                    let old_repo = old_snapshot
-                                        .repository_entries
-                                        .get(&RepositoryWorkDirectory(entry.path.clone()))
-                                        .cloned();
-                                    changes.push((
-                                        entry.path.clone(),
-                                        GitRepositoryChange {
-                                            old_repository: old_repo,
-                                        },
-                                    ));
-                                }
-                            }
-                            new_repos.next();
-                            old_repos.next();
-                        }
-                        Ordering::Greater => {
-                            if let Some(entry) = old_snapshot.entry_for_id(old_entry_id) {
-                                let old_repo = old_snapshot
-                                    .repository_entries
-                                    .get(&RepositoryWorkDirectory(entry.path.clone()))
-                                    .cloned();
-                                changes.push((
-                                    entry.path.clone(),
-                                    GitRepositoryChange {
-                                        old_repository: old_repo,
-                                    },
-                                ));
-                            }
-                            old_repos.next();
-                        }
-                    }
-                }
-                (Some((entry_id, _)), None) => {
-                    if let Some(entry) = new_snapshot.entry_for_id(entry_id) {
-                        changes.push((
-                            entry.path.clone(),
-                            GitRepositoryChange {
-                                old_repository: None,
-                            },
-                        ));
-                    }
-                    new_repos.next();
-                }
-                (None, Some((entry_id, _))) => {
-                    if let Some(entry) = old_snapshot.entry_for_id(entry_id) {
-                        let old_repo = old_snapshot
-                            .repository_entries
-                            .get(&RepositoryWorkDirectory(entry.path.clone()))
-                            .cloned();
-                        changes.push((
-                            entry.path.clone(),
-                            GitRepositoryChange {
-                                old_repository: old_repo,
-                            },
-                        ));
-                    }
-                    old_repos.next();
-                }
-                (None, None) => break,
-            }
-        }
-
-        fn clone<T: Clone, U: Clone>(value: &(&T, &U)) -> (T, U) {
-            (value.0.clone(), value.1.clone())
-        }
-
-        changes.into()
-    }
-
-    pub fn scan_complete(&self) -> impl Future<Output = ()> {
-        let mut is_scanning_rx = self.is_scanning.1.clone();
-        async move {
-            let mut is_scanning = is_scanning_rx.borrow().clone();
-            while is_scanning {
-                if let Some(value) = is_scanning_rx.recv().await {
-                    is_scanning = value;
-                } else {
-                    break;
-                }
-            }
-        }
-    }
-
-    pub fn snapshot(&self) -> LocalSnapshot {
-        self.snapshot.clone()
-    }
-
-    pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
-        proto::WorktreeMetadata {
-            id: self.id().to_proto(),
-            root_name: self.root_name().to_string(),
-            visible: self.visible,
-            abs_path: self.abs_path().as_os_str().to_string_lossy().into(),
-        }
-    }
-
-    fn load(
-        &self,
-        path: &Path,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<(File, String, Option<String>)>> {
-        let path = Arc::from(path);
-        let abs_path = self.absolutize(&path);
-        let fs = self.fs.clone();
-        let entry = self.refresh_entry(path.clone(), None, cx);
-
-        cx.spawn(|this, mut cx| async move {
-            let text = fs.load(&abs_path).await?;
-            let mut index_task = None;
-            let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?;
-            if let Some(repo) = snapshot.repository_for_path(&path) {
-                let repo_path = repo.work_directory.relativize(&snapshot, &path).unwrap();
-                if let Some(repo) = snapshot.git_repositories.get(&*repo.work_directory) {
-                    let repo = repo.repo_ptr.clone();
-                    index_task = Some(
-                        cx.background_executor()
-                            .spawn(async move { repo.lock().load_index_text(&repo_path) }),
-                    );
-                }
-            }
-
-            let diff_base = if let Some(index_task) = index_task {
-                index_task.await
-            } else {
-                None
-            };
-
-            let worktree = this
-                .upgrade()
-                .ok_or_else(|| anyhow!("worktree was dropped"))?;
-            match entry.await? {
-                Some(entry) => Ok((
-                    File {
-                        entry_id: Some(entry.id),
-                        worktree,
-                        path: entry.path,
-                        mtime: entry.mtime,
-                        is_local: true,
-                        is_deleted: false,
-                    },
-                    text,
-                    diff_base,
-                )),
-                None => {
-                    let metadata = fs
-                        .metadata(&abs_path)
-                        .await
-                        .with_context(|| {
-                            format!("Loading metadata for excluded file {abs_path:?}")
-                        })?
-                        .with_context(|| {
-                            format!("Excluded file {abs_path:?} got removed during loading")
-                        })?;
-                    Ok((
-                        File {
-                            entry_id: None,
-                            worktree,
-                            path,
-                            mtime: metadata.mtime,
-                            is_local: true,
-                            is_deleted: false,
-                        },
-                        text,
-                        diff_base,
-                    ))
-                }
-            }
-        })
-    }
-
-    pub fn save_buffer(
-        &self,
-        buffer_handle: Model<Buffer>,
-        path: Arc<Path>,
-        has_changed_file: bool,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<()>> {
-        let buffer = buffer_handle.read(cx);
-
-        let rpc = self.client.clone();
-        let buffer_id = buffer.remote_id();
-        let project_id = self.share.as_ref().map(|share| share.project_id);
-
-        let text = buffer.as_rope().clone();
-        let fingerprint = text.fingerprint();
-        let version = buffer.version();
-        let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx);
-        let fs = Arc::clone(&self.fs);
-        let abs_path = self.absolutize(&path);
-
-        cx.spawn(move |this, mut cx| async move {
-            let entry = save.await?;
-            let this = this.upgrade().context("worktree dropped")?;
-
-            let (entry_id, mtime, path) = match entry {
-                Some(entry) => (Some(entry.id), entry.mtime, entry.path),
-                None => {
-                    let metadata = fs
-                        .metadata(&abs_path)
-                        .await
-                        .with_context(|| {
-                            format!(
-                                "Fetching metadata after saving the excluded buffer {abs_path:?}"
-                            )
-                        })?
-                        .with_context(|| {
-                            format!("Excluded buffer {path:?} got removed during saving")
-                        })?;
-                    (None, metadata.mtime, path)
-                }
-            };
-
-            if has_changed_file {
-                let new_file = Arc::new(File {
-                    entry_id,
-                    worktree: this,
-                    path,
-                    mtime,
-                    is_local: true,
-                    is_deleted: false,
-                });
-
-                if let Some(project_id) = project_id {
-                    rpc.send(proto::UpdateBufferFile {
-                        project_id,
-                        buffer_id,
-                        file: Some(new_file.to_proto()),
-                    })
-                    .log_err();
-                }
-
-                buffer_handle.update(&mut cx, |buffer, cx| {
-                    if has_changed_file {
-                        buffer.file_updated(new_file, cx);
-                    }
-                })?;
-            }
-
-            if let Some(project_id) = project_id {
-                rpc.send(proto::BufferSaved {
-                    project_id,
-                    buffer_id,
-                    version: serialize_version(&version),
-                    mtime: Some(mtime.into()),
-                    fingerprint: serialize_fingerprint(fingerprint),
-                })?;
-            }
-
-            buffer_handle.update(&mut cx, |buffer, cx| {
-                buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            })?;
-
-            Ok(())
-        })
-    }
-
-    /// Find the lowest path in the worktree's datastructures that is an ancestor
-    fn lowest_ancestor(&self, path: &Path) -> PathBuf {
-        let mut lowest_ancestor = None;
-        for path in path.ancestors() {
-            if self.entry_for_path(path).is_some() {
-                lowest_ancestor = Some(path.to_path_buf());
-                break;
-            }
-        }
-
-        lowest_ancestor.unwrap_or_else(|| PathBuf::from(""))
-    }
-
-    pub fn create_entry(
-        &self,
-        path: impl Into<Arc<Path>>,
-        is_dir: bool,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let path = path.into();
-        let lowest_ancestor = self.lowest_ancestor(&path);
-        let abs_path = self.absolutize(&path);
-        let fs = self.fs.clone();
-        let write = cx.background_executor().spawn(async move {
-            if is_dir {
-                fs.create_dir(&abs_path).await
-            } else {
-                fs.save(&abs_path, &Default::default(), Default::default())
-                    .await
-            }
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            write.await?;
-            let (result, refreshes) = this.update(&mut cx, |this, cx| {
-                let mut refreshes = Vec::new();
-                let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
-                for refresh_path in refresh_paths.ancestors() {
-                    if refresh_path == Path::new("") {
-                        continue;
-                    }
-                    let refresh_full_path = lowest_ancestor.join(refresh_path);
-
-                    refreshes.push(this.as_local_mut().unwrap().refresh_entry(
-                        refresh_full_path.into(),
-                        None,
-                        cx,
-                    ));
-                }
-                (
-                    this.as_local_mut().unwrap().refresh_entry(path, None, cx),
-                    refreshes,
-                )
-            })?;
-            for refresh in refreshes {
-                refresh.await.log_err();
-            }
-
-            result.await
-        })
-    }
-
-    pub(crate) fn write_file(
-        &self,
-        path: impl Into<Arc<Path>>,
-        text: Rope,
-        line_ending: LineEnding,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let path: Arc<Path> = path.into();
-        let abs_path = self.absolutize(&path);
-        let fs = self.fs.clone();
-        let write = cx
-            .background_executor()
-            .spawn(async move { fs.save(&abs_path, &text, line_ending).await });
-
-        cx.spawn(|this, mut cx| async move {
-            write.await?;
-            this.update(&mut cx, |this, cx| {
-                this.as_local_mut().unwrap().refresh_entry(path, None, cx)
-            })?
-            .await
-        })
-    }
-
-    pub fn delete_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Option<Task<Result<()>>> {
-        let entry = self.entry_for_id(entry_id)?.clone();
-        let abs_path = self.absolutize(&entry.path);
-        let fs = self.fs.clone();
-
-        let delete = cx.background_executor().spawn(async move {
-            if entry.is_file() {
-                fs.remove_file(&abs_path, Default::default()).await?;
-            } else {
-                fs.remove_dir(
-                    &abs_path,
-                    RemoveOptions {
-                        recursive: true,
-                        ignore_if_not_exists: false,
-                    },
-                )
-                .await?;
-            }
-            anyhow::Ok(entry.path)
-        });
-
-        Some(cx.spawn(|this, mut cx| async move {
-            let path = delete.await?;
-            this.update(&mut cx, |this, _| {
-                this.as_local_mut()
-                    .unwrap()
-                    .refresh_entries_for_paths(vec![path])
-            })?
-            .recv()
-            .await;
-            Ok(())
-        }))
-    }
-
-    pub fn rename_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let old_path = match self.entry_for_id(entry_id) {
-            Some(entry) => entry.path.clone(),
-            None => return Task::ready(Ok(None)),
-        };
-        let new_path = new_path.into();
-        let abs_old_path = self.absolutize(&old_path);
-        let abs_new_path = self.absolutize(&new_path);
-        let fs = self.fs.clone();
-        let rename = cx.background_executor().spawn(async move {
-            fs.rename(&abs_old_path, &abs_new_path, Default::default())
-                .await
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            rename.await?;
-            this.update(&mut cx, |this, cx| {
-                this.as_local_mut()
-                    .unwrap()
-                    .refresh_entry(new_path.clone(), Some(old_path), cx)
-            })?
-            .await
-        })
-    }
-
-    pub fn copy_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        new_path: impl Into<Arc<Path>>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        let old_path = match self.entry_for_id(entry_id) {
-            Some(entry) => entry.path.clone(),
-            None => return Task::ready(Ok(None)),
-        };
-        let new_path = new_path.into();
-        let abs_old_path = self.absolutize(&old_path);
-        let abs_new_path = self.absolutize(&new_path);
-        let fs = self.fs.clone();
-        let copy = cx.background_executor().spawn(async move {
-            copy_recursive(
-                fs.as_ref(),
-                &abs_old_path,
-                &abs_new_path,
-                Default::default(),
-            )
-            .await
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            copy.await?;
-            this.update(&mut cx, |this, cx| {
-                this.as_local_mut()
-                    .unwrap()
-                    .refresh_entry(new_path.clone(), None, cx)
-            })?
-            .await
-        })
-    }
-
-    pub fn expand_entry(
-        &mut self,
-        entry_id: ProjectEntryId,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Option<Task<Result<()>>> {
-        let path = self.entry_for_id(entry_id)?.path.clone();
-        let mut refresh = self.refresh_entries_for_paths(vec![path]);
-        Some(cx.background_executor().spawn(async move {
-            refresh.next().await;
-            Ok(())
-        }))
-    }
-
-    pub fn refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
-        let (tx, rx) = barrier::channel();
-        self.scan_requests_tx
-            .try_send(ScanRequest {
-                relative_paths: paths,
-                done: tx,
-            })
-            .ok();
-        rx
-    }
-
-    pub fn add_path_prefix_to_scan(&self, path_prefix: Arc<Path>) {
-        self.path_prefixes_to_scan_tx.try_send(path_prefix).ok();
-    }
-
-    fn refresh_entry(
-        &self,
-        path: Arc<Path>,
-        old_path: Option<Arc<Path>>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Option<Entry>>> {
-        if self.is_path_excluded(path.to_path_buf()) {
-            return Task::ready(Ok(None));
-        }
-        let paths = if let Some(old_path) = old_path.as_ref() {
-            vec![old_path.clone(), path.clone()]
-        } else {
-            vec![path.clone()]
-        };
-        let mut refresh = self.refresh_entries_for_paths(paths);
-        cx.spawn(move |this, mut cx| async move {
-            refresh.recv().await;
-            let new_entry = this.update(&mut cx, |this, _| {
-                this.entry_for_path(path)
-                    .cloned()
-                    .ok_or_else(|| anyhow!("failed to read path after update"))
-            })??;
-            Ok(Some(new_entry))
-        })
-    }
-
-    pub fn observe_updates<F, Fut>(
-        &mut self,
-        project_id: u64,
-        cx: &mut ModelContext<Worktree>,
-        callback: F,
-    ) -> oneshot::Receiver<()>
-    where
-        F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
-        Fut: Send + Future<Output = bool>,
-    {
-        #[cfg(any(test, feature = "test-support"))]
-        const MAX_CHUNK_SIZE: usize = 2;
-        #[cfg(not(any(test, feature = "test-support")))]
-        const MAX_CHUNK_SIZE: usize = 256;
-
-        let (share_tx, share_rx) = oneshot::channel();
-
-        if let Some(share) = self.share.as_mut() {
-            share_tx.send(()).ok();
-            *share.resume_updates.borrow_mut() = ();
-            return share_rx;
-        }
-
-        let (resume_updates_tx, mut resume_updates_rx) = watch::channel::<()>();
-        let (snapshots_tx, mut snapshots_rx) =
-            mpsc::unbounded::<(LocalSnapshot, UpdatedEntriesSet, UpdatedGitRepositoriesSet)>();
-        snapshots_tx
-            .unbounded_send((self.snapshot(), Arc::from([]), Arc::from([])))
-            .ok();
-
-        let worktree_id = cx.entity_id().as_u64();
-        let _maintain_remote_snapshot = cx.background_executor().spawn(async move {
-            let mut is_first = true;
-            while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await {
-                let update;
-                if is_first {
-                    update = snapshot.build_initial_update(project_id, worktree_id);
-                    is_first = false;
-                } else {
-                    update =
-                        snapshot.build_update(project_id, worktree_id, entry_changes, repo_changes);
-                }
-
-                for update in proto::split_worktree_update(update, MAX_CHUNK_SIZE) {
-                    let _ = resume_updates_rx.try_recv();
-                    loop {
-                        let result = callback(update.clone());
-                        if result.await {
-                            break;
-                        } else {
-                            log::info!("waiting to resume updates");
-                            if resume_updates_rx.next().await.is_none() {
-                                return Some(());
-                            }
-                        }
-                    }
-                }
-            }
-            share_tx.send(()).ok();
-            Some(())
-        });
-
-        self.share = Some(ShareState {
-            project_id,
-            snapshots_tx,
-            resume_updates: resume_updates_tx,
-            _maintain_remote_snapshot,
-        });
-        share_rx
-    }
-
-    pub fn share(&mut self, project_id: u64, cx: &mut ModelContext<Worktree>) -> Task<Result<()>> {
-        let client = self.client.clone();
-
-        for (path, summaries) in &self.diagnostic_summaries {
-            for (&server_id, summary) in summaries {
-                if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
-                    project_id,
-                    worktree_id: cx.entity_id().as_u64(),
-                    summary: Some(summary.to_proto(server_id, &path)),
-                }) {
-                    return Task::ready(Err(e));
-                }
-            }
-        }
-
-        let rx = self.observe_updates(project_id, cx, move |update| {
-            client.request(update).map(|result| result.is_ok())
-        });
-        cx.background_executor()
-            .spawn(async move { rx.await.map_err(|_| anyhow!("share ended")) })
-    }
-
-    pub fn unshare(&mut self) {
-        self.share.take();
-    }
-
-    pub fn is_shared(&self) -> bool {
-        self.share.is_some()
-    }
-}
-
-impl RemoteWorktree {
-    fn snapshot(&self) -> Snapshot {
-        self.snapshot.clone()
-    }
-
-    pub fn disconnected_from_host(&mut self) {
-        self.updates_tx.take();
-        self.snapshot_subscriptions.clear();
-        self.disconnected = true;
-    }
-
-    pub fn save_buffer(
-        &self,
-        buffer_handle: Model<Buffer>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<()>> {
-        let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
-        let version = buffer.version();
-        let rpc = self.client.clone();
-        let project_id = self.project_id;
-        cx.spawn(move |_, mut cx| async move {
-            let response = rpc
-                .request(proto::SaveBuffer {
-                    project_id,
-                    buffer_id,
-                    version: serialize_version(&version),
-                })
-                .await?;
-            let version = deserialize_version(&response.version);
-            let fingerprint = deserialize_fingerprint(&response.fingerprint)?;
-            let mtime = response
-                .mtime
-                .ok_or_else(|| anyhow!("missing mtime"))?
-                .into();
-
-            buffer_handle.update(&mut cx, |buffer, cx| {
-                buffer.did_save(version.clone(), fingerprint, mtime, cx);
-            })?;
-
-            Ok(())
-        })
-    }
-
-    pub fn update_from_remote(&mut self, update: proto::UpdateWorktree) {
-        if let Some(updates_tx) = &self.updates_tx {
-            updates_tx
-                .unbounded_send(update)
-                .expect("consumer runs to completion");
-        }
-    }
-
-    fn observed_snapshot(&self, scan_id: usize) -> bool {
-        self.completed_scan_id >= scan_id
-    }
-
-    pub(crate) fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
-        let (tx, rx) = oneshot::channel();
-        if self.observed_snapshot(scan_id) {
-            let _ = tx.send(());
-        } else if self.disconnected {
-            drop(tx);
-        } else {
-            match self
-                .snapshot_subscriptions
-                .binary_search_by_key(&scan_id, |probe| probe.0)
-            {
-                Ok(ix) | Err(ix) => self.snapshot_subscriptions.insert(ix, (scan_id, tx)),
-            }
-        }
-
-        async move {
-            rx.await?;
-            Ok(())
-        }
-    }
-
-    pub fn update_diagnostic_summary(
-        &mut self,
-        path: Arc<Path>,
-        summary: &proto::DiagnosticSummary,
-    ) {
-        let server_id = LanguageServerId(summary.language_server_id as usize);
-        let summary = DiagnosticSummary {
-            error_count: summary.error_count as usize,
-            warning_count: summary.warning_count as usize,
-        };
-
-        if summary.is_empty() {
-            if let Some(summaries) = self.diagnostic_summaries.get_mut(&path) {
-                summaries.remove(&server_id);
-                if summaries.is_empty() {
-                    self.diagnostic_summaries.remove(&path);
-                }
-            }
-        } else {
-            self.diagnostic_summaries
-                .entry(path)
-                .or_default()
-                .insert(server_id, summary);
-        }
-    }
-
-    pub fn insert_entry(
-        &mut self,
-        entry: proto::Entry,
-        scan_id: usize,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Entry>> {
-        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
-        cx.spawn(|this, mut cx| async move {
-            wait_for_snapshot.await?;
-            this.update(&mut cx, |worktree, _| {
-                let worktree = worktree.as_remote_mut().unwrap();
-                let mut snapshot = worktree.background_snapshot.lock();
-                let entry = snapshot.insert_entry(entry);
-                worktree.snapshot = snapshot.clone();
-                entry
-            })?
-        })
-    }
-
-    pub(crate) fn delete_entry(
-        &mut self,
-        id: ProjectEntryId,
-        scan_id: usize,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<()>> {
-        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
-        cx.spawn(move |this, mut cx| async move {
-            wait_for_snapshot.await?;
-            this.update(&mut cx, |worktree, _| {
-                let worktree = worktree.as_remote_mut().unwrap();
-                let mut snapshot = worktree.background_snapshot.lock();
-                snapshot.delete_entry(id);
-                worktree.snapshot = snapshot.clone();
-            })?;
-            Ok(())
-        })
-    }
-}
-
-impl Snapshot {
-    pub fn id(&self) -> WorktreeId {
-        self.id
-    }
-
-    pub fn abs_path(&self) -> &Arc<Path> {
-        &self.abs_path
-    }
-
-    pub fn absolutize(&self, path: &Path) -> PathBuf {
-        if path.file_name().is_some() {
-            self.abs_path.join(path)
-        } else {
-            self.abs_path.to_path_buf()
-        }
-    }
-
-    pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
-        self.entries_by_id.get(&entry_id, &()).is_some()
-    }
-
-    fn insert_entry(&mut self, entry: proto::Entry) -> Result<Entry> {
-        let entry = Entry::try_from((&self.root_char_bag, entry))?;
-        let old_entry = self.entries_by_id.insert_or_replace(
-            PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id: 0,
-            },
-            &(),
-        );
-        if let Some(old_entry) = old_entry {
-            self.entries_by_path.remove(&PathKey(old_entry.path), &());
-        }
-        self.entries_by_path.insert_or_replace(entry.clone(), &());
-        Ok(entry)
-    }
-
-    fn delete_entry(&mut self, entry_id: ProjectEntryId) -> Option<Arc<Path>> {
-        let removed_entry = self.entries_by_id.remove(&entry_id, &())?;
-        self.entries_by_path = {
-            let mut cursor = self.entries_by_path.cursor::<TraversalProgress>();
-            let mut new_entries_by_path =
-                cursor.slice(&TraversalTarget::Path(&removed_entry.path), Bias::Left, &());
-            while let Some(entry) = cursor.item() {
-                if entry.path.starts_with(&removed_entry.path) {
-                    self.entries_by_id.remove(&entry.id, &());
-                    cursor.next(&());
-                } else {
-                    break;
-                }
-            }
-            new_entries_by_path.append(cursor.suffix(&()), &());
-            new_entries_by_path
-        };
-
-        Some(removed_entry.path)
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn status_for_file(&self, path: impl Into<PathBuf>) -> Option<GitFileStatus> {
-        let path = path.into();
-        self.entries_by_path
-            .get(&PathKey(Arc::from(path)), &())
-            .and_then(|entry| entry.git_status)
-    }
-
-    pub(crate) fn apply_remote_update(&mut self, mut update: proto::UpdateWorktree) -> Result<()> {
-        let mut entries_by_path_edits = Vec::new();
-        let mut entries_by_id_edits = Vec::new();
-
-        for entry_id in update.removed_entries {
-            let entry_id = ProjectEntryId::from_proto(entry_id);
-            entries_by_id_edits.push(Edit::Remove(entry_id));
-            if let Some(entry) = self.entry_for_id(entry_id) {
-                entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
-            }
-        }
-
-        for entry in update.updated_entries {
-            let entry = Entry::try_from((&self.root_char_bag, entry))?;
-            if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) {
-                entries_by_path_edits.push(Edit::Remove(PathKey(path.clone())));
-            }
-            if let Some(old_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) {
-                if old_entry.id != entry.id {
-                    entries_by_id_edits.push(Edit::Remove(old_entry.id));
-                }
-            }
-            entries_by_id_edits.push(Edit::Insert(PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id: 0,
-            }));
-            entries_by_path_edits.push(Edit::Insert(entry));
-        }
-
-        self.entries_by_path.edit(entries_by_path_edits, &());
-        self.entries_by_id.edit(entries_by_id_edits, &());
-
-        update.removed_repositories.sort_unstable();
-        self.repository_entries.retain(|_, entry| {
-            if let Ok(_) = update
-                .removed_repositories
-                .binary_search(&entry.work_directory.to_proto())
-            {
-                false
-            } else {
-                true
-            }
-        });
-
-        for repository in update.updated_repositories {
-            let work_directory_entry: WorkDirectoryEntry =
-                ProjectEntryId::from_proto(repository.work_directory_id).into();
-
-            if let Some(entry) = self.entry_for_id(*work_directory_entry) {
-                let work_directory = RepositoryWorkDirectory(entry.path.clone());
-                if self.repository_entries.get(&work_directory).is_some() {
-                    self.repository_entries.update(&work_directory, |repo| {
-                        repo.branch = repository.branch.map(Into::into);
-                    });
-                } else {
-                    self.repository_entries.insert(
-                        work_directory,
-                        RepositoryEntry {
-                            work_directory: work_directory_entry,
-                            branch: repository.branch.map(Into::into),
-                        },
-                    )
-                }
-            } else {
-                log::error!("no work directory entry for repository {:?}", repository)
-            }
-        }
-
-        self.scan_id = update.scan_id as usize;
-        if update.is_last_update {
-            self.completed_scan_id = update.scan_id as usize;
-        }
-
-        Ok(())
-    }
-
-    pub fn file_count(&self) -> usize {
-        self.entries_by_path.summary().file_count
-    }
-
-    pub fn visible_file_count(&self) -> usize {
-        self.entries_by_path.summary().non_ignored_file_count
-    }
-
-    fn traverse_from_offset(
-        &self,
-        include_dirs: bool,
-        include_ignored: bool,
-        start_offset: usize,
-    ) -> Traversal {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(
-            &TraversalTarget::Count {
-                count: start_offset,
-                include_dirs,
-                include_ignored,
-            },
-            Bias::Right,
-            &(),
-        );
-        Traversal {
-            cursor,
-            include_dirs,
-            include_ignored,
-        }
-    }
-
-    fn traverse_from_path(
-        &self,
-        include_dirs: bool,
-        include_ignored: bool,
-        path: &Path,
-    ) -> Traversal {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(&TraversalTarget::Path(path), Bias::Left, &());
-        Traversal {
-            cursor,
-            include_dirs,
-            include_ignored,
-        }
-    }
-
-    pub fn files(&self, include_ignored: bool, start: usize) -> Traversal {
-        self.traverse_from_offset(false, include_ignored, start)
-    }
-
-    pub fn entries(&self, include_ignored: bool) -> Traversal {
-        self.traverse_from_offset(true, include_ignored, 0)
-    }
-
-    pub fn repositories(&self) -> impl Iterator<Item = (&Arc<Path>, &RepositoryEntry)> {
-        self.repository_entries
-            .iter()
-            .map(|(path, entry)| (&path.0, entry))
-    }
-
-    /// Get the repository whose work directory contains the given path.
-    pub fn repository_for_work_directory(&self, path: &Path) -> Option<RepositoryEntry> {
-        self.repository_entries
-            .get(&RepositoryWorkDirectory(path.into()))
-            .cloned()
-    }
-
-    /// Get the repository whose work directory contains the given path.
-    pub fn repository_for_path(&self, path: &Path) -> Option<RepositoryEntry> {
-        self.repository_and_work_directory_for_path(path)
-            .map(|e| e.1)
-    }
-
-    pub fn repository_and_work_directory_for_path(
-        &self,
-        path: &Path,
-    ) -> Option<(RepositoryWorkDirectory, RepositoryEntry)> {
-        self.repository_entries
-            .iter()
-            .filter(|(workdir_path, _)| path.starts_with(workdir_path))
-            .last()
-            .map(|(path, repo)| (path.clone(), repo.clone()))
-    }
-
-    /// Given an ordered iterator of entries, returns an iterator of those entries,
-    /// along with their containing git repository.
-    pub fn entries_with_repositories<'a>(
-        &'a self,
-        entries: impl 'a + Iterator<Item = &'a Entry>,
-    ) -> impl 'a + Iterator<Item = (&'a Entry, Option<&'a RepositoryEntry>)> {
-        let mut containing_repos = Vec::<(&Arc<Path>, &RepositoryEntry)>::new();
-        let mut repositories = self.repositories().peekable();
-        entries.map(move |entry| {
-            while let Some((repo_path, _)) = containing_repos.last() {
-                if !entry.path.starts_with(repo_path) {
-                    containing_repos.pop();
-                } else {
-                    break;
-                }
-            }
-            while let Some((repo_path, _)) = repositories.peek() {
-                if entry.path.starts_with(repo_path) {
-                    containing_repos.push(repositories.next().unwrap());
-                } else {
-                    break;
-                }
-            }
-            let repo = containing_repos.last().map(|(_, repo)| *repo);
-            (entry, repo)
-        })
-    }
-
-    /// Update the `git_status` of the given entries such that files'
-    /// statuses bubble up to their ancestor directories.
-    pub fn propagate_git_statuses(&self, result: &mut [Entry]) {
-        let mut cursor = self
-            .entries_by_path
-            .cursor::<(TraversalProgress, GitStatuses)>();
-        let mut entry_stack = Vec::<(usize, GitStatuses)>::new();
-
-        let mut result_ix = 0;
-        loop {
-            let next_entry = result.get(result_ix);
-            let containing_entry = entry_stack.last().map(|(ix, _)| &result[*ix]);
-
-            let entry_to_finish = match (containing_entry, next_entry) {
-                (Some(_), None) => entry_stack.pop(),
-                (Some(containing_entry), Some(next_path)) => {
-                    if !next_path.path.starts_with(&containing_entry.path) {
-                        entry_stack.pop()
-                    } else {
-                        None
-                    }
-                }
-                (None, Some(_)) => None,
-                (None, None) => break,
-            };
-
-            if let Some((entry_ix, prev_statuses)) = entry_to_finish {
-                cursor.seek_forward(
-                    &TraversalTarget::PathSuccessor(&result[entry_ix].path),
-                    Bias::Left,
-                    &(),
-                );
-
-                let statuses = cursor.start().1 - prev_statuses;
-
-                result[entry_ix].git_status = if statuses.conflict > 0 {
-                    Some(GitFileStatus::Conflict)
-                } else if statuses.modified > 0 {
-                    Some(GitFileStatus::Modified)
-                } else if statuses.added > 0 {
-                    Some(GitFileStatus::Added)
-                } else {
-                    None
-                };
-            } else {
-                if result[result_ix].is_dir() {
-                    cursor.seek_forward(
-                        &TraversalTarget::Path(&result[result_ix].path),
-                        Bias::Left,
-                        &(),
-                    );
-                    entry_stack.push((result_ix, cursor.start().1));
-                }
-                result_ix += 1;
-            }
-        }
-    }
-
-    pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
-        let empty_path = Path::new("");
-        self.entries_by_path
-            .cursor::<()>()
-            .filter(move |entry| entry.path.as_ref() != empty_path)
-            .map(|entry| &entry.path)
-    }
-
-    fn child_entries<'a>(&'a self, parent_path: &'a Path) -> ChildEntriesIter<'a> {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(&TraversalTarget::Path(parent_path), Bias::Right, &());
-        let traversal = Traversal {
-            cursor,
-            include_dirs: true,
-            include_ignored: true,
-        };
-        ChildEntriesIter {
-            traversal,
-            parent_path,
-        }
-    }
-
-    pub fn descendent_entries<'a>(
-        &'a self,
-        include_dirs: bool,
-        include_ignored: bool,
-        parent_path: &'a Path,
-    ) -> DescendentEntriesIter<'a> {
-        let mut cursor = self.entries_by_path.cursor();
-        cursor.seek(&TraversalTarget::Path(parent_path), Bias::Left, &());
-        let mut traversal = Traversal {
-            cursor,
-            include_dirs,
-            include_ignored,
-        };
-
-        if traversal.end_offset() == traversal.start_offset() {
-            traversal.advance();
-        }
-
-        DescendentEntriesIter {
-            traversal,
-            parent_path,
-        }
-    }
-
-    pub fn root_entry(&self) -> Option<&Entry> {
-        self.entry_for_path("")
-    }
-
-    pub fn root_name(&self) -> &str {
-        &self.root_name
-    }
-
-    pub fn root_git_entry(&self) -> Option<RepositoryEntry> {
-        self.repository_entries
-            .get(&RepositoryWorkDirectory(Path::new("").into()))
-            .map(|entry| entry.to_owned())
-    }
-
-    pub fn git_entries(&self) -> impl Iterator<Item = &RepositoryEntry> {
-        self.repository_entries.values()
-    }
-
-    pub fn scan_id(&self) -> usize {
-        self.scan_id
-    }
-
-    pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
-        let path = path.as_ref();
-        self.traverse_from_path(true, true, path)
-            .entry()
-            .and_then(|entry| {
-                if entry.path.as_ref() == path {
-                    Some(entry)
-                } else {
-                    None
-                }
-            })
-    }
-
-    pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> {
-        let entry = self.entries_by_id.get(&id, &())?;
-        self.entry_for_path(&entry.path)
-    }
-
-    pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
-        self.entry_for_path(path.as_ref()).map(|e| e.inode)
-    }
-}
-
-impl LocalSnapshot {
-    pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
-        self.git_repositories.get(&repo.work_directory.0)
-    }
-
-    pub(crate) fn local_repo_for_path(
-        &self,
-        path: &Path,
-    ) -> Option<(RepositoryWorkDirectory, &LocalRepositoryEntry)> {
-        let (path, repo) = self.repository_and_work_directory_for_path(path)?;
-        Some((path, self.git_repositories.get(&repo.work_directory_id())?))
-    }
-
-    fn build_update(
-        &self,
-        project_id: u64,
-        worktree_id: u64,
-        entry_changes: UpdatedEntriesSet,
-        repo_changes: UpdatedGitRepositoriesSet,
-    ) -> proto::UpdateWorktree {
-        let mut updated_entries = Vec::new();
-        let mut removed_entries = Vec::new();
-        let mut updated_repositories = Vec::new();
-        let mut removed_repositories = Vec::new();
-
-        for (_, entry_id, path_change) in entry_changes.iter() {
-            if let PathChange::Removed = path_change {
-                removed_entries.push(entry_id.0 as u64);
-            } else if let Some(entry) = self.entry_for_id(*entry_id) {
-                updated_entries.push(proto::Entry::from(entry));
-            }
-        }
-
-        for (work_dir_path, change) in repo_changes.iter() {
-            let new_repo = self
-                .repository_entries
-                .get(&RepositoryWorkDirectory(work_dir_path.clone()));
-            match (&change.old_repository, new_repo) {
-                (Some(old_repo), Some(new_repo)) => {
-                    updated_repositories.push(new_repo.build_update(old_repo));
-                }
-                (None, Some(new_repo)) => {
-                    updated_repositories.push(proto::RepositoryEntry::from(new_repo));
-                }
-                (Some(old_repo), None) => {
-                    removed_repositories.push(old_repo.work_directory.0.to_proto());
-                }
-                _ => {}
-            }
-        }
-
-        removed_entries.sort_unstable();
-        updated_entries.sort_unstable_by_key(|e| e.id);
-        removed_repositories.sort_unstable();
-        updated_repositories.sort_unstable_by_key(|e| e.work_directory_id);
-
-        // TODO - optimize, knowing that removed_entries are sorted.
-        removed_entries.retain(|id| updated_entries.binary_search_by_key(id, |e| e.id).is_err());
-
-        proto::UpdateWorktree {
-            project_id,
-            worktree_id,
-            abs_path: self.abs_path().to_string_lossy().into(),
-            root_name: self.root_name().to_string(),
-            updated_entries,
-            removed_entries,
-            scan_id: self.scan_id as u64,
-            is_last_update: self.completed_scan_id == self.scan_id,
-            updated_repositories,
-            removed_repositories,
-        }
-    }
-
-    fn build_initial_update(&self, project_id: u64, worktree_id: u64) -> proto::UpdateWorktree {
-        let mut updated_entries = self
-            .entries_by_path
-            .iter()
-            .map(proto::Entry::from)
-            .collect::<Vec<_>>();
-        updated_entries.sort_unstable_by_key(|e| e.id);
-
-        let mut updated_repositories = self
-            .repository_entries
-            .values()
-            .map(proto::RepositoryEntry::from)
-            .collect::<Vec<_>>();
-        updated_repositories.sort_unstable_by_key(|e| e.work_directory_id);
-
-        proto::UpdateWorktree {
-            project_id,
-            worktree_id,
-            abs_path: self.abs_path().to_string_lossy().into(),
-            root_name: self.root_name().to_string(),
-            updated_entries,
-            removed_entries: Vec::new(),
-            scan_id: self.scan_id as u64,
-            is_last_update: self.completed_scan_id == self.scan_id,
-            updated_repositories,
-            removed_repositories: Vec::new(),
-        }
-    }
-
-    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
-        if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
-            let abs_path = self.abs_path.join(&entry.path);
-            match smol::block_on(build_gitignore(&abs_path, fs)) {
-                Ok(ignore) => {
-                    self.ignores_by_parent_abs_path
-                        .insert(abs_path.parent().unwrap().into(), (Arc::new(ignore), true));
-                }
-                Err(error) => {
-                    log::error!(
-                        "error loading .gitignore file {:?} - {:?}",
-                        &entry.path,
-                        error
-                    );
-                }
-            }
-        }
-
-        if entry.kind == EntryKind::PendingDir {
-            if let Some(existing_entry) =
-                self.entries_by_path.get(&PathKey(entry.path.clone()), &())
-            {
-                entry.kind = existing_entry.kind;
-            }
-        }
-
-        let scan_id = self.scan_id;
-        let removed = self.entries_by_path.insert_or_replace(entry.clone(), &());
-        if let Some(removed) = removed {
-            if removed.id != entry.id {
-                self.entries_by_id.remove(&removed.id, &());
-            }
-        }
-        self.entries_by_id.insert_or_replace(
-            PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id,
-            },
-            &(),
-        );
-
-        entry
-    }
-
-    fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet<u64> {
-        let mut inodes = TreeSet::default();
-        for ancestor in path.ancestors().skip(1) {
-            if let Some(entry) = self.entry_for_path(ancestor) {
-                inodes.insert(entry.inode);
-            }
-        }
-        inodes
-    }
-
-    fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
-        let mut new_ignores = Vec::new();
-        for ancestor in abs_path.ancestors().skip(1) {
-            if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
-                new_ignores.push((ancestor, Some(ignore.clone())));
-            } else {
-                new_ignores.push((ancestor, None));
-            }
-        }
-
-        let mut ignore_stack = IgnoreStack::none();
-        for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
-            if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
-                ignore_stack = IgnoreStack::all();
-                break;
-            } else if let Some(ignore) = ignore {
-                ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore);
-            }
-        }
-
-        if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
-            ignore_stack = IgnoreStack::all();
-        }
-
-        ignore_stack
-    }
-
-    #[cfg(test)]
-    pub(crate) fn expanded_entries(&self) -> impl Iterator<Item = &Entry> {
-        self.entries_by_path
-            .cursor::<()>()
-            .filter(|entry| entry.kind == EntryKind::Dir && (entry.is_external || entry.is_ignored))
-    }
-
-    #[cfg(test)]
-    pub fn check_invariants(&self, git_state: bool) {
-        use pretty_assertions::assert_eq;
-
-        assert_eq!(
-            self.entries_by_path
-                .cursor::<()>()
-                .map(|e| (&e.path, e.id))
-                .collect::<Vec<_>>(),
-            self.entries_by_id
-                .cursor::<()>()
-                .map(|e| (&e.path, e.id))
-                .collect::<collections::BTreeSet<_>>()
-                .into_iter()
-                .collect::<Vec<_>>(),
-            "entries_by_path and entries_by_id are inconsistent"
-        );
-
-        let mut files = self.files(true, 0);
-        let mut visible_files = self.files(false, 0);
-        for entry in self.entries_by_path.cursor::<()>() {
-            if entry.is_file() {
-                assert_eq!(files.next().unwrap().inode, entry.inode);
-                if !entry.is_ignored && !entry.is_external {
-                    assert_eq!(visible_files.next().unwrap().inode, entry.inode);
-                }
-            }
-        }
-
-        assert!(files.next().is_none());
-        assert!(visible_files.next().is_none());
-
-        let mut bfs_paths = Vec::new();
-        let mut stack = self
-            .root_entry()
-            .map(|e| e.path.as_ref())
-            .into_iter()
-            .collect::<Vec<_>>();
-        while let Some(path) = stack.pop() {
-            bfs_paths.push(path);
-            let ix = stack.len();
-            for child_entry in self.child_entries(path) {
-                stack.insert(ix, &child_entry.path);
-            }
-        }
-
-        let dfs_paths_via_iter = self
-            .entries_by_path
-            .cursor::<()>()
-            .map(|e| e.path.as_ref())
-            .collect::<Vec<_>>();
-        assert_eq!(bfs_paths, dfs_paths_via_iter);
-
-        let dfs_paths_via_traversal = self
-            .entries(true)
-            .map(|e| e.path.as_ref())
-            .collect::<Vec<_>>();
-        assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
-
-        if git_state {
-            for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
-                let ignore_parent_path =
-                    ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
-                assert!(self.entry_for_path(&ignore_parent_path).is_some());
-                assert!(self
-                    .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
-                    .is_some());
-            }
-        }
-    }
-
-    #[cfg(test)]
-    pub fn entries_without_ids(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
-        let mut paths = Vec::new();
-        for entry in self.entries_by_path.cursor::<()>() {
-            if include_ignored || !entry.is_ignored {
-                paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
-            }
-        }
-        paths.sort_by(|a, b| a.0.cmp(b.0));
-        paths
-    }
-
-    pub fn is_path_excluded(&self, mut path: PathBuf) -> bool {
-        loop {
-            if self
-                .file_scan_exclusions
-                .iter()
-                .any(|exclude_matcher| exclude_matcher.is_match(&path))
-            {
-                return true;
-            }
-            if !path.pop() {
-                return false;
-            }
-        }
-    }
-}
-
-impl BackgroundScannerState {
-    fn should_scan_directory(&self, entry: &Entry) -> bool {
-        (!entry.is_external && !entry.is_ignored)
-            || entry.path.file_name() == Some(&*DOT_GIT)
-            || self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
-            || self
-                .paths_to_scan
-                .iter()
-                .any(|p| p.starts_with(&entry.path))
-            || self
-                .path_prefixes_to_scan
-                .iter()
-                .any(|p| entry.path.starts_with(p))
-    }
-
-    fn enqueue_scan_dir(&self, abs_path: Arc<Path>, entry: &Entry, scan_job_tx: &Sender<ScanJob>) {
-        let path = entry.path.clone();
-        let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true);
-        let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path);
-        let mut containing_repository = None;
-        if !ignore_stack.is_abs_path_ignored(&abs_path, true) {
-            if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) {
-                if let Ok(repo_path) = path.strip_prefix(&workdir_path.0) {
-                    containing_repository = Some((
-                        workdir_path,
-                        repo.repo_ptr.clone(),
-                        repo.repo_ptr.lock().staged_statuses(repo_path),
-                    ));
-                }
-            }
-        }
-        if !ancestor_inodes.contains(&entry.inode) {
-            ancestor_inodes.insert(entry.inode);
-            scan_job_tx
-                .try_send(ScanJob {
-                    abs_path,
-                    path,
-                    ignore_stack,
-                    scan_queue: scan_job_tx.clone(),
-                    ancestor_inodes,
-                    is_external: entry.is_external,
-                    containing_repository,
-                })
-                .unwrap();
-        }
-    }
-
-    fn reuse_entry_id(&mut self, entry: &mut Entry) {
-        if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
-            entry.id = removed_entry_id;
-        } else if let Some(existing_entry) = self.snapshot.entry_for_path(&entry.path) {
-            entry.id = existing_entry.id;
-        }
-    }
-
-    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
-        self.reuse_entry_id(&mut entry);
-        let entry = self.snapshot.insert_entry(entry, fs);
-        if entry.path.file_name() == Some(&DOT_GIT) {
-            self.build_git_repository(entry.path.clone(), fs);
-        }
-
-        #[cfg(test)]
-        self.snapshot.check_invariants(false);
-
-        entry
-    }
-
-    fn populate_dir(
-        &mut self,
-        parent_path: &Arc<Path>,
-        entries: impl IntoIterator<Item = Entry>,
-        ignore: Option<Arc<Gitignore>>,
-    ) {
-        let mut parent_entry = if let Some(parent_entry) = self
-            .snapshot
-            .entries_by_path
-            .get(&PathKey(parent_path.clone()), &())
-        {
-            parent_entry.clone()
-        } else {
-            log::warn!(
-                "populating a directory {:?} that has been removed",
-                parent_path
-            );
-            return;
-        };
-
-        match parent_entry.kind {
-            EntryKind::PendingDir | EntryKind::UnloadedDir => parent_entry.kind = EntryKind::Dir,
-            EntryKind::Dir => {}
-            _ => return,
-        }
-
-        if let Some(ignore) = ignore {
-            let abs_parent_path = self.snapshot.abs_path.join(&parent_path).into();
-            self.snapshot
-                .ignores_by_parent_abs_path
-                .insert(abs_parent_path, (ignore, false));
-        }
-
-        let parent_entry_id = parent_entry.id;
-        self.scanned_dirs.insert(parent_entry_id);
-        let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
-        let mut entries_by_id_edits = Vec::new();
-
-        for entry in entries {
-            entries_by_id_edits.push(Edit::Insert(PathEntry {
-                id: entry.id,
-                path: entry.path.clone(),
-                is_ignored: entry.is_ignored,
-                scan_id: self.snapshot.scan_id,
-            }));
-            entries_by_path_edits.push(Edit::Insert(entry));
-        }
-
-        self.snapshot
-            .entries_by_path
-            .edit(entries_by_path_edits, &());
-        self.snapshot.entries_by_id.edit(entries_by_id_edits, &());
-
-        if let Err(ix) = self.changed_paths.binary_search(parent_path) {
-            self.changed_paths.insert(ix, parent_path.clone());
-        }
-
-        #[cfg(test)]
-        self.snapshot.check_invariants(false);
-    }
-
-    fn remove_path(&mut self, path: &Path) {
-        let mut new_entries;
-        let removed_entries;
-        {
-            let mut cursor = self.snapshot.entries_by_path.cursor::<TraversalProgress>();
-            new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &());
-            removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &());
-            new_entries.append(cursor.suffix(&()), &());
-        }
-        self.snapshot.entries_by_path = new_entries;
-
-        let mut entries_by_id_edits = Vec::new();
-        for entry in removed_entries.cursor::<()>() {
-            let removed_entry_id = self
-                .removed_entry_ids
-                .entry(entry.inode)
-                .or_insert(entry.id);
-            *removed_entry_id = cmp::max(*removed_entry_id, entry.id);
-            entries_by_id_edits.push(Edit::Remove(entry.id));
-        }
-        self.snapshot.entries_by_id.edit(entries_by_id_edits, &());
-
-        if path.file_name() == Some(&GITIGNORE) {
-            let abs_parent_path = self.snapshot.abs_path.join(path.parent().unwrap());
-            if let Some((_, needs_update)) = self
-                .snapshot
-                .ignores_by_parent_abs_path
-                .get_mut(abs_parent_path.as_path())
-            {
-                *needs_update = true;
-            }
-        }
-
-        #[cfg(test)]
-        self.snapshot.check_invariants(false);
-    }
-
-    fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) {
-        let scan_id = self.snapshot.scan_id;
-
-        for dot_git_dir in dot_git_dirs_to_reload {
-            // If there is already a repository for this .git directory, reload
-            // the status for all of its files.
-            let repository = self
-                .snapshot
-                .git_repositories
-                .iter()
-                .find_map(|(entry_id, repo)| {
-                    (repo.git_dir_path.as_ref() == dot_git_dir).then(|| (*entry_id, repo.clone()))
-                });
-            match repository {
-                None => {
-                    self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs);
-                }
-                Some((entry_id, repository)) => {
-                    if repository.git_dir_scan_id == scan_id {
-                        continue;
-                    }
-                    let Some(work_dir) = self
-                        .snapshot
-                        .entry_for_id(entry_id)
-                        .map(|entry| RepositoryWorkDirectory(entry.path.clone()))
-                    else {
-                        continue;
-                    };
-
-                    log::info!("reload git repository {dot_git_dir:?}");
-                    let repository = repository.repo_ptr.lock();
-                    let branch = repository.branch_name();
-                    repository.reload_index();
-
-                    self.snapshot
-                        .git_repositories
-                        .update(&entry_id, |entry| entry.git_dir_scan_id = scan_id);
-                    self.snapshot
-                        .snapshot
-                        .repository_entries
-                        .update(&work_dir, |entry| entry.branch = branch.map(Into::into));
-
-                    self.update_git_statuses(&work_dir, &*repository);
-                }
-            }
-        }
-
-        // Remove any git repositories whose .git entry no longer exists.
-        let snapshot = &mut self.snapshot;
-        let mut ids_to_preserve = HashSet::default();
-        for (&work_directory_id, entry) in snapshot.git_repositories.iter() {
-            let exists_in_snapshot = snapshot
-                .entry_for_id(work_directory_id)
-                .map_or(false, |entry| {
-                    snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some()
-                });
-            if exists_in_snapshot {
-                ids_to_preserve.insert(work_directory_id);
-            } else {
-                let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path);
-                let git_dir_excluded = snapshot.is_path_excluded(entry.git_dir_path.to_path_buf());
-                if git_dir_excluded
-                    && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
-                {
-                    ids_to_preserve.insert(work_directory_id);
-                }
-            }
-        }
-        snapshot
-            .git_repositories
-            .retain(|work_directory_id, _| ids_to_preserve.contains(work_directory_id));
-        snapshot
-            .repository_entries
-            .retain(|_, entry| ids_to_preserve.contains(&entry.work_directory.0));
-    }
-
-    fn build_git_repository(
-        &mut self,
-        dot_git_path: Arc<Path>,
-        fs: &dyn Fs,
-    ) -> Option<(
-        RepositoryWorkDirectory,
-        Arc<Mutex<dyn GitRepository>>,
-        TreeMap<RepoPath, GitFileStatus>,
-    )> {
-        log::info!("build git repository {:?}", dot_git_path);
-
-        let work_dir_path: Arc<Path> = dot_git_path.parent().unwrap().into();
-
-        // Guard against repositories inside the repository metadata
-        if work_dir_path.iter().any(|component| component == *DOT_GIT) {
-            return None;
-        };
-
-        let work_dir_id = self
-            .snapshot
-            .entry_for_path(work_dir_path.clone())
-            .map(|entry| entry.id)?;
-
-        if self.snapshot.git_repositories.get(&work_dir_id).is_some() {
-            return None;
-        }
-
-        let abs_path = self.snapshot.abs_path.join(&dot_git_path);
-        let repository = fs.open_repo(abs_path.as_path())?;
-        let work_directory = RepositoryWorkDirectory(work_dir_path.clone());
-
-        let repo_lock = repository.lock();
-        self.snapshot.repository_entries.insert(
-            work_directory.clone(),
-            RepositoryEntry {
-                work_directory: work_dir_id.into(),
-                branch: repo_lock.branch_name().map(Into::into),
-            },
-        );
-
-        let staged_statuses = self.update_git_statuses(&work_directory, &*repo_lock);
-        drop(repo_lock);
-
-        self.snapshot.git_repositories.insert(
-            work_dir_id,
-            LocalRepositoryEntry {
-                git_dir_scan_id: 0,
-                repo_ptr: repository.clone(),
-                git_dir_path: dot_git_path.clone(),
-            },
-        );
-
-        Some((work_directory, repository, staged_statuses))
-    }
-
-    fn update_git_statuses(
-        &mut self,
-        work_directory: &RepositoryWorkDirectory,
-        repo: &dyn GitRepository,
-    ) -> TreeMap<RepoPath, GitFileStatus> {
-        let staged_statuses = repo.staged_statuses(Path::new(""));
-
-        let mut changes = vec![];
-        let mut edits = vec![];
-
-        for mut entry in self
-            .snapshot
-            .descendent_entries(false, false, &work_directory.0)
-            .cloned()
-        {
-            let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else {
-                continue;
-            };
-            let repo_path = RepoPath(repo_path.to_path_buf());
-            let git_file_status = combine_git_statuses(
-                staged_statuses.get(&repo_path).copied(),
-                repo.unstaged_status(&repo_path, entry.mtime),
-            );
-            if entry.git_status != git_file_status {
-                entry.git_status = git_file_status;
-                changes.push(entry.path.clone());
-                edits.push(Edit::Insert(entry));
-            }
-        }
-
-        self.snapshot.entries_by_path.edit(edits, &());
-        util::extend_sorted(&mut self.changed_paths, changes, usize::MAX, Ord::cmp);
-        staged_statuses
-    }
-}
-
-async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
-    let contents = fs.load(abs_path).await?;
-    let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
-    let mut builder = GitignoreBuilder::new(parent);
-    for line in contents.lines() {
-        builder.add_line(Some(abs_path.into()), line)?;
-    }
-    Ok(builder.build()?)
-}
-
-impl WorktreeId {
-    pub fn from_usize(handle_id: usize) -> Self {
-        Self(handle_id)
-    }
-
-    pub(crate) fn from_proto(id: u64) -> Self {
-        Self(id as usize)
-    }
-
-    pub fn to_proto(&self) -> u64 {
-        self.0 as u64
-    }
-
-    pub fn to_usize(&self) -> usize {
-        self.0
-    }
-}
-
-impl fmt::Display for WorktreeId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.fmt(f)
-    }
-}
-
-impl Deref for Worktree {
-    type Target = Snapshot;
-
-    fn deref(&self) -> &Self::Target {
-        match self {
-            Worktree::Local(worktree) => &worktree.snapshot,
-            Worktree::Remote(worktree) => &worktree.snapshot,
-        }
-    }
-}
-
-impl Deref for LocalWorktree {
-    type Target = LocalSnapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl Deref for RemoteWorktree {
-    type Target = Snapshot;
-
-    fn deref(&self) -> &Self::Target {
-        &self.snapshot
-    }
-}
-
-impl fmt::Debug for LocalWorktree {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.snapshot.fmt(f)
-    }
-}
-
-impl fmt::Debug for Snapshot {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        struct EntriesById<'a>(&'a SumTree<PathEntry>);
-        struct EntriesByPath<'a>(&'a SumTree<Entry>);
-
-        impl<'a> fmt::Debug for EntriesByPath<'a> {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                f.debug_map()
-                    .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
-                    .finish()
-            }
-        }
-
-        impl<'a> fmt::Debug for EntriesById<'a> {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                f.debug_list().entries(self.0.iter()).finish()
-            }
-        }
-
-        f.debug_struct("Snapshot")
-            .field("id", &self.id)
-            .field("root_name", &self.root_name)
-            .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
-            .field("entries_by_id", &EntriesById(&self.entries_by_id))
-            .finish()
-    }
-}
-
-#[derive(Clone, PartialEq)]
-pub struct File {
-    pub worktree: Model<Worktree>,
-    pub path: Arc<Path>,
-    pub mtime: SystemTime,
-    pub(crate) entry_id: Option<ProjectEntryId>,
-    pub(crate) is_local: bool,
-    pub(crate) is_deleted: bool,
-}
-
-impl language::File for File {
-    fn as_local(&self) -> Option<&dyn language::LocalFile> {
-        if self.is_local {
-            Some(self)
-        } else {
-            None
-        }
-    }
-
-    fn mtime(&self) -> SystemTime {
-        self.mtime
-    }
-
-    fn path(&self) -> &Arc<Path> {
-        &self.path
-    }
-
-    fn full_path(&self, cx: &AppContext) -> PathBuf {
-        let mut full_path = PathBuf::new();
-        let worktree = self.worktree.read(cx);
-
-        if worktree.is_visible() {
-            full_path.push(worktree.root_name());
-        } else {
-            let path = worktree.abs_path();
-
-            if worktree.is_local() && path.starts_with(HOME.as_path()) {
-                full_path.push("~");
-                full_path.push(path.strip_prefix(HOME.as_path()).unwrap());
-            } else {
-                full_path.push(path)
-            }
-        }
-
-        if self.path.components().next().is_some() {
-            full_path.push(&self.path);
-        }
-
-        full_path
-    }
-
-    /// Returns the last component of this handle's absolute path. If this handle refers to the root
-    /// of its worktree, then this method will return the name of the worktree itself.
-    fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {
-        self.path
-            .file_name()
-            .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
-    }
-
-    fn worktree_id(&self) -> usize {
-        self.worktree.entity_id().as_u64() as usize
-    }
-
-    fn is_deleted(&self) -> bool {
-        self.is_deleted
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn to_proto(&self) -> rpc::proto::File {
-        rpc::proto::File {
-            worktree_id: self.worktree.entity_id().as_u64(),
-            entry_id: self.entry_id.map(|id| id.to_proto()),
-            path: self.path.to_string_lossy().into(),
-            mtime: Some(self.mtime.into()),
-            is_deleted: self.is_deleted,
-        }
-    }
-}
-
-impl language::LocalFile for File {
-    fn abs_path(&self, cx: &AppContext) -> PathBuf {
-        let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path;
-        if self.path.as_ref() == Path::new("") {
-            worktree_path.to_path_buf()
-        } else {
-            worktree_path.join(&self.path)
-        }
-    }
-
-    fn load(&self, cx: &AppContext) -> Task<Result<String>> {
-        let worktree = self.worktree.read(cx).as_local().unwrap();
-        let abs_path = worktree.absolutize(&self.path);
-        let fs = worktree.fs.clone();
-        cx.background_executor()
-            .spawn(async move { fs.load(&abs_path).await })
-    }
-
-    fn buffer_reloaded(
-        &self,
-        buffer_id: u64,
-        version: &clock::Global,
-        fingerprint: RopeFingerprint,
-        line_ending: LineEnding,
-        mtime: SystemTime,
-        cx: &mut AppContext,
-    ) {
-        let worktree = self.worktree.read(cx).as_local().unwrap();
-        if let Some(project_id) = worktree.share.as_ref().map(|share| share.project_id) {
-            worktree
-                .client
-                .send(proto::BufferReloaded {
-                    project_id,
-                    buffer_id,
-                    version: serialize_version(version),
-                    mtime: Some(mtime.into()),
-                    fingerprint: serialize_fingerprint(fingerprint),
-                    line_ending: serialize_line_ending(line_ending) as i32,
-                })
-                .log_err();
-        }
-    }
-}
-
-impl File {
-    pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
-        Arc::new(Self {
-            worktree,
-            path: entry.path.clone(),
-            mtime: entry.mtime,
-            entry_id: Some(entry.id),
-            is_local: true,
-            is_deleted: false,
-        })
-    }
-
-    pub fn from_proto(
-        proto: rpc::proto::File,
-        worktree: Model<Worktree>,
-        cx: &AppContext,
-    ) -> Result<Self> {
-        let worktree_id = worktree
-            .read(cx)
-            .as_remote()
-            .ok_or_else(|| anyhow!("not remote"))?
-            .id();
-
-        if worktree_id.to_proto() != proto.worktree_id {
-            return Err(anyhow!("worktree id does not match file"));
-        }
-
-        Ok(Self {
-            worktree,
-            path: Path::new(&proto.path).into(),
-            mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
-            entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
-            is_local: false,
-            is_deleted: proto.is_deleted,
-        })
-    }
-
-    pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
-        file.and_then(|f| f.as_any().downcast_ref())
-    }
-
-    pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
-        self.worktree.read(cx).id()
-    }
-
-    pub fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-        if self.is_deleted {
-            None
-        } else {
-            self.entry_id
-        }
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Entry {
-    pub id: ProjectEntryId,
-    pub kind: EntryKind,
-    pub path: Arc<Path>,
-    pub inode: u64,
-    pub mtime: SystemTime,
-    pub is_symlink: bool,
-
-    /// Whether this entry is ignored by Git.
-    ///
-    /// We only scan ignored entries once the directory is expanded and
-    /// exclude them from searches.
-    pub is_ignored: bool,
-
-    /// Whether this entry's canonical path is outside of the worktree.
-    /// This means the entry is only accessible from the worktree root via a
-    /// symlink.
-    ///
-    /// We only scan entries outside of the worktree once the symlinked
-    /// directory is expanded. External entries are treated like gitignored
-    /// entries in that they are not included in searches.
-    pub is_external: bool,
-    pub git_status: Option<GitFileStatus>,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum EntryKind {
-    UnloadedDir,
-    PendingDir,
-    Dir,
-    File(CharBag),
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum PathChange {
-    /// A filesystem entry was was created.
-    Added,
-    /// A filesystem entry was removed.
-    Removed,
-    /// A filesystem entry was updated.
-    Updated,
-    /// A filesystem entry was either updated or added. We don't know
-    /// whether or not it already existed, because the path had not
-    /// been loaded before the event.
-    AddedOrUpdated,
-    /// A filesystem entry was found during the initial scan of the worktree.
-    Loaded,
-}
-
-pub struct GitRepositoryChange {
-    /// The previous state of the repository, if it already existed.
-    pub old_repository: Option<RepositoryEntry>,
-}
-
-pub type UpdatedEntriesSet = Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>;
-pub type UpdatedGitRepositoriesSet = Arc<[(Arc<Path>, GitRepositoryChange)]>;
-
-impl Entry {
-    fn new(
-        path: Arc<Path>,
-        metadata: &fs::Metadata,
-        next_entry_id: &AtomicUsize,
-        root_char_bag: CharBag,
-    ) -> Self {
-        Self {
-            id: ProjectEntryId::new(next_entry_id),
-            kind: if metadata.is_dir {
-                EntryKind::PendingDir
-            } else {
-                EntryKind::File(char_bag_for_path(root_char_bag, &path))
-            },
-            path,
-            inode: metadata.inode,
-            mtime: metadata.mtime,
-            is_symlink: metadata.is_symlink,
-            is_ignored: false,
-            is_external: false,
-            git_status: None,
-        }
-    }
-
-    pub fn is_dir(&self) -> bool {
-        self.kind.is_dir()
-    }
-
-    pub fn is_file(&self) -> bool {
-        self.kind.is_file()
-    }
-
-    pub fn git_status(&self) -> Option<GitFileStatus> {
-        self.git_status
-    }
-}
-
-impl EntryKind {
-    pub fn is_dir(&self) -> bool {
-        matches!(
-            self,
-            EntryKind::Dir | EntryKind::PendingDir | EntryKind::UnloadedDir
-        )
-    }
-
-    pub fn is_unloaded(&self) -> bool {
-        matches!(self, EntryKind::UnloadedDir)
-    }
-
-    pub fn is_file(&self) -> bool {
-        matches!(self, EntryKind::File(_))
-    }
-}
-
-impl sum_tree::Item for Entry {
-    type Summary = EntrySummary;
-
-    fn summary(&self) -> Self::Summary {
-        let non_ignored_count = if self.is_ignored || self.is_external {
-            0
-        } else {
-            1
-        };
-        let file_count;
-        let non_ignored_file_count;
-        if self.is_file() {
-            file_count = 1;
-            non_ignored_file_count = non_ignored_count;
-        } else {
-            file_count = 0;
-            non_ignored_file_count = 0;
-        }
-
-        let mut statuses = GitStatuses::default();
-        match self.git_status {
-            Some(status) => match status {
-                GitFileStatus::Added => statuses.added = 1,
-                GitFileStatus::Modified => statuses.modified = 1,
-                GitFileStatus::Conflict => statuses.conflict = 1,
-            },
-            None => {}
-        }
-
-        EntrySummary {
-            max_path: self.path.clone(),
-            count: 1,
-            non_ignored_count,
-            file_count,
-            non_ignored_file_count,
-            statuses,
-        }
-    }
-}
-
-impl sum_tree::KeyedItem for Entry {
-    type Key = PathKey;
-
-    fn key(&self) -> Self::Key {
-        PathKey(self.path.clone())
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct EntrySummary {
-    max_path: Arc<Path>,
-    count: usize,
-    non_ignored_count: usize,
-    file_count: usize,
-    non_ignored_file_count: usize,
-    statuses: GitStatuses,
-}
-
-impl Default for EntrySummary {
-    fn default() -> Self {
-        Self {
-            max_path: Arc::from(Path::new("")),
-            count: 0,
-            non_ignored_count: 0,
-            file_count: 0,
-            non_ignored_file_count: 0,
-            statuses: Default::default(),
-        }
-    }
-}
-
-impl sum_tree::Summary for EntrySummary {
-    type Context = ();
-
-    fn add_summary(&mut self, rhs: &Self, _: &()) {
-        self.max_path = rhs.max_path.clone();
-        self.count += rhs.count;
-        self.non_ignored_count += rhs.non_ignored_count;
-        self.file_count += rhs.file_count;
-        self.non_ignored_file_count += rhs.non_ignored_file_count;
-        self.statuses += rhs.statuses;
-    }
-}
-
-#[derive(Clone, Debug)]
-struct PathEntry {
-    id: ProjectEntryId,
-    path: Arc<Path>,
-    is_ignored: bool,
-    scan_id: usize,
-}
-
-impl sum_tree::Item for PathEntry {
-    type Summary = PathEntrySummary;
-
-    fn summary(&self) -> Self::Summary {
-        PathEntrySummary { max_id: self.id }
-    }
-}
-
-impl sum_tree::KeyedItem for PathEntry {
-    type Key = ProjectEntryId;
-
-    fn key(&self) -> Self::Key {
-        self.id
-    }
-}
-
-#[derive(Clone, Debug, Default)]
-struct PathEntrySummary {
-    max_id: ProjectEntryId,
-}
-
-impl sum_tree::Summary for PathEntrySummary {
-    type Context = ();
-
-    fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
-        self.max_id = summary.max_id;
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId {
-    fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) {
-        *self = summary.max_id;
-    }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
-pub struct PathKey(Arc<Path>);
-
-impl Default for PathKey {
-    fn default() -> Self {
-        Self(Path::new("").into())
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
-    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
-        self.0 = summary.max_path.clone();
-    }
-}
-
-struct BackgroundScanner {
-    state: Mutex<BackgroundScannerState>,
-    fs: Arc<dyn Fs>,
-    status_updates_tx: UnboundedSender<ScanState>,
-    executor: BackgroundExecutor,
-    scan_requests_rx: channel::Receiver<ScanRequest>,
-    path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
-    next_entry_id: Arc<AtomicUsize>,
-    phase: BackgroundScannerPhase,
-}
-
-#[derive(PartialEq)]
-enum BackgroundScannerPhase {
-    InitialScan,
-    EventsReceivedDuringInitialScan,
-    Events,
-}
-
-impl BackgroundScanner {
-    fn new(
-        snapshot: LocalSnapshot,
-        next_entry_id: Arc<AtomicUsize>,
-        fs: Arc<dyn Fs>,
-        status_updates_tx: UnboundedSender<ScanState>,
-        executor: BackgroundExecutor,
-        scan_requests_rx: channel::Receiver<ScanRequest>,
-        path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
-    ) -> Self {
-        Self {
-            fs,
-            status_updates_tx,
-            executor,
-            scan_requests_rx,
-            path_prefixes_to_scan_rx,
-            next_entry_id,
-            state: Mutex::new(BackgroundScannerState {
-                prev_snapshot: snapshot.snapshot.clone(),
-                snapshot,
-                scanned_dirs: Default::default(),
-                path_prefixes_to_scan: Default::default(),
-                paths_to_scan: Default::default(),
-                removed_entry_ids: Default::default(),
-                changed_paths: Default::default(),
-            }),
-            phase: BackgroundScannerPhase::InitialScan,
-        }
-    }
-
-    async fn run(
-        &mut self,
-        mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
-    ) {
-        use futures::FutureExt as _;
-
-        // Populate ignores above the root.
-        let root_abs_path = self.state.lock().snapshot.abs_path.clone();
-        for ancestor in root_abs_path.ancestors().skip(1) {
-            if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
-            {
-                self.state
-                    .lock()
-                    .snapshot
-                    .ignores_by_parent_abs_path
-                    .insert(ancestor.into(), (ignore.into(), false));
-            }
-        }
-
-        let (scan_job_tx, scan_job_rx) = channel::unbounded();
-        {
-            let mut state = self.state.lock();
-            state.snapshot.scan_id += 1;
-            if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
-                let ignore_stack = state
-                    .snapshot
-                    .ignore_stack_for_abs_path(&root_abs_path, true);
-                if ignore_stack.is_abs_path_ignored(&root_abs_path, true) {
-                    root_entry.is_ignored = true;
-                    state.insert_entry(root_entry.clone(), self.fs.as_ref());
-                }
-                state.enqueue_scan_dir(root_abs_path, &root_entry, &scan_job_tx);
-            }
-        };
-
-        // Perform an initial scan of the directory.
-        drop(scan_job_tx);
-        self.scan_dirs(true, scan_job_rx).await;
-        {
-            let mut state = self.state.lock();
-            state.snapshot.completed_scan_id = state.snapshot.scan_id;
-        }
-
-        self.send_status_update(false, None);
-
-        // Process any any FS events that occurred while performing the initial scan.
-        // For these events, update events cannot be as precise, because we didn't
-        // have the previous state loaded yet.
-        self.phase = BackgroundScannerPhase::EventsReceivedDuringInitialScan;
-        if let Poll::Ready(Some(events)) = futures::poll!(fs_events_rx.next()) {
-            let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
-            while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) {
-                paths.extend(more_events.into_iter().map(|e| e.path));
-            }
-            self.process_events(paths).await;
-        }
-
-        // Continue processing events until the worktree is dropped.
-        self.phase = BackgroundScannerPhase::Events;
-        loop {
-            select_biased! {
-                // Process any path refresh requests from the worktree. Prioritize
-                // these before handling changes reported by the filesystem.
-                request = self.scan_requests_rx.recv().fuse() => {
-                    let Ok(request) = request else { break };
-                    if !self.process_scan_request(request, false).await {
-                        return;
-                    }
-                }
-
-                path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => {
-                    let Ok(path_prefix) = path_prefix else { break };
-                    log::trace!("adding path prefix {:?}", path_prefix);
-
-                    let did_scan = self.forcibly_load_paths(&[path_prefix.clone()]).await;
-                    if did_scan {
-                        let abs_path =
-                        {
-                            let mut state = self.state.lock();
-                            state.path_prefixes_to_scan.insert(path_prefix.clone());
-                            state.snapshot.abs_path.join(&path_prefix)
-                        };
-
-                        if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() {
-                            self.process_events(vec![abs_path]).await;
-                        }
-                    }
-                }
-
-                events = fs_events_rx.next().fuse() => {
-                    let Some(events) = events else { break };
-                    let mut paths = events.into_iter().map(|e| e.path).collect::<Vec<_>>();
-                    while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) {
-                        paths.extend(more_events.into_iter().map(|e| e.path));
-                    }
-                    self.process_events(paths.clone()).await;
-                }
-            }
-        }
-    }
-
-    async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool {
-        log::debug!("rescanning paths {:?}", request.relative_paths);
-
-        request.relative_paths.sort_unstable();
-        self.forcibly_load_paths(&request.relative_paths).await;
-
-        let root_path = self.state.lock().snapshot.abs_path.clone();
-        let root_canonical_path = match self.fs.canonicalize(&root_path).await {
-            Ok(path) => path,
-            Err(err) => {
-                log::error!("failed to canonicalize root path: {}", err);
-                return false;
-            }
-        };
-        let abs_paths = request
-            .relative_paths
-            .iter()
-            .map(|path| {
-                if path.file_name().is_some() {
-                    root_canonical_path.join(path)
-                } else {
-                    root_canonical_path.clone()
-                }
-            })
-            .collect::<Vec<_>>();
-
-        self.reload_entries_for_paths(
-            root_path,
-            root_canonical_path,
-            &request.relative_paths,
-            abs_paths,
-            None,
-        )
-        .await;
-        self.send_status_update(scanning, Some(request.done))
-    }
-
-    async fn process_events(&mut self, mut abs_paths: Vec<PathBuf>) {
-        let root_path = self.state.lock().snapshot.abs_path.clone();
-        let root_canonical_path = match self.fs.canonicalize(&root_path).await {
-            Ok(path) => path,
-            Err(err) => {
-                log::error!("failed to canonicalize root path: {}", err);
-                return;
-            }
-        };
-
-        let mut relative_paths = Vec::with_capacity(abs_paths.len());
-        let mut dot_git_paths_to_reload = HashSet::default();
-        abs_paths.sort_unstable();
-        abs_paths.dedup_by(|a, b| a.starts_with(&b));
-        abs_paths.retain(|abs_path| {
-            let snapshot = &self.state.lock().snapshot;
-            {
-                let mut is_git_related = false;
-                if let Some(dot_git_dir) = abs_path
-                    .ancestors()
-                    .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
-                {
-                    let dot_git_path = dot_git_dir
-                        .strip_prefix(&root_canonical_path)
-                        .ok()
-                        .map(|path| path.to_path_buf())
-                        .unwrap_or_else(|| dot_git_dir.to_path_buf());
-                    dot_git_paths_to_reload.insert(dot_git_path.to_path_buf());
-                    is_git_related = true;
-                }
-
-                let relative_path: Arc<Path> =
-                    if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
-                        path.into()
-                    } else {
-                        log::error!(
-                        "ignoring event {abs_path:?} outside of root path {root_canonical_path:?}",
-                    );
-                        return false;
-                    };
-
-                let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
-                    snapshot
-                        .entry_for_path(parent)
-                        .map_or(false, |entry| entry.kind == EntryKind::Dir)
-                });
-                if !parent_dir_is_loaded {
-                    log::debug!("ignoring event {relative_path:?} within unloaded directory");
-                    return false;
-                }
-
-                if snapshot.is_path_excluded(relative_path.to_path_buf()) {
-                    if !is_git_related {
-                        log::debug!("ignoring FS event for excluded path {relative_path:?}");
-                    }
-                    return false;
-                }
-
-                relative_paths.push(relative_path);
-                true
-            }
-        });
-
-        if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() {
-            return;
-        }
-
-        if !relative_paths.is_empty() {
-            log::debug!("received fs events {:?}", relative_paths);
-
-            let (scan_job_tx, scan_job_rx) = channel::unbounded();
-            self.reload_entries_for_paths(
-                root_path,
-                root_canonical_path,
-                &relative_paths,
-                abs_paths,
-                Some(scan_job_tx.clone()),
-            )
-            .await;
-            drop(scan_job_tx);
-            self.scan_dirs(false, scan_job_rx).await;
-
-            let (scan_job_tx, scan_job_rx) = channel::unbounded();
-            self.update_ignore_statuses(scan_job_tx).await;
-            self.scan_dirs(false, scan_job_rx).await;
-        }
-
-        {
-            let mut state = self.state.lock();
-            if !dot_git_paths_to_reload.is_empty() {
-                if relative_paths.is_empty() {
-                    state.snapshot.scan_id += 1;
-                }
-                log::debug!("reloading repositories: {dot_git_paths_to_reload:?}");
-                state.reload_repositories(&dot_git_paths_to_reload, self.fs.as_ref());
-            }
-            state.snapshot.completed_scan_id = state.snapshot.scan_id;
-            for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
-                state.scanned_dirs.remove(&entry_id);
-            }
-        }
-
-        self.send_status_update(false, None);
-    }
-
-    async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> bool {
-        let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
-        {
-            let mut state = self.state.lock();
-            let root_path = state.snapshot.abs_path.clone();
-            for path in paths {
-                for ancestor in path.ancestors() {
-                    if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
-                        if entry.kind == EntryKind::UnloadedDir {
-                            let abs_path = root_path.join(ancestor);
-                            state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx);
-                            state.paths_to_scan.insert(path.clone());
-                            break;
-                        }
-                    }
-                }
-            }
-            drop(scan_job_tx);
-        }
-        while let Some(job) = scan_job_rx.next().await {
-            self.scan_dir(&job).await.log_err();
-        }
-
-        mem::take(&mut self.state.lock().paths_to_scan).len() > 0
-    }
-
-    async fn scan_dirs(
-        &self,
-        enable_progress_updates: bool,
-        scan_jobs_rx: channel::Receiver<ScanJob>,
-    ) {
-        use futures::FutureExt as _;
-
-        if self
-            .status_updates_tx
-            .unbounded_send(ScanState::Started)
-            .is_err()
-        {
-            return;
-        }
-
-        let progress_update_count = AtomicUsize::new(0);
-        self.executor
-            .scoped(|scope| {
-                for _ in 0..self.executor.num_cpus() {
-                    scope.spawn(async {
-                        let mut last_progress_update_count = 0;
-                        let progress_update_timer = self.progress_timer(enable_progress_updates).fuse();
-                        futures::pin_mut!(progress_update_timer);
-
-                        loop {
-                            select_biased! {
-                                // Process any path refresh requests before moving on to process
-                                // the scan queue, so that user operations are prioritized.
-                                request = self.scan_requests_rx.recv().fuse() => {
-                                    let Ok(request) = request else { break };
-                                    if !self.process_scan_request(request, true).await {
-                                        return;
-                                    }
-                                }
-
-                                // Send periodic progress updates to the worktree. Use an atomic counter
-                                // to ensure that only one of the workers sends a progress update after
-                                // the update interval elapses.
-                                _ = progress_update_timer => {
-                                    match progress_update_count.compare_exchange(
-                                        last_progress_update_count,
-                                        last_progress_update_count + 1,
-                                        SeqCst,
-                                        SeqCst
-                                    ) {
-                                        Ok(_) => {
-                                            last_progress_update_count += 1;
-                                            self.send_status_update(true, None);
-                                        }
-                                        Err(count) => {
-                                            last_progress_update_count = count;
-                                        }
-                                    }
-                                    progress_update_timer.set(self.progress_timer(enable_progress_updates).fuse());
-                                }
-
-                                // Recursively load directories from the file system.
-                                job = scan_jobs_rx.recv().fuse() => {
-                                    let Ok(job) = job else { break };
-                                    if let Err(err) = self.scan_dir(&job).await {
-                                        if job.path.as_ref() != Path::new("") {
-                                            log::error!("error scanning directory {:?}: {}", job.abs_path, err);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    })
-                }
-            })
-            .await;
-    }
-
-    fn send_status_update(&self, scanning: bool, barrier: Option<barrier::Sender>) -> bool {
-        let mut state = self.state.lock();
-        if state.changed_paths.is_empty() && scanning {
-            return true;
-        }
-
-        let new_snapshot = state.snapshot.clone();
-        let old_snapshot = mem::replace(&mut state.prev_snapshot, new_snapshot.snapshot.clone());
-        let changes = self.build_change_set(&old_snapshot, &new_snapshot, &state.changed_paths);
-        state.changed_paths.clear();
-
-        self.status_updates_tx
-            .unbounded_send(ScanState::Updated {
-                snapshot: new_snapshot,
-                changes,
-                scanning,
-                barrier,
-            })
-            .is_ok()
-    }
-
-    async fn scan_dir(&self, job: &ScanJob) -> Result<()> {
-        let root_abs_path;
-        let mut ignore_stack;
-        let mut new_ignore;
-        let root_char_bag;
-        let next_entry_id;
-        {
-            let state = self.state.lock();
-            let snapshot = &state.snapshot;
-            root_abs_path = snapshot.abs_path().clone();
-            if snapshot.is_path_excluded(job.path.to_path_buf()) {
-                log::error!("skipping excluded directory {:?}", job.path);
-                return Ok(());
-            }
-            log::debug!("scanning directory {:?}", job.path);
-            ignore_stack = job.ignore_stack.clone();
-            new_ignore = None;
-            root_char_bag = snapshot.root_char_bag;
-            next_entry_id = self.next_entry_id.clone();
-            drop(state);
-        }
-
-        let mut dotgit_path = None;
-        let mut root_canonical_path = None;
-        let mut new_entries: Vec<Entry> = Vec::new();
-        let mut new_jobs: Vec<Option<ScanJob>> = Vec::new();
-        let mut child_paths = self.fs.read_dir(&job.abs_path).await?;
-        while let Some(child_abs_path) = child_paths.next().await {
-            let child_abs_path: Arc<Path> = match child_abs_path {
-                Ok(child_abs_path) => child_abs_path.into(),
-                Err(error) => {
-                    log::error!("error processing entry {:?}", error);
-                    continue;
-                }
-            };
-            let child_name = child_abs_path.file_name().unwrap();
-            let child_path: Arc<Path> = job.path.join(child_name).into();
-            // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
-            if child_name == *GITIGNORE {
-                match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
-                    Ok(ignore) => {
-                        let ignore = Arc::new(ignore);
-                        ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
-                        new_ignore = Some(ignore);
-                    }
-                    Err(error) => {
-                        log::error!(
-                            "error loading .gitignore file {:?} - {:?}",
-                            child_name,
-                            error
-                        );
-                    }
-                }
-
-                // Update ignore status of any child entries we've already processed to reflect the
-                // ignore file in the current directory. Because `.gitignore` starts with a `.`,
-                // there should rarely be too numerous. Update the ignore stack associated with any
-                // new jobs as well.
-                let mut new_jobs = new_jobs.iter_mut();
-                for entry in &mut new_entries {
-                    let entry_abs_path = root_abs_path.join(&entry.path);
-                    entry.is_ignored =
-                        ignore_stack.is_abs_path_ignored(&entry_abs_path, entry.is_dir());
-
-                    if entry.is_dir() {
-                        if let Some(job) = new_jobs.next().expect("missing scan job for entry") {
-                            job.ignore_stack = if entry.is_ignored {
-                                IgnoreStack::all()
-                            } else {
-                                ignore_stack.clone()
-                            };
-                        }
-                    }
-                }
-            }
-            // If we find a .git, we'll need to load the repository.
-            else if child_name == *DOT_GIT {
-                dotgit_path = Some(child_path.clone());
-            }
-
-            {
-                let relative_path = job.path.join(child_name);
-                let mut state = self.state.lock();
-                if state.snapshot.is_path_excluded(relative_path.clone()) {
-                    log::debug!("skipping excluded child entry {relative_path:?}");
-                    state.remove_path(&relative_path);
-                    continue;
-                }
-                drop(state);
-            }
-
-            let child_metadata = match self.fs.metadata(&child_abs_path).await {
-                Ok(Some(metadata)) => metadata,
-                Ok(None) => continue,
-                Err(err) => {
-                    log::error!("error processing {child_abs_path:?}: {err:?}");
-                    continue;
-                }
-            };
-
-            let mut child_entry = Entry::new(
-                child_path.clone(),
-                &child_metadata,
-                &next_entry_id,
-                root_char_bag,
-            );
-
-            if job.is_external {
-                child_entry.is_external = true;
-            } else if child_metadata.is_symlink {
-                let canonical_path = match self.fs.canonicalize(&child_abs_path).await {
-                    Ok(path) => path,
-                    Err(err) => {
-                        log::error!(
-                            "error reading target of symlink {:?}: {:?}",
-                            child_abs_path,
-                            err
-                        );
-                        continue;
-                    }
-                };
-
-                // lazily canonicalize the root path in order to determine if
-                // symlinks point outside of the worktree.
-                let root_canonical_path = match &root_canonical_path {
-                    Some(path) => path,
-                    None => match self.fs.canonicalize(&root_abs_path).await {
-                        Ok(path) => root_canonical_path.insert(path),
-                        Err(err) => {
-                            log::error!("error canonicalizing root {:?}: {:?}", root_abs_path, err);
-                            continue;
-                        }
-                    },
-                };
-
-                if !canonical_path.starts_with(root_canonical_path) {
-                    child_entry.is_external = true;
-                }
-            }
-
-            if child_entry.is_dir() {
-                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
-
-                // Avoid recursing until crash in the case of a recursive symlink
-                if !job.ancestor_inodes.contains(&child_entry.inode) {
-                    let mut ancestor_inodes = job.ancestor_inodes.clone();
-                    ancestor_inodes.insert(child_entry.inode);
-
-                    new_jobs.push(Some(ScanJob {
-                        abs_path: child_abs_path,
-                        path: child_path,
-                        is_external: child_entry.is_external,
-                        ignore_stack: if child_entry.is_ignored {
-                            IgnoreStack::all()
-                        } else {
-                            ignore_stack.clone()
-                        },
-                        ancestor_inodes,
-                        scan_queue: job.scan_queue.clone(),
-                        containing_repository: job.containing_repository.clone(),
-                    }));
-                } else {
-                    new_jobs.push(None);
-                }
-            } else {
-                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
-                if !child_entry.is_ignored {
-                    if let Some((repository_dir, repository, staged_statuses)) =
-                        &job.containing_repository
-                    {
-                        if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) {
-                            let repo_path = RepoPath(repo_path.into());
-                            child_entry.git_status = combine_git_statuses(
-                                staged_statuses.get(&repo_path).copied(),
-                                repository
-                                    .lock()
-                                    .unstaged_status(&repo_path, child_entry.mtime),
-                            );
-                        }
-                    }
-                }
-            }
-
-            new_entries.push(child_entry);
-        }
-
-        let mut state = self.state.lock();
-
-        // Identify any subdirectories that should not be scanned.
-        let mut job_ix = 0;
-        for entry in &mut new_entries {
-            state.reuse_entry_id(entry);
-            if entry.is_dir() {
-                if state.should_scan_directory(&entry) {
-                    job_ix += 1;
-                } else {
-                    log::debug!("defer scanning directory {:?}", entry.path);
-                    entry.kind = EntryKind::UnloadedDir;
-                    new_jobs.remove(job_ix);
-                }
-            }
-        }
-
-        state.populate_dir(&job.path, new_entries, new_ignore);
-
-        let repository =
-            dotgit_path.and_then(|path| state.build_git_repository(path, self.fs.as_ref()));
-
-        for new_job in new_jobs {
-            if let Some(mut new_job) = new_job {
-                if let Some(containing_repository) = &repository {
-                    new_job.containing_repository = Some(containing_repository.clone());
-                }
-
-                job.scan_queue
-                    .try_send(new_job)
-                    .expect("channel is unbounded");
-            }
-        }
-
-        Ok(())
-    }
-
-    async fn reload_entries_for_paths(
-        &self,
-        root_abs_path: Arc<Path>,
-        root_canonical_path: PathBuf,
-        relative_paths: &[Arc<Path>],
-        abs_paths: Vec<PathBuf>,
-        scan_queue_tx: Option<Sender<ScanJob>>,
-    ) {
-        let metadata = futures::future::join_all(
-            abs_paths
-                .iter()
-                .map(|abs_path| async move {
-                    let metadata = self.fs.metadata(&abs_path).await?;
-                    if let Some(metadata) = metadata {
-                        let canonical_path = self.fs.canonicalize(&abs_path).await?;
-                        anyhow::Ok(Some((metadata, canonical_path)))
-                    } else {
-                        Ok(None)
-                    }
-                })
-                .collect::<Vec<_>>(),
-        )
-        .await;
-
-        let mut state = self.state.lock();
-        let snapshot = &mut state.snapshot;
-        let is_idle = snapshot.completed_scan_id == snapshot.scan_id;
-        let doing_recursive_update = scan_queue_tx.is_some();
-        snapshot.scan_id += 1;
-        if is_idle && !doing_recursive_update {
-            snapshot.completed_scan_id = snapshot.scan_id;
-        }
-
-        // Remove any entries for paths that no longer exist or are being recursively
-        // refreshed. Do this before adding any new entries, so that renames can be
-        // detected regardless of the order of the paths.
-        for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
-            if matches!(metadata, Ok(None)) || doing_recursive_update {
-                log::trace!("remove path {:?}", path);
-                state.remove_path(path);
-            }
-        }
-
-        for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
-            let abs_path: Arc<Path> = root_abs_path.join(&path).into();
-            match metadata {
-                Ok(Some((metadata, canonical_path))) => {
-                    let ignore_stack = state
-                        .snapshot
-                        .ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
-
-                    let mut fs_entry = Entry::new(
-                        path.clone(),
-                        metadata,
-                        self.next_entry_id.as_ref(),
-                        state.snapshot.root_char_bag,
-                    );
-                    let is_dir = fs_entry.is_dir();
-                    fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir);
-                    fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
-
-                    if !is_dir && !fs_entry.is_ignored {
-                        if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) {
-                            if let Ok(repo_path) = path.strip_prefix(work_dir.0) {
-                                let repo_path = RepoPath(repo_path.into());
-                                let repo = repo.repo_ptr.lock();
-                                fs_entry.git_status = repo.status(&repo_path, fs_entry.mtime);
-                            }
-                        }
-                    }
-
-                    if let (Some(scan_queue_tx), true) = (&scan_queue_tx, fs_entry.is_dir()) {
-                        if state.should_scan_directory(&fs_entry) {
-                            state.enqueue_scan_dir(abs_path, &fs_entry, scan_queue_tx);
-                        } else {
-                            fs_entry.kind = EntryKind::UnloadedDir;
-                        }
-                    }
-
-                    state.insert_entry(fs_entry, self.fs.as_ref());
-                }
-                Ok(None) => {
-                    self.remove_repo_path(&path, &mut state.snapshot);
-                }
-                Err(err) => {
-                    // TODO - create a special 'error' entry in the entries tree to mark this
-                    log::error!("error reading file {abs_path:?} on event: {err:#}");
-                }
-            }
-        }
-
-        util::extend_sorted(
-            &mut state.changed_paths,
-            relative_paths.iter().cloned(),
-            usize::MAX,
-            Ord::cmp,
-        );
-    }
-
-    fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> {
-        if !path
-            .components()
-            .any(|component| component.as_os_str() == *DOT_GIT)
-        {
-            if let Some(repository) = snapshot.repository_for_work_directory(path) {
-                let entry = repository.work_directory.0;
-                snapshot.git_repositories.remove(&entry);
-                snapshot
-                    .snapshot
-                    .repository_entries
-                    .remove(&RepositoryWorkDirectory(path.into()));
-                return Some(());
-            }
-        }
-
-        // TODO statuses
-        // Track when a .git is removed and iterate over the file system there
-
-        Some(())
-    }
-
-    async fn update_ignore_statuses(&self, scan_job_tx: Sender<ScanJob>) {
-        use futures::FutureExt as _;
-
-        let mut snapshot = self.state.lock().snapshot.clone();
-        let mut ignores_to_update = Vec::new();
-        let mut ignores_to_delete = Vec::new();
-        let abs_path = snapshot.abs_path.clone();
-        for (parent_abs_path, (_, needs_update)) in &mut snapshot.ignores_by_parent_abs_path {
-            if let Ok(parent_path) = parent_abs_path.strip_prefix(&abs_path) {
-                if *needs_update {
-                    *needs_update = false;
-                    if snapshot.snapshot.entry_for_path(parent_path).is_some() {
-                        ignores_to_update.push(parent_abs_path.clone());
-                    }
-                }
-
-                let ignore_path = parent_path.join(&*GITIGNORE);
-                if snapshot.snapshot.entry_for_path(ignore_path).is_none() {
-                    ignores_to_delete.push(parent_abs_path.clone());
-                }
-            }
-        }
-
-        for parent_abs_path in ignores_to_delete {
-            snapshot.ignores_by_parent_abs_path.remove(&parent_abs_path);
-            self.state
-                .lock()
-                .snapshot
-                .ignores_by_parent_abs_path
-                .remove(&parent_abs_path);
-        }
-
-        let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
-        ignores_to_update.sort_unstable();
-        let mut ignores_to_update = ignores_to_update.into_iter().peekable();
-        while let Some(parent_abs_path) = ignores_to_update.next() {
-            while ignores_to_update
-                .peek()
-                .map_or(false, |p| p.starts_with(&parent_abs_path))
-            {
-                ignores_to_update.next().unwrap();
-            }
-
-            let ignore_stack = snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
-            smol::block_on(ignore_queue_tx.send(UpdateIgnoreStatusJob {
-                abs_path: parent_abs_path,
-                ignore_stack,
-                ignore_queue: ignore_queue_tx.clone(),
-                scan_queue: scan_job_tx.clone(),
-            }))
-            .unwrap();
-        }
-        drop(ignore_queue_tx);
-
-        self.executor
-            .scoped(|scope| {
-                for _ in 0..self.executor.num_cpus() {
-                    scope.spawn(async {
-                        loop {
-                            select_biased! {
-                                // Process any path refresh requests before moving on to process
-                                // the queue of ignore statuses.
-                                request = self.scan_requests_rx.recv().fuse() => {
-                                    let Ok(request) = request else { break };
-                                    if !self.process_scan_request(request, true).await {
-                                        return;
-                                    }
-                                }
-
-                                // Recursively process directories whose ignores have changed.
-                                job = ignore_queue_rx.recv().fuse() => {
-                                    let Ok(job) = job else { break };
-                                    self.update_ignore_status(job, &snapshot).await;
-                                }
-                            }
-                        }
-                    });
-                }
-            })
-            .await;
-    }
-
-    async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
-        log::trace!("update ignore status {:?}", job.abs_path);
-
-        let mut ignore_stack = job.ignore_stack;
-        if let Some((ignore, _)) = snapshot.ignores_by_parent_abs_path.get(&job.abs_path) {
-            ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
-        }
-
-        let mut entries_by_id_edits = Vec::new();
-        let mut entries_by_path_edits = Vec::new();
-        let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap();
-        for mut entry in snapshot.child_entries(path).cloned() {
-            let was_ignored = entry.is_ignored;
-            let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
-            entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
-            if entry.is_dir() {
-                let child_ignore_stack = if entry.is_ignored {
-                    IgnoreStack::all()
-                } else {
-                    ignore_stack.clone()
-                };
-
-                // Scan any directories that were previously ignored and weren't previously scanned.
-                if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
-                    let state = self.state.lock();
-                    if state.should_scan_directory(&entry) {
-                        state.enqueue_scan_dir(abs_path.clone(), &entry, &job.scan_queue);
-                    }
-                }
-
-                job.ignore_queue
-                    .send(UpdateIgnoreStatusJob {
-                        abs_path: abs_path.clone(),
-                        ignore_stack: child_ignore_stack,
-                        ignore_queue: job.ignore_queue.clone(),
-                        scan_queue: job.scan_queue.clone(),
-                    })
-                    .await
-                    .unwrap();
-            }
-
-            if entry.is_ignored != was_ignored {
-                let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
-                path_entry.scan_id = snapshot.scan_id;
-                path_entry.is_ignored = entry.is_ignored;
-                entries_by_id_edits.push(Edit::Insert(path_entry));
-                entries_by_path_edits.push(Edit::Insert(entry));
-            }
-        }
-
-        let state = &mut self.state.lock();
-        for edit in &entries_by_path_edits {
-            if let Edit::Insert(entry) = edit {
-                if let Err(ix) = state.changed_paths.binary_search(&entry.path) {
-                    state.changed_paths.insert(ix, entry.path.clone());
-                }
-            }
-        }
-
-        state
-            .snapshot
-            .entries_by_path
-            .edit(entries_by_path_edits, &());
-        state.snapshot.entries_by_id.edit(entries_by_id_edits, &());
-    }
-
-    fn build_change_set(
-        &self,
-        old_snapshot: &Snapshot,
-        new_snapshot: &Snapshot,
-        event_paths: &[Arc<Path>],
-    ) -> UpdatedEntriesSet {
-        use BackgroundScannerPhase::*;
-        use PathChange::{Added, AddedOrUpdated, Loaded, Removed, Updated};
-
-        // Identify which paths have changed. Use the known set of changed
-        // parent paths to optimize the search.
-        let mut changes = Vec::new();
-        let mut old_paths = old_snapshot.entries_by_path.cursor::<PathKey>();
-        let mut new_paths = new_snapshot.entries_by_path.cursor::<PathKey>();
-        let mut last_newly_loaded_dir_path = None;
-        old_paths.next(&());
-        new_paths.next(&());
-        for path in event_paths {
-            let path = PathKey(path.clone());
-            if old_paths.item().map_or(false, |e| e.path < path.0) {
-                old_paths.seek_forward(&path, Bias::Left, &());
-            }
-            if new_paths.item().map_or(false, |e| e.path < path.0) {
-                new_paths.seek_forward(&path, Bias::Left, &());
-            }
-            loop {
-                match (old_paths.item(), new_paths.item()) {
-                    (Some(old_entry), Some(new_entry)) => {
-                        if old_entry.path > path.0
-                            && new_entry.path > path.0
-                            && !old_entry.path.starts_with(&path.0)
-                            && !new_entry.path.starts_with(&path.0)
-                        {
-                            break;
-                        }
-
-                        match Ord::cmp(&old_entry.path, &new_entry.path) {
-                            Ordering::Less => {
-                                changes.push((old_entry.path.clone(), old_entry.id, Removed));
-                                old_paths.next(&());
-                            }
-                            Ordering::Equal => {
-                                if self.phase == EventsReceivedDuringInitialScan {
-                                    if old_entry.id != new_entry.id {
-                                        changes.push((
-                                            old_entry.path.clone(),
-                                            old_entry.id,
-                                            Removed,
-                                        ));
-                                    }
-                                    // If the worktree was not fully initialized when this event was generated,
-                                    // we can't know whether this entry was added during the scan or whether
-                                    // it was merely updated.
-                                    changes.push((
-                                        new_entry.path.clone(),
-                                        new_entry.id,
-                                        AddedOrUpdated,
-                                    ));
-                                } else if old_entry.id != new_entry.id {
-                                    changes.push((old_entry.path.clone(), old_entry.id, Removed));
-                                    changes.push((new_entry.path.clone(), new_entry.id, Added));
-                                } else if old_entry != new_entry {
-                                    if old_entry.kind.is_unloaded() {
-                                        last_newly_loaded_dir_path = Some(&new_entry.path);
-                                        changes.push((
-                                            new_entry.path.clone(),
-                                            new_entry.id,
-                                            Loaded,
-                                        ));
-                                    } else {
-                                        changes.push((
-                                            new_entry.path.clone(),
-                                            new_entry.id,
-                                            Updated,
-                                        ));
-                                    }
-                                }
-                                old_paths.next(&());
-                                new_paths.next(&());
-                            }
-                            Ordering::Greater => {
-                                let is_newly_loaded = self.phase == InitialScan
-                                    || last_newly_loaded_dir_path
-                                        .as_ref()
-                                        .map_or(false, |dir| new_entry.path.starts_with(&dir));
-                                changes.push((
-                                    new_entry.path.clone(),
-                                    new_entry.id,
-                                    if is_newly_loaded { Loaded } else { Added },
-                                ));
-                                new_paths.next(&());
-                            }
-                        }
-                    }
-                    (Some(old_entry), None) => {
-                        changes.push((old_entry.path.clone(), old_entry.id, Removed));
-                        old_paths.next(&());
-                    }
-                    (None, Some(new_entry)) => {
-                        let is_newly_loaded = self.phase == InitialScan
-                            || last_newly_loaded_dir_path
-                                .as_ref()
-                                .map_or(false, |dir| new_entry.path.starts_with(&dir));
-                        changes.push((
-                            new_entry.path.clone(),
-                            new_entry.id,
-                            if is_newly_loaded { Loaded } else { Added },
-                        ));
-                        new_paths.next(&());
-                    }
-                    (None, None) => break,
-                }
-            }
-        }
-
-        changes.into()
-    }
-
-    async fn progress_timer(&self, running: bool) {
-        if !running {
-            return futures::future::pending().await;
-        }
-
-        #[cfg(any(test, feature = "test-support"))]
-        if self.fs.is_fake() {
-            return self.executor.simulate_random_delay().await;
-        }
-
-        smol::Timer::after(Duration::from_millis(100)).await;
-    }
-}
-
-fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
-    let mut result = root_char_bag;
-    result.extend(
-        path.to_string_lossy()
-            .chars()
-            .map(|c| c.to_ascii_lowercase()),
-    );
-    result
-}
-
-struct ScanJob {
-    abs_path: Arc<Path>,
-    path: Arc<Path>,
-    ignore_stack: Arc<IgnoreStack>,
-    scan_queue: Sender<ScanJob>,
-    ancestor_inodes: TreeSet<u64>,
-    is_external: bool,
-    containing_repository: Option<(
-        RepositoryWorkDirectory,
-        Arc<Mutex<dyn GitRepository>>,
-        TreeMap<RepoPath, GitFileStatus>,
-    )>,
-}
-
-struct UpdateIgnoreStatusJob {
-    abs_path: Arc<Path>,
-    ignore_stack: Arc<IgnoreStack>,
-    ignore_queue: Sender<UpdateIgnoreStatusJob>,
-    scan_queue: Sender<ScanJob>,
-}
-
-pub trait WorktreeModelHandle {
-    #[cfg(any(test, feature = "test-support"))]
-    fn flush_fs_events<'a>(
-        &self,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> futures::future::LocalBoxFuture<'a, ()>;
-}
-
-impl WorktreeModelHandle for Model<Worktree> {
-    // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
-    // occurred before the worktree was constructed. These events can cause the worktree to perform
-    // extra directory scans, and emit extra scan-state notifications.
-    //
-    // This function mutates the worktree's directory and waits for those mutations to be picked up,
-    // to ensure that all redundant FS events have already been processed.
-    #[cfg(any(test, feature = "test-support"))]
-    fn flush_fs_events<'a>(
-        &self,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> futures::future::LocalBoxFuture<'a, ()> {
-        let file_name = "fs-event-sentinel";
-
-        let tree = self.clone();
-        let (fs, root_path) = self.update(cx, |tree, _| {
-            let tree = tree.as_local().unwrap();
-            (tree.fs.clone(), tree.abs_path().clone())
-        });
-
-        async move {
-            fs.create_file(&root_path.join(file_name), Default::default())
-                .await
-                .unwrap();
-
-            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some())
-                .await;
-
-            fs.remove_file(&root_path.join(file_name), Default::default())
-                .await
-                .unwrap();
-            cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none())
-                .await;
-
-            cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-                .await;
-        }
-        .boxed_local()
-    }
-}
-
-#[derive(Clone, Debug)]
-struct TraversalProgress<'a> {
-    max_path: &'a Path,
-    count: usize,
-    non_ignored_count: usize,
-    file_count: usize,
-    non_ignored_file_count: usize,
-}
-
-impl<'a> TraversalProgress<'a> {
-    fn count(&self, include_dirs: bool, include_ignored: bool) -> usize {
-        match (include_ignored, include_dirs) {
-            (true, true) => self.count,
-            (true, false) => self.file_count,
-            (false, true) => self.non_ignored_count,
-            (false, false) => self.non_ignored_file_count,
-        }
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> {
-    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
-        self.max_path = summary.max_path.as_ref();
-        self.count += summary.count;
-        self.non_ignored_count += summary.non_ignored_count;
-        self.file_count += summary.file_count;
-        self.non_ignored_file_count += summary.non_ignored_file_count;
-    }
-}
-
-impl<'a> Default for TraversalProgress<'a> {
-    fn default() -> Self {
-        Self {
-            max_path: Path::new(""),
-            count: 0,
-            non_ignored_count: 0,
-            file_count: 0,
-            non_ignored_file_count: 0,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Default, Copy)]
-struct GitStatuses {
-    added: usize,
-    modified: usize,
-    conflict: usize,
-}
-
-impl AddAssign for GitStatuses {
-    fn add_assign(&mut self, rhs: Self) {
-        self.added += rhs.added;
-        self.modified += rhs.modified;
-        self.conflict += rhs.conflict;
-    }
-}
-
-impl Sub for GitStatuses {
-    type Output = GitStatuses;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        GitStatuses {
-            added: self.added - rhs.added,
-            modified: self.modified - rhs.modified,
-            conflict: self.conflict - rhs.conflict,
-        }
-    }
-}
-
-impl<'a> sum_tree::Dimension<'a, EntrySummary> for GitStatuses {
-    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
-        *self += summary.statuses
-    }
-}
-
-pub struct Traversal<'a> {
-    cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
-    include_ignored: bool,
-    include_dirs: bool,
-}
-
-impl<'a> Traversal<'a> {
-    pub fn advance(&mut self) -> bool {
-        self.cursor.seek_forward(
-            &TraversalTarget::Count {
-                count: self.end_offset() + 1,
-                include_dirs: self.include_dirs,
-                include_ignored: self.include_ignored,
-            },
-            Bias::Left,
-            &(),
-        )
-    }
-
-    pub fn advance_to_sibling(&mut self) -> bool {
-        while let Some(entry) = self.cursor.item() {
-            self.cursor.seek_forward(
-                &TraversalTarget::PathSuccessor(&entry.path),
-                Bias::Left,
-                &(),
-            );
-            if let Some(entry) = self.cursor.item() {
-                if (self.include_dirs || !entry.is_dir())
-                    && (self.include_ignored || !entry.is_ignored)
-                {
-                    return true;
-                }
-            }
-        }
-        false
-    }
-
-    pub fn entry(&self) -> Option<&'a Entry> {
-        self.cursor.item()
-    }
-
-    pub fn start_offset(&self) -> usize {
-        self.cursor
-            .start()
-            .count(self.include_dirs, self.include_ignored)
-    }
-
-    pub fn end_offset(&self) -> usize {
-        self.cursor
-            .end(&())
-            .count(self.include_dirs, self.include_ignored)
-    }
-}
-
-impl<'a> Iterator for Traversal<'a> {
-    type Item = &'a Entry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(item) = self.entry() {
-            self.advance();
-            Some(item)
-        } else {
-            None
-        }
-    }
-}
-
-#[derive(Debug)]
-enum TraversalTarget<'a> {
-    Path(&'a Path),
-    PathSuccessor(&'a Path),
-    Count {
-        count: usize,
-        include_ignored: bool,
-        include_dirs: bool,
-    },
-}
-
-impl<'a, 'b> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'b> {
-    fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
-        match self {
-            TraversalTarget::Path(path) => path.cmp(&cursor_location.max_path),
-            TraversalTarget::PathSuccessor(path) => {
-                if !cursor_location.max_path.starts_with(path) {
-                    Ordering::Equal
-                } else {
-                    Ordering::Greater
-                }
-            }
-            TraversalTarget::Count {
-                count,
-                include_dirs,
-                include_ignored,
-            } => Ord::cmp(
-                count,
-                &cursor_location.count(*include_dirs, *include_ignored),
-            ),
-        }
-    }
-}
-
-impl<'a, 'b> SeekTarget<'a, EntrySummary, (TraversalProgress<'a>, GitStatuses)>
-    for TraversalTarget<'b>
-{
-    fn cmp(&self, cursor_location: &(TraversalProgress<'a>, GitStatuses), _: &()) -> Ordering {
-        self.cmp(&cursor_location.0, &())
-    }
-}
-
-struct ChildEntriesIter<'a> {
-    parent_path: &'a Path,
-    traversal: Traversal<'a>,
-}
-
-impl<'a> Iterator for ChildEntriesIter<'a> {
-    type Item = &'a Entry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(item) = self.traversal.entry() {
-            if item.path.starts_with(&self.parent_path) {
-                self.traversal.advance_to_sibling();
-                return Some(item);
-            }
-        }
-        None
-    }
-}
-
-pub struct DescendentEntriesIter<'a> {
-    parent_path: &'a Path,
-    traversal: Traversal<'a>,
-}
-
-impl<'a> Iterator for DescendentEntriesIter<'a> {
-    type Item = &'a Entry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(item) = self.traversal.entry() {
-            if item.path.starts_with(&self.parent_path) {
-                self.traversal.advance();
-                return Some(item);
-            }
-        }
-        None
-    }
-}
-
-impl<'a> From<&'a Entry> for proto::Entry {
-    fn from(entry: &'a Entry) -> Self {
-        Self {
-            id: entry.id.to_proto(),
-            is_dir: entry.is_dir(),
-            path: entry.path.to_string_lossy().into(),
-            inode: entry.inode,
-            mtime: Some(entry.mtime.into()),
-            is_symlink: entry.is_symlink,
-            is_ignored: entry.is_ignored,
-            is_external: entry.is_external,
-            git_status: entry.git_status.map(git_status_to_proto),
-        }
-    }
-}
-
-impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
-    type Error = anyhow::Error;
-
-    fn try_from((root_char_bag, entry): (&'a CharBag, proto::Entry)) -> Result<Self> {
-        if let Some(mtime) = entry.mtime {
-            let kind = if entry.is_dir {
-                EntryKind::Dir
-            } else {
-                let mut char_bag = *root_char_bag;
-                char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
-                EntryKind::File(char_bag)
-            };
-            let path: Arc<Path> = PathBuf::from(entry.path).into();
-            Ok(Entry {
-                id: ProjectEntryId::from_proto(entry.id),
-                kind,
-                path,
-                inode: entry.inode,
-                mtime: mtime.into(),
-                is_symlink: entry.is_symlink,
-                is_ignored: entry.is_ignored,
-                is_external: entry.is_external,
-                git_status: git_status_from_proto(entry.git_status),
-            })
-        } else {
-            Err(anyhow!(
-                "missing mtime in remote worktree entry {:?}",
-                entry.path
-            ))
-        }
-    }
-}
-
-fn combine_git_statuses(
-    staged: Option<GitFileStatus>,
-    unstaged: Option<GitFileStatus>,
-) -> Option<GitFileStatus> {
-    if let Some(staged) = staged {
-        if let Some(unstaged) = unstaged {
-            if unstaged != staged {
-                Some(GitFileStatus::Modified)
-            } else {
-                Some(staged)
-            }
-        } else {
-            Some(staged)
-        }
-    } else {
-        unstaged
-    }
-}
-
-fn git_status_from_proto(git_status: Option<i32>) -> Option<GitFileStatus> {
-    git_status.and_then(|status| {
-        proto::GitStatus::from_i32(status).map(|status| match status {
-            proto::GitStatus::Added => GitFileStatus::Added,
-            proto::GitStatus::Modified => GitFileStatus::Modified,
-            proto::GitStatus::Conflict => GitFileStatus::Conflict,
-        })
-    })
-}
-
-fn git_status_to_proto(status: GitFileStatus) -> i32 {
-    match status {
-        GitFileStatus::Added => proto::GitStatus::Added as i32,
-        GitFileStatus::Modified => proto::GitStatus::Modified as i32,
-        GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
-    }
-}

crates/project2/src/worktree_tests.rs 🔗

@@ -1,2462 +0,0 @@
-use crate::{
-    project_settings::ProjectSettings,
-    worktree::{Event, Snapshot, WorktreeModelHandle},
-    Entry, EntryKind, PathChange, Project, Worktree,
-};
-use anyhow::Result;
-use client::Client;
-use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
-use git::GITIGNORE;
-use gpui::{ModelContext, Task, TestAppContext};
-use parking_lot::Mutex;
-use postage::stream::Stream;
-use pretty_assertions::assert_eq;
-use rand::prelude::*;
-use serde_json::json;
-use settings::SettingsStore;
-use std::{
-    env,
-    fmt::Write,
-    mem,
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
-
-#[gpui::test]
-async fn test_traversal(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-           ".gitignore": "a/b\n",
-           "a": {
-               "b": "",
-               "c": "",
-           }
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(false)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new(".gitignore"),
-                Path::new("a"),
-                Path::new("a/c"),
-            ]
-        );
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new(".gitignore"),
-                Path::new("a"),
-                Path::new("a/b"),
-                Path::new("a/c"),
-            ]
-        );
-    })
-}
-
-#[gpui::test]
-async fn test_descendent_entries(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "a": "",
-            "b": {
-               "c": {
-                   "d": ""
-               },
-               "e": {}
-            },
-            "f": "",
-            "g": {
-                "h": {}
-            },
-            "i": {
-                "j": {
-                    "k": ""
-                },
-                "l": {
-
-                }
-            },
-            ".gitignore": "i/j\n",
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.descendent_entries(false, false, Path::new("b"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("b/c/d"),]
-        );
-        assert_eq!(
-            tree.descendent_entries(true, false, Path::new("b"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new("b"),
-                Path::new("b/c"),
-                Path::new("b/c/d"),
-                Path::new("b/e"),
-            ]
-        );
-
-        assert_eq!(
-            tree.descendent_entries(false, false, Path::new("g"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            Vec::<PathBuf>::new()
-        );
-        assert_eq!(
-            tree.descendent_entries(true, false, Path::new("g"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("g"), Path::new("g/h"),]
-        );
-    });
-
-    // Expand gitignored directory.
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("i/j").into()])
-    })
-    .recv()
-    .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.descendent_entries(false, false, Path::new("i"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            Vec::<PathBuf>::new()
-        );
-        assert_eq!(
-            tree.descendent_entries(false, true, Path::new("i"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("i/j/k")]
-        );
-        assert_eq!(
-            tree.descendent_entries(true, false, Path::new("i"))
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![Path::new("i"), Path::new("i/l"),]
-        );
-    })
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_circular_symlinks(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "lib": {
-                "a": {
-                    "a.txt": ""
-                },
-                "b": {
-                    "b.txt": ""
-                }
-            }
-        }),
-    )
-    .await;
-    fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
-    fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(false)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new("lib"),
-                Path::new("lib/a"),
-                Path::new("lib/a/a.txt"),
-                Path::new("lib/a/lib"),
-                Path::new("lib/b"),
-                Path::new("lib/b/b.txt"),
-                Path::new("lib/b/lib"),
-            ]
-        );
-    });
-
-    fs.rename(
-        Path::new("/root/lib/a/lib"),
-        Path::new("/root/lib/a/lib-2"),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    cx.executor().run_until_parked();
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(false)
-                .map(|entry| entry.path.as_ref())
-                .collect::<Vec<_>>(),
-            vec![
-                Path::new(""),
-                Path::new("lib"),
-                Path::new("lib/a"),
-                Path::new("lib/a/a.txt"),
-                Path::new("lib/a/lib-2"),
-                Path::new("lib/b"),
-                Path::new("lib/b/b.txt"),
-                Path::new("lib/b/lib"),
-            ]
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "dir1": {
-                "deps": {
-                    // symlinks here
-                },
-                "src": {
-                    "a.rs": "",
-                    "b.rs": "",
-                },
-            },
-            "dir2": {
-                "src": {
-                    "c.rs": "",
-                    "d.rs": "",
-                }
-            },
-            "dir3": {
-                "deps": {},
-                "src": {
-                    "e.rs": "",
-                    "f.rs": "",
-                },
-            }
-        }),
-    )
-    .await;
-
-    // These symlinks point to directories outside of the worktree's root, dir1.
-    fs.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())
-        .await;
-    fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())
-        .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root/dir1"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    let tree_updates = Arc::new(Mutex::new(Vec::new()));
-    tree.update(cx, |_, cx| {
-        let tree_updates = tree_updates.clone();
-        cx.subscribe(&tree, move |_, _, event, _| {
-            if let Event::UpdatedEntries(update) = event {
-                tree_updates.lock().extend(
-                    update
-                        .iter()
-                        .map(|(path, _, change)| (path.clone(), *change)),
-                );
-            }
-        })
-        .detach();
-    });
-
-    // The symlinked directories are not scanned by default.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_external))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new("deps"), false),
-                (Path::new("deps/dep-dir2"), true),
-                (Path::new("deps/dep-dir3"), true),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-            ]
-        );
-
-        assert_eq!(
-            tree.entry_for_path("deps/dep-dir2").unwrap().kind,
-            EntryKind::UnloadedDir
-        );
-    });
-
-    // Expand one of the symlinked directories.
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])
-    })
-    .recv()
-    .await;
-
-    // The expanded directory's contents are loaded. Subdirectories are
-    // not scanned yet.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_external))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new("deps"), false),
-                (Path::new("deps/dep-dir2"), true),
-                (Path::new("deps/dep-dir3"), true),
-                (Path::new("deps/dep-dir3/deps"), true),
-                (Path::new("deps/dep-dir3/src"), true),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-            ]
-        );
-    });
-    assert_eq!(
-        mem::take(&mut *tree_updates.lock()),
-        &[
-            (Path::new("deps/dep-dir3").into(), PathChange::Loaded),
-            (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded),
-            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded)
-        ]
-    );
-
-    // Expand a subdirectory of one of the symlinked directories.
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])
-    })
-    .recv()
-    .await;
-
-    // The expanded subdirectory's contents are loaded.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_external))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new("deps"), false),
-                (Path::new("deps/dep-dir2"), true),
-                (Path::new("deps/dep-dir3"), true),
-                (Path::new("deps/dep-dir3/deps"), true),
-                (Path::new("deps/dep-dir3/src"), true),
-                (Path::new("deps/dep-dir3/src/e.rs"), true),
-                (Path::new("deps/dep-dir3/src/f.rs"), true),
-                (Path::new("src"), false),
-                (Path::new("src/a.rs"), false),
-                (Path::new("src/b.rs"), false),
-            ]
-        );
-    });
-
-    assert_eq!(
-        mem::take(&mut *tree_updates.lock()),
-        &[
-            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded),
-            (
-                Path::new("deps/dep-dir3/src/e.rs").into(),
-                PathChange::Loaded
-            ),
-            (
-                Path::new("deps/dep-dir3/src/f.rs").into(),
-                PathChange::Loaded
-            )
-        ]
-    );
-}
-
-#[gpui::test]
-async fn test_open_gitignored_files(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".gitignore": "node_modules\n",
-            "one": {
-                "node_modules": {
-                    "a": {
-                        "a1.js": "a1",
-                        "a2.js": "a2",
-                    },
-                    "b": {
-                        "b1.js": "b1",
-                        "b2.js": "b2",
-                    },
-                    "c": {
-                        "c1.js": "c1",
-                        "c2.js": "c2",
-                    }
-                },
-            },
-            "two": {
-                "x.js": "",
-                "y.js": "",
-            },
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("one"), false),
-                (Path::new("one/node_modules"), true),
-                (Path::new("two"), false),
-                (Path::new("two/x.js"), false),
-                (Path::new("two/y.js"), false),
-            ]
-        );
-    });
-
-    // Open a file that is nested inside of a gitignored directory that
-    // has not yet been expanded.
-    let prev_read_dir_count = fs.read_dir_call_count();
-    let buffer = tree
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
-        })
-        .await
-        .unwrap();
-
-    tree.read_with(cx, |tree, cx| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("one"), false),
-                (Path::new("one/node_modules"), true),
-                (Path::new("one/node_modules/a"), true),
-                (Path::new("one/node_modules/b"), true),
-                (Path::new("one/node_modules/b/b1.js"), true),
-                (Path::new("one/node_modules/b/b2.js"), true),
-                (Path::new("one/node_modules/c"), true),
-                (Path::new("two"), false),
-                (Path::new("two/x.js"), false),
-                (Path::new("two/y.js"), false),
-            ]
-        );
-
-        assert_eq!(
-            buffer.read(cx).file().unwrap().path().as_ref(),
-            Path::new("one/node_modules/b/b1.js")
-        );
-
-        // Only the newly-expanded directories are scanned.
-        assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
-    });
-
-    // Open another file in a different subdirectory of the same
-    // gitignored directory.
-    let prev_read_dir_count = fs.read_dir_call_count();
-    let buffer = tree
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
-        })
-        .await
-        .unwrap();
-
-    tree.read_with(cx, |tree, cx| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|entry| (entry.path.as_ref(), entry.is_ignored))
-                .collect::<Vec<_>>(),
-            vec![
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("one"), false),
-                (Path::new("one/node_modules"), true),
-                (Path::new("one/node_modules/a"), true),
-                (Path::new("one/node_modules/a/a1.js"), true),
-                (Path::new("one/node_modules/a/a2.js"), true),
-                (Path::new("one/node_modules/b"), true),
-                (Path::new("one/node_modules/b/b1.js"), true),
-                (Path::new("one/node_modules/b/b2.js"), true),
-                (Path::new("one/node_modules/c"), true),
-                (Path::new("two"), false),
-                (Path::new("two/x.js"), false),
-                (Path::new("two/y.js"), false),
-            ]
-        );
-
-        assert_eq!(
-            buffer.read(cx).file().unwrap().path().as_ref(),
-            Path::new("one/node_modules/a/a2.js")
-        );
-
-        // Only the newly-expanded directory is scanned.
-        assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
-    });
-
-    // No work happens when files and directories change within an unloaded directory.
-    let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count();
-    fs.create_dir("/root/one/node_modules/c/lib".as_ref())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-    assert_eq!(
-        fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
-        0
-    );
-}
-
-#[gpui::test]
-async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".gitignore": "node_modules\n",
-            "a": {
-                "a.js": "",
-            },
-            "b": {
-                "b.js": "",
-            },
-            "node_modules": {
-                "c": {
-                    "c.js": "",
-                },
-                "d": {
-                    "d.js": "",
-                    "e": {
-                        "e1.js": "",
-                        "e2.js": "",
-                    },
-                    "f": {
-                        "f1.js": "",
-                        "f2.js": "",
-                    }
-                },
-            },
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    // Open a file within the gitignored directory, forcing some of its
-    // subdirectories to be read, but not all.
-    let read_dir_count_1 = fs.read_dir_call_count();
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()])
-    })
-    .recv()
-    .await;
-
-    // Those subdirectories are now loaded.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|e| (e.path.as_ref(), e.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("a"), false),
-                (Path::new("a/a.js"), false),
-                (Path::new("b"), false),
-                (Path::new("b/b.js"), false),
-                (Path::new("node_modules"), true),
-                (Path::new("node_modules/c"), true),
-                (Path::new("node_modules/d"), true),
-                (Path::new("node_modules/d/d.js"), true),
-                (Path::new("node_modules/d/e"), true),
-                (Path::new("node_modules/d/f"), true),
-            ]
-        );
-    });
-    let read_dir_count_2 = fs.read_dir_call_count();
-    assert_eq!(read_dir_count_2 - read_dir_count_1, 2);
-
-    // Update the gitignore so that node_modules is no longer ignored,
-    // but a subdirectory is ignored
-    fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
-        .await
-        .unwrap();
-    cx.executor().run_until_parked();
-
-    // All of the directories that are no longer ignored are now loaded.
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(
-            tree.entries(true)
-                .map(|e| (e.path.as_ref(), e.is_ignored))
-                .collect::<Vec<_>>(),
-            &[
-                (Path::new(""), false),
-                (Path::new(".gitignore"), false),
-                (Path::new("a"), false),
-                (Path::new("a/a.js"), false),
-                (Path::new("b"), false),
-                (Path::new("b/b.js"), false),
-                // This directory is no longer ignored
-                (Path::new("node_modules"), false),
-                (Path::new("node_modules/c"), false),
-                (Path::new("node_modules/c/c.js"), false),
-                (Path::new("node_modules/d"), false),
-                (Path::new("node_modules/d/d.js"), false),
-                // This subdirectory is now ignored
-                (Path::new("node_modules/d/e"), true),
-                (Path::new("node_modules/d/f"), false),
-                (Path::new("node_modules/d/f/f1.js"), false),
-                (Path::new("node_modules/d/f/f2.js"), false),
-            ]
-        );
-    });
-
-    // Each of the newly-loaded directories is scanned only once.
-    let read_dir_count_3 = fs.read_dir_call_count();
-    assert_eq!(read_dir_count_3 - read_dir_count_2, 2);
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(Vec::new());
-            });
-        });
-    });
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
-            "tree": {
-                ".git": {},
-                ".gitignore": "ignored-dir\n",
-                "tracked-dir": {
-                    "tracked-file1": "",
-                    "ancestor-ignored-file1": "",
-                },
-                "ignored-dir": {
-                    "ignored-file1": ""
-                }
-            }
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        "/root/tree".as_ref(),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.read_with(cx, |tree, _| {
-        tree.as_local()
-            .unwrap()
-            .refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
-    })
-    .recv()
-    .await;
-
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        assert!(
-            !tree
-                .entry_for_path("tracked-dir/tracked-file1")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("ignored-dir/ignored-file1")
-                .unwrap()
-                .is_ignored
-        );
-    });
-
-    fs.create_file(
-        "/root/tree/tracked-dir/tracked-file2".as_ref(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    fs.create_file(
-        "/root/tree/tracked-dir/ancestor-ignored-file2".as_ref(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-    fs.create_file(
-        "/root/tree/ignored-dir/ignored-file2".as_ref(),
-        Default::default(),
-    )
-    .await
-    .unwrap();
-
-    cx.executor().run_until_parked();
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        assert!(
-            !tree
-                .entry_for_path("tracked-dir/tracked-file2")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(
-            tree.entry_for_path("ignored-dir/ignored-file2")
-                .unwrap()
-                .is_ignored
-        );
-        assert!(tree.entry_for_path(".git").unwrap().is_ignored);
-    });
-}
-
-#[gpui::test]
-async fn test_write_file(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let dir = temp_tree(json!({
-        ".git": {},
-        ".gitignore": "ignored-dir\n",
-        "tracked-dir": {},
-        "ignored-dir": {}
-    }));
-
-    let tree = Worktree::local(
-        build_client(cx),
-        dir.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-
-    tree.update(cx, |tree, cx| {
-        tree.as_local().unwrap().write_file(
-            Path::new("tracked-dir/file.txt"),
-            "hello".into(),
-            Default::default(),
-            cx,
-        )
-    })
-    .await
-    .unwrap();
-    tree.update(cx, |tree, cx| {
-        tree.as_local().unwrap().write_file(
-            Path::new("ignored-dir/file.txt"),
-            "world".into(),
-            Default::default(),
-            cx,
-        )
-    })
-    .await
-    .unwrap();
-
-    tree.read_with(cx, |tree, _| {
-        let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
-        let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
-        assert!(!tracked.is_ignored);
-        assert!(ignored.is_ignored);
-    });
-}
-
-#[gpui::test]
-async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let dir = temp_tree(json!({
-        ".gitignore": "**/target\n/node_modules\n",
-        "target": {
-            "index": "blah2"
-        },
-        "node_modules": {
-            ".DS_Store": "",
-            "prettier": {
-                "package.json": "{}",
-            },
-        },
-        "src": {
-            ".DS_Store": "",
-            "foo": {
-                "foo.rs": "mod another;\n",
-                "another.rs": "// another",
-            },
-            "bar": {
-                "bar.rs": "// bar",
-            },
-            "lib.rs": "mod foo;\nmod bar;\n",
-        },
-        ".DS_Store": "",
-    }));
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
-            });
-        });
-    });
-
-    let tree = Worktree::local(
-        build_client(cx),
-        dir.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "node_modules/.DS_Store",
-                "src/.DS_Store",
-                ".DS_Store",
-            ],
-            &["target", "node_modules"],
-            &["src/lib.rs", "src/bar/bar.rs", ".gitignore"],
-        )
-    });
-
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/node_modules/**".to_string()]);
-            });
-        });
-    });
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                "node_modules/prettier/package.json",
-                "node_modules/.DS_Store",
-                "node_modules",
-            ],
-            &["target"],
-            &[
-                ".gitignore",
-                "src/lib.rs",
-                "src/bar/bar.rs",
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "src/.DS_Store",
-                ".DS_Store",
-            ],
-        )
-    });
-}
-
-#[gpui::test]
-async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let dir = temp_tree(json!({
-        ".git": {
-            "HEAD": "ref: refs/heads/main\n",
-            "foo": "bar",
-        },
-        ".gitignore": "**/target\n/node_modules\ntest_output\n",
-        "target": {
-            "index": "blah2"
-        },
-        "node_modules": {
-            ".DS_Store": "",
-            "prettier": {
-                "package.json": "{}",
-            },
-        },
-        "src": {
-            ".DS_Store": "",
-            "foo": {
-                "foo.rs": "mod another;\n",
-                "another.rs": "// another",
-            },
-            "bar": {
-                "bar.rs": "// bar",
-            },
-            "lib.rs": "mod foo;\nmod bar;\n",
-        },
-        ".DS_Store": "",
-    }));
-    cx.update(|cx| {
-        cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![
-                    "**/.git".to_string(),
-                    "node_modules/".to_string(),
-                    "build_output".to_string(),
-                ]);
-            });
-        });
-    });
-
-    let tree = Worktree::local(
-        build_client(cx),
-        dir.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                ".git/HEAD",
-                ".git/foo",
-                "node_modules",
-                "node_modules/.DS_Store",
-                "node_modules/prettier",
-                "node_modules/prettier/package.json",
-            ],
-            &["target"],
-            &[
-                ".DS_Store",
-                "src/.DS_Store",
-                "src/lib.rs",
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "src/bar/bar.rs",
-                ".gitignore",
-            ],
-        )
-    });
-
-    let new_excluded_dir = dir.path().join("build_output");
-    let new_ignored_dir = dir.path().join("test_output");
-    std::fs::create_dir_all(&new_excluded_dir)
-        .unwrap_or_else(|e| panic!("Failed to create a {new_excluded_dir:?} directory: {e}"));
-    std::fs::create_dir_all(&new_ignored_dir)
-        .unwrap_or_else(|e| panic!("Failed to create a {new_ignored_dir:?} directory: {e}"));
-    let node_modules_dir = dir.path().join("node_modules");
-    let dot_git_dir = dir.path().join(".git");
-    let src_dir = dir.path().join("src");
-    for existing_dir in [&node_modules_dir, &dot_git_dir, &src_dir] {
-        assert!(
-            existing_dir.is_dir(),
-            "Expect {existing_dir:?} to be present in the FS already"
-        );
-    }
-
-    for directory_for_new_file in [
-        new_excluded_dir,
-        new_ignored_dir,
-        node_modules_dir,
-        dot_git_dir,
-        src_dir,
-    ] {
-        std::fs::write(directory_for_new_file.join("new_file"), "new file contents")
-            .unwrap_or_else(|e| {
-                panic!("Failed to create in {directory_for_new_file:?} a new file: {e}")
-            });
-    }
-    tree.flush_fs_events(cx).await;
-
-    tree.read_with(cx, |tree, _| {
-        check_worktree_entries(
-            tree,
-            &[
-                ".git/HEAD",
-                ".git/foo",
-                ".git/new_file",
-                "node_modules",
-                "node_modules/.DS_Store",
-                "node_modules/prettier",
-                "node_modules/prettier/package.json",
-                "node_modules/new_file",
-                "build_output",
-                "build_output/new_file",
-                "test_output/new_file",
-            ],
-            &["target", "test_output"],
-            &[
-                ".DS_Store",
-                "src/.DS_Store",
-                "src/lib.rs",
-                "src/foo/foo.rs",
-                "src/foo/another.rs",
-                "src/bar/bar.rs",
-                "src/new_file",
-                ".gitignore",
-            ],
-        )
-    });
-}
-
-#[gpui::test(iterations = 30)]
-async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            "b": {},
-            "c": {},
-            "d": {},
-        }),
-    )
-    .await;
-
-    let tree = Worktree::local(
-        build_client(cx),
-        "/root".as_ref(),
-        true,
-        fs,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let snapshot1 = tree.update(cx, |tree, cx| {
-        let tree = tree.as_local_mut().unwrap();
-        let snapshot = Arc::new(Mutex::new(tree.snapshot()));
-        let _ = tree.observe_updates(0, cx, {
-            let snapshot = snapshot.clone();
-            move |update| {
-                snapshot.lock().apply_remote_update(update).unwrap();
-                async { true }
-            }
-        });
-        snapshot
-    });
-
-    let entry = tree
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/e".as_ref(), true, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_dir());
-
-    cx.executor().run_until_parked();
-    tree.read_with(cx, |tree, _| {
-        assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
-    });
-
-    let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
-    assert_eq!(
-        snapshot1.lock().entries(true).collect::<Vec<_>>(),
-        snapshot2.entries(true).collect::<Vec<_>>()
-    );
-}
-
-#[gpui::test]
-async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-
-    let fs_fake = FakeFs::new(cx.background_executor.clone());
-    fs_fake
-        .insert_tree(
-            "/root",
-            json!({
-                "a": {},
-            }),
-        )
-        .await;
-
-    let tree_fake = Worktree::local(
-        client_fake,
-        "/root".as_ref(),
-        true,
-        fs_fake,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let entry = tree_fake
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/b/c/d.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_fake.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
-        assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
-        assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
-    });
-
-    let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
-
-    let fs_real = Arc::new(RealFs);
-    let temp_root = temp_tree(json!({
-        "a": {}
-    }));
-
-    let tree_real = Worktree::local(
-        client_real,
-        temp_root.path(),
-        true,
-        fs_real,
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let entry = tree_real
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/b/c/d.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_real.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
-        assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
-        assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
-    });
-
-    // Test smallest change
-    let entry = tree_real
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("a/b/c/e.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_real.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("a/b/c/e.txt").unwrap().is_file());
-    });
-
-    // Test largest change
-    let entry = tree_real
-        .update(cx, |tree, cx| {
-            tree.as_local_mut()
-                .unwrap()
-                .create_entry("d/e/f/g.txt".as_ref(), false, cx)
-        })
-        .await
-        .unwrap()
-        .unwrap();
-    assert!(entry.is_file());
-
-    cx.executor().run_until_parked();
-    tree_real.read_with(cx, |tree, _| {
-        assert!(tree.entry_for_path("d/e/f/g.txt").unwrap().is_file());
-        assert!(tree.entry_for_path("d/e/f").unwrap().is_dir());
-        assert!(tree.entry_for_path("d/e/").unwrap().is_dir());
-        assert!(tree.entry_for_path("d/").unwrap().is_dir());
-    });
-}
-
-#[gpui::test(iterations = 100)]
-async fn test_random_worktree_operations_during_initial_scan(
-    cx: &mut TestAppContext,
-    mut rng: StdRng,
-) {
-    init_test(cx);
-    let operations = env::var("OPERATIONS")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(5);
-    let initial_entries = env::var("INITIAL_ENTRIES")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(20);
-
-    let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
-    fs.as_fake().insert_tree(root_dir, json!({})).await;
-    for _ in 0..initial_entries {
-        randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
-    }
-    log::info!("generated initial tree");
-
-    let worktree = Worktree::local(
-        build_client(cx),
-        root_dir,
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let mut snapshots = vec![worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot())];
-    let updates = Arc::new(Mutex::new(Vec::new()));
-    worktree.update(cx, |tree, cx| {
-        check_worktree_change_events(tree, cx);
-
-        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-            let updates = updates.clone();
-            move |update| {
-                updates.lock().push(update);
-                async { true }
-            }
-        });
-    });
-
-    for _ in 0..operations {
-        worktree
-            .update(cx, |worktree, cx| {
-                randomly_mutate_worktree(worktree, &mut rng, cx)
-            })
-            .await
-            .log_err();
-        worktree.read_with(cx, |tree, _| {
-            tree.as_local().unwrap().snapshot().check_invariants(true)
-        });
-
-        if rng.gen_bool(0.6) {
-            snapshots.push(worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()));
-        }
-    }
-
-    worktree
-        .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
-        .await;
-
-    cx.executor().run_until_parked();
-
-    let final_snapshot = worktree.read_with(cx, |tree, _| {
-        let tree = tree.as_local().unwrap();
-        let snapshot = tree.snapshot();
-        snapshot.check_invariants(true);
-        snapshot
-    });
-
-    for (i, snapshot) in snapshots.into_iter().enumerate().rev() {
-        let mut updated_snapshot = snapshot.clone();
-        for update in updates.lock().iter() {
-            if update.scan_id >= updated_snapshot.scan_id() as u64 {
-                updated_snapshot
-                    .apply_remote_update(update.clone())
-                    .unwrap();
-            }
-        }
-
-        assert_eq!(
-            updated_snapshot.entries(true).collect::<Vec<_>>(),
-            final_snapshot.entries(true).collect::<Vec<_>>(),
-            "wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}",
-        );
-    }
-}
-
-#[gpui::test(iterations = 100)]
-async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) {
-    init_test(cx);
-    let operations = env::var("OPERATIONS")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(40);
-    let initial_entries = env::var("INITIAL_ENTRIES")
-        .map(|o| o.parse().unwrap())
-        .unwrap_or(20);
-
-    let root_dir = Path::new("/test");
-    let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
-    fs.as_fake().insert_tree(root_dir, json!({})).await;
-    for _ in 0..initial_entries {
-        randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
-    }
-    log::info!("generated initial tree");
-
-    let worktree = Worktree::local(
-        build_client(cx),
-        root_dir,
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let updates = Arc::new(Mutex::new(Vec::new()));
-    worktree.update(cx, |tree, cx| {
-        check_worktree_change_events(tree, cx);
-
-        let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
-            let updates = updates.clone();
-            move |update| {
-                updates.lock().push(update);
-                async { true }
-            }
-        });
-    });
-
-    worktree
-        .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
-        .await;
-
-    fs.as_fake().pause_events();
-    let mut snapshots = Vec::new();
-    let mut mutations_len = operations;
-    while mutations_len > 1 {
-        if rng.gen_bool(0.2) {
-            worktree
-                .update(cx, |worktree, cx| {
-                    randomly_mutate_worktree(worktree, &mut rng, cx)
-                })
-                .await
-                .log_err();
-        } else {
-            randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
-        }
-
-        let buffered_event_count = fs.as_fake().buffered_event_count();
-        if buffered_event_count > 0 && rng.gen_bool(0.3) {
-            let len = rng.gen_range(0..=buffered_event_count);
-            log::info!("flushing {} events", len);
-            fs.as_fake().flush_events(len);
-        } else {
-            randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
-            mutations_len -= 1;
-        }
-
-        cx.executor().run_until_parked();
-        if rng.gen_bool(0.2) {
-            log::info!("storing snapshot {}", snapshots.len());
-            let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-            snapshots.push(snapshot);
-        }
-    }
-
-    log::info!("quiescing");
-    fs.as_fake().flush_events(usize::MAX);
-    cx.executor().run_until_parked();
-
-    let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-    snapshot.check_invariants(true);
-    let expanded_paths = snapshot
-        .expanded_entries()
-        .map(|e| e.path.clone())
-        .collect::<Vec<_>>();
-
-    {
-        let new_worktree = Worktree::local(
-            build_client(cx),
-            root_dir,
-            true,
-            fs.clone(),
-            Default::default(),
-            &mut cx.to_async(),
-        )
-        .await
-        .unwrap();
-        new_worktree
-            .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
-            .await;
-        new_worktree
-            .update(cx, |tree, _| {
-                tree.as_local_mut()
-                    .unwrap()
-                    .refresh_entries_for_paths(expanded_paths)
-            })
-            .recv()
-            .await;
-        let new_snapshot =
-            new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
-        assert_eq!(
-            snapshot.entries_without_ids(true),
-            new_snapshot.entries_without_ids(true)
-        );
-    }
-
-    for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() {
-        for update in updates.lock().iter() {
-            if update.scan_id >= prev_snapshot.scan_id() as u64 {
-                prev_snapshot.apply_remote_update(update.clone()).unwrap();
-            }
-        }
-
-        assert_eq!(
-            prev_snapshot
-                .entries(true)
-                .map(ignore_pending_dir)
-                .collect::<Vec<_>>(),
-            snapshot
-                .entries(true)
-                .map(ignore_pending_dir)
-                .collect::<Vec<_>>(),
-            "wrong updates after snapshot {i}: {updates:#?}",
-        );
-    }
-
-    fn ignore_pending_dir(entry: &Entry) -> Entry {
-        let mut entry = entry.clone();
-        if entry.kind.is_dir() {
-            entry.kind = EntryKind::Dir
-        }
-        entry
-    }
-}
-
-// The worktree's `UpdatedEntries` event can be used to follow along with
-// all changes to the worktree's snapshot.
-fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Worktree>) {
-    let mut entries = tree.entries(true).cloned().collect::<Vec<_>>();
-    cx.subscribe(&cx.handle(), move |tree, _, event, _| {
-        if let Event::UpdatedEntries(changes) = event {
-            for (path, _, change_type) in changes.iter() {
-                let entry = tree.entry_for_path(&path).cloned();
-                let ix = match entries.binary_search_by_key(&path, |e| &e.path) {
-                    Ok(ix) | Err(ix) => ix,
-                };
-                match change_type {
-                    PathChange::Added => entries.insert(ix, entry.unwrap()),
-                    PathChange::Removed => drop(entries.remove(ix)),
-                    PathChange::Updated => {
-                        let entry = entry.unwrap();
-                        let existing_entry = entries.get_mut(ix).unwrap();
-                        assert_eq!(existing_entry.path, entry.path);
-                        *existing_entry = entry;
-                    }
-                    PathChange::AddedOrUpdated | PathChange::Loaded => {
-                        let entry = entry.unwrap();
-                        if entries.get(ix).map(|e| &e.path) == Some(&entry.path) {
-                            *entries.get_mut(ix).unwrap() = entry;
-                        } else {
-                            entries.insert(ix, entry);
-                        }
-                    }
-                }
-            }
-
-            let new_entries = tree.entries(true).cloned().collect::<Vec<_>>();
-            assert_eq!(entries, new_entries, "incorrect changes: {:?}", changes);
-        }
-    })
-    .detach();
-}
-
-fn randomly_mutate_worktree(
-    worktree: &mut Worktree,
-    rng: &mut impl Rng,
-    cx: &mut ModelContext<Worktree>,
-) -> Task<Result<()>> {
-    log::info!("mutating worktree");
-    let worktree = worktree.as_local_mut().unwrap();
-    let snapshot = worktree.snapshot();
-    let entry = snapshot.entries(false).choose(rng).unwrap();
-
-    match rng.gen_range(0_u32..100) {
-        0..=33 if entry.path.as_ref() != Path::new("") => {
-            log::info!("deleting entry {:?} ({})", entry.path, entry.id.0);
-            worktree.delete_entry(entry.id, cx).unwrap()
-        }
-        ..=66 if entry.path.as_ref() != Path::new("") => {
-            let other_entry = snapshot.entries(false).choose(rng).unwrap();
-            let new_parent_path = if other_entry.is_dir() {
-                other_entry.path.clone()
-            } else {
-                other_entry.path.parent().unwrap().into()
-            };
-            let mut new_path = new_parent_path.join(random_filename(rng));
-            if new_path.starts_with(&entry.path) {
-                new_path = random_filename(rng).into();
-            }
-
-            log::info!(
-                "renaming entry {:?} ({}) to {:?}",
-                entry.path,
-                entry.id.0,
-                new_path
-            );
-            let task = worktree.rename_entry(entry.id, new_path, cx);
-            cx.background_executor().spawn(async move {
-                task.await?.unwrap();
-                Ok(())
-            })
-        }
-        _ => {
-            if entry.is_dir() {
-                let child_path = entry.path.join(random_filename(rng));
-                let is_dir = rng.gen_bool(0.3);
-                log::info!(
-                    "creating {} at {:?}",
-                    if is_dir { "dir" } else { "file" },
-                    child_path,
-                );
-                let task = worktree.create_entry(child_path, is_dir, cx);
-                cx.background_executor().spawn(async move {
-                    task.await?;
-                    Ok(())
-                })
-            } else {
-                log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
-                let task =
-                    worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
-                cx.background_executor().spawn(async move {
-                    task.await?;
-                    Ok(())
-                })
-            }
-        }
-    }
-}
-
-async fn randomly_mutate_fs(
-    fs: &Arc<dyn Fs>,
-    root_path: &Path,
-    insertion_probability: f64,
-    rng: &mut impl Rng,
-) {
-    log::info!("mutating fs");
-    let mut files = Vec::new();
-    let mut dirs = Vec::new();
-    for path in fs.as_fake().paths(false) {
-        if path.starts_with(root_path) {
-            if fs.is_file(&path).await {
-                files.push(path);
-            } else {
-                dirs.push(path);
-            }
-        }
-    }
-
-    if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
-        let path = dirs.choose(rng).unwrap();
-        let new_path = path.join(random_filename(rng));
-
-        if rng.gen() {
-            log::info!(
-                "creating dir {:?}",
-                new_path.strip_prefix(root_path).unwrap()
-            );
-            fs.create_dir(&new_path).await.unwrap();
-        } else {
-            log::info!(
-                "creating file {:?}",
-                new_path.strip_prefix(root_path).unwrap()
-            );
-            fs.create_file(&new_path, Default::default()).await.unwrap();
-        }
-    } else if rng.gen_bool(0.05) {
-        let ignore_dir_path = dirs.choose(rng).unwrap();
-        let ignore_path = ignore_dir_path.join(&*GITIGNORE);
-
-        let subdirs = dirs
-            .iter()
-            .filter(|d| d.starts_with(&ignore_dir_path))
-            .cloned()
-            .collect::<Vec<_>>();
-        let subfiles = files
-            .iter()
-            .filter(|d| d.starts_with(&ignore_dir_path))
-            .cloned()
-            .collect::<Vec<_>>();
-        let files_to_ignore = {
-            let len = rng.gen_range(0..=subfiles.len());
-            subfiles.choose_multiple(rng, len)
-        };
-        let dirs_to_ignore = {
-            let len = rng.gen_range(0..subdirs.len());
-            subdirs.choose_multiple(rng, len)
-        };
-
-        let mut ignore_contents = String::new();
-        for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
-            writeln!(
-                ignore_contents,
-                "{}",
-                path_to_ignore
-                    .strip_prefix(&ignore_dir_path)
-                    .unwrap()
-                    .to_str()
-                    .unwrap()
-            )
-            .unwrap();
-        }
-        log::info!(
-            "creating gitignore {:?} with contents:\n{}",
-            ignore_path.strip_prefix(&root_path).unwrap(),
-            ignore_contents
-        );
-        fs.save(
-            &ignore_path,
-            &ignore_contents.as_str().into(),
-            Default::default(),
-        )
-        .await
-        .unwrap();
-    } else {
-        let old_path = {
-            let file_path = files.choose(rng);
-            let dir_path = dirs[1..].choose(rng);
-            file_path.into_iter().chain(dir_path).choose(rng).unwrap()
-        };
-
-        let is_rename = rng.gen();
-        if is_rename {
-            let new_path_parent = dirs
-                .iter()
-                .filter(|d| !d.starts_with(old_path))
-                .choose(rng)
-                .unwrap();
-
-            let overwrite_existing_dir =
-                !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
-            let new_path = if overwrite_existing_dir {
-                fs.remove_dir(
-                    &new_path_parent,
-                    RemoveOptions {
-                        recursive: true,
-                        ignore_if_not_exists: true,
-                    },
-                )
-                .await
-                .unwrap();
-                new_path_parent.to_path_buf()
-            } else {
-                new_path_parent.join(random_filename(rng))
-            };
-
-            log::info!(
-                "renaming {:?} to {}{:?}",
-                old_path.strip_prefix(&root_path).unwrap(),
-                if overwrite_existing_dir {
-                    "overwrite "
-                } else {
-                    ""
-                },
-                new_path.strip_prefix(&root_path).unwrap()
-            );
-            fs.rename(
-                &old_path,
-                &new_path,
-                fs::RenameOptions {
-                    overwrite: true,
-                    ignore_if_exists: true,
-                },
-            )
-            .await
-            .unwrap();
-        } else if fs.is_file(&old_path).await {
-            log::info!(
-                "deleting file {:?}",
-                old_path.strip_prefix(&root_path).unwrap()
-            );
-            fs.remove_file(old_path, Default::default()).await.unwrap();
-        } else {
-            log::info!(
-                "deleting dir {:?}",
-                old_path.strip_prefix(&root_path).unwrap()
-            );
-            fs.remove_dir(
-                &old_path,
-                RemoveOptions {
-                    recursive: true,
-                    ignore_if_not_exists: true,
-                },
-            )
-            .await
-            .unwrap();
-        }
-    }
-}
-
-fn random_filename(rng: &mut impl Rng) -> String {
-    (0..6)
-        .map(|_| rng.sample(rand::distributions::Alphanumeric))
-        .map(char::from)
-        .collect()
-}
-
-#[gpui::test]
-async fn test_rename_work_directory(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let root = temp_tree(json!({
-        "projects": {
-            "project1": {
-                "a": "",
-                "b": "",
-            }
-        },
-
-    }));
-    let root_path = root.path();
-
-    let tree = Worktree::local(
-        build_client(cx),
-        root_path,
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    let repo = git_init(&root_path.join("projects/project1"));
-    git_add("a", &repo);
-    git_commit("init", &repo);
-    std::fs::write(root_path.join("projects/project1/a"), "aa").ok();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    tree.flush_fs_events(cx).await;
-
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        let (work_dir, _) = tree.repositories().next().unwrap();
-        assert_eq!(work_dir.as_ref(), Path::new("projects/project1"));
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project1/a")),
-            Some(GitFileStatus::Modified)
-        );
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project1/b")),
-            Some(GitFileStatus::Added)
-        );
-    });
-
-    std::fs::rename(
-        root_path.join("projects/project1"),
-        root_path.join("projects/project2"),
-    )
-    .ok();
-    tree.flush_fs_events(cx).await;
-
-    cx.read(|cx| {
-        let tree = tree.read(cx);
-        let (work_dir, _) = tree.repositories().next().unwrap();
-        assert_eq!(work_dir.as_ref(), Path::new("projects/project2"));
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project2/a")),
-            Some(GitFileStatus::Modified)
-        );
-        assert_eq!(
-            tree.status_for_file(Path::new("projects/project2/b")),
-            Some(GitFileStatus::Added)
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_git_repository_for_path(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    let root = temp_tree(json!({
-        "c.txt": "",
-        "dir1": {
-            ".git": {},
-            "deps": {
-                "dep1": {
-                    ".git": {},
-                    "src": {
-                        "a.txt": ""
-                    }
-                }
-            },
-            "src": {
-                "b.txt": ""
-            }
-        },
-    }));
-
-    let tree = Worktree::local(
-        build_client(cx),
-        root.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    tree.flush_fs_events(cx).await;
-
-    tree.read_with(cx, |tree, _cx| {
-        let tree = tree.as_local().unwrap();
-
-        assert!(tree.repository_for_path("c.txt".as_ref()).is_none());
-
-        let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap();
-        assert_eq!(
-            entry
-                .work_directory(tree)
-                .map(|directory| directory.as_ref().to_owned()),
-            Some(Path::new("dir1").to_owned())
-        );
-
-        let entry = tree
-            .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref())
-            .unwrap();
-        assert_eq!(
-            entry
-                .work_directory(tree)
-                .map(|directory| directory.as_ref().to_owned()),
-            Some(Path::new("dir1/deps/dep1").to_owned())
-        );
-
-        let entries = tree.files(false, 0);
-
-        let paths_with_repos = tree
-            .entries_with_repositories(entries)
-            .map(|(entry, repo)| {
-                (
-                    entry.path.as_ref(),
-                    repo.and_then(|repo| {
-                        repo.work_directory(&tree)
-                            .map(|work_directory| work_directory.0.to_path_buf())
-                    }),
-                )
-            })
-            .collect::<Vec<_>>();
-
-        assert_eq!(
-            paths_with_repos,
-            &[
-                (Path::new("c.txt"), None),
-                (
-                    Path::new("dir1/deps/dep1/src/a.txt"),
-                    Some(Path::new("dir1/deps/dep1").into())
-                ),
-                (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
-            ]
-        );
-    });
-
-    let repo_update_events = Arc::new(Mutex::new(vec![]));
-    tree.update(cx, |_, cx| {
-        let repo_update_events = repo_update_events.clone();
-        cx.subscribe(&tree, move |_, _, event, _| {
-            if let Event::UpdatedGitRepositories(update) = event {
-                repo_update_events.lock().push(update.clone());
-            }
-        })
-        .detach();
-    });
-
-    std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
-    tree.flush_fs_events(cx).await;
-
-    assert_eq!(
-        repo_update_events.lock()[0]
-            .iter()
-            .map(|e| e.0.clone())
-            .collect::<Vec<Arc<Path>>>(),
-        vec![Path::new("dir1").into()]
-    );
-
-    std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
-    tree.flush_fs_events(cx).await;
-
-    tree.read_with(cx, |tree, _cx| {
-        let tree = tree.as_local().unwrap();
-
-        assert!(tree
-            .repository_for_path("dir1/src/b.txt".as_ref())
-            .is_none());
-    });
-}
-
-#[gpui::test]
-async fn test_git_status(cx: &mut TestAppContext) {
-    init_test(cx);
-    cx.executor().allow_parking();
-    const IGNORE_RULE: &'static str = "**/target";
-
-    let root = temp_tree(json!({
-        "project": {
-            "a.txt": "a",
-            "b.txt": "bb",
-            "c": {
-                "d": {
-                    "e.txt": "eee"
-                }
-            },
-            "f.txt": "ffff",
-            "target": {
-                "build_file": "???"
-            },
-            ".gitignore": IGNORE_RULE
-        },
-
-    }));
-
-    const A_TXT: &'static str = "a.txt";
-    const B_TXT: &'static str = "b.txt";
-    const E_TXT: &'static str = "c/d/e.txt";
-    const F_TXT: &'static str = "f.txt";
-    const DOTGITIGNORE: &'static str = ".gitignore";
-    const BUILD_FILE: &'static str = "target/build_file";
-    let project_path = Path::new("project");
-
-    // Set up git repository before creating the worktree.
-    let work_dir = root.path().join("project");
-    let mut repo = git_init(work_dir.as_path());
-    repo.add_ignore_rule(IGNORE_RULE).unwrap();
-    git_add(A_TXT, &repo);
-    git_add(E_TXT, &repo);
-    git_add(DOTGITIGNORE, &repo);
-    git_commit("Initial commit", &repo);
-
-    let tree = Worktree::local(
-        build_client(cx),
-        root.path(),
-        true,
-        Arc::new(RealFs),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    tree.flush_fs_events(cx).await;
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-    cx.executor().run_until_parked();
-
-    // Check that the right git state is observed on startup
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(snapshot.repositories().count(), 1);
-        let (dir, _) = snapshot.repositories().next().unwrap();
-        assert_eq!(dir.as_ref(), Path::new("project"));
-
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(B_TXT)),
-            Some(GitFileStatus::Added)
-        );
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(F_TXT)),
-            Some(GitFileStatus::Added)
-        );
-    });
-
-    // Modify a file in the working copy.
-    std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    // The worktree detects that the file's git status has changed.
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(A_TXT)),
-            Some(GitFileStatus::Modified)
-        );
-    });
-
-    // Create a commit in the git repository.
-    git_add(A_TXT, &repo);
-    git_add(B_TXT, &repo);
-    git_commit("Committing modified and added", &repo);
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    // The worktree detects that the files' git status have changed.
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(F_TXT)),
-            Some(GitFileStatus::Added)
-        );
-        assert_eq!(snapshot.status_for_file(project_path.join(B_TXT)), None);
-        assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
-    });
-
-    // Modify files in the working copy and perform git operations on other files.
-    git_reset(0, &repo);
-    git_remove_index(Path::new(B_TXT), &repo);
-    git_stash(&mut repo);
-    std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
-    std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    // Check that more complex repo changes are tracked
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-
-        assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(B_TXT)),
-            Some(GitFileStatus::Added)
-        );
-        assert_eq!(
-            snapshot.status_for_file(project_path.join(E_TXT)),
-            Some(GitFileStatus::Modified)
-        );
-    });
-
-    std::fs::remove_file(work_dir.join(B_TXT)).unwrap();
-    std::fs::remove_dir_all(work_dir.join("c")).unwrap();
-    std::fs::write(
-        work_dir.join(DOTGITIGNORE),
-        [IGNORE_RULE, "f.txt"].join("\n"),
-    )
-    .unwrap();
-
-    git_add(Path::new(DOTGITIGNORE), &repo);
-    git_commit("Committing modified git ignore", &repo);
-
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    let mut renamed_dir_name = "first_directory/second_directory";
-    const RENAMED_FILE: &'static str = "rf.txt";
-
-    std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap();
-    std::fs::write(
-        work_dir.join(renamed_dir_name).join(RENAMED_FILE),
-        "new-contents",
-    )
-    .unwrap();
-
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-        assert_eq!(
-            snapshot.status_for_file(&project_path.join(renamed_dir_name).join(RENAMED_FILE)),
-            Some(GitFileStatus::Added)
-        );
-    });
-
-    renamed_dir_name = "new_first_directory/second_directory";
-
-    std::fs::rename(
-        work_dir.join("first_directory"),
-        work_dir.join("new_first_directory"),
-    )
-    .unwrap();
-
-    tree.flush_fs_events(cx).await;
-    cx.executor().run_until_parked();
-
-    tree.read_with(cx, |tree, _cx| {
-        let snapshot = tree.snapshot();
-
-        assert_eq!(
-            snapshot.status_for_file(
-                project_path
-                    .join(Path::new(renamed_dir_name))
-                    .join(RENAMED_FILE)
-            ),
-            Some(GitFileStatus::Added)
-        );
-    });
-}
-
-#[gpui::test]
-async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
-    init_test(cx);
-    let fs = FakeFs::new(cx.background_executor.clone());
-    fs.insert_tree(
-        "/root",
-        json!({
-            ".git": {},
-            "a": {
-                "b": {
-                    "c1.txt": "",
-                    "c2.txt": "",
-                },
-                "d": {
-                    "e1.txt": "",
-                    "e2.txt": "",
-                    "e3.txt": "",
-                }
-            },
-            "f": {
-                "no-status.txt": ""
-            },
-            "g": {
-                "h1.txt": "",
-                "h2.txt": ""
-            },
-
-        }),
-    )
-    .await;
-
-    fs.set_status_for_repo_via_git_operation(
-        &Path::new("/root/.git"),
-        &[
-            (Path::new("a/b/c1.txt"), GitFileStatus::Added),
-            (Path::new("a/d/e2.txt"), GitFileStatus::Modified),
-            (Path::new("g/h2.txt"), GitFileStatus::Conflict),
-        ],
-    );
-
-    let tree = Worktree::local(
-        build_client(cx),
-        Path::new("/root"),
-        true,
-        fs.clone(),
-        Default::default(),
-        &mut cx.to_async(),
-    )
-    .await
-    .unwrap();
-
-    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
-        .await;
-
-    cx.executor().run_until_parked();
-    let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
-
-    check_propagated_statuses(
-        &snapshot,
-        &[
-            (Path::new(""), Some(GitFileStatus::Conflict)),
-            (Path::new("a"), Some(GitFileStatus::Modified)),
-            (Path::new("a/b"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c2.txt"), None),
-            (Path::new("a/d"), Some(GitFileStatus::Modified)),
-            (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
-            (Path::new("f"), None),
-            (Path::new("f/no-status.txt"), None),
-            (Path::new("g"), Some(GitFileStatus::Conflict)),
-            (Path::new("g/h2.txt"), Some(GitFileStatus::Conflict)),
-        ],
-    );
-
-    check_propagated_statuses(
-        &snapshot,
-        &[
-            (Path::new("a/b"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c2.txt"), None),
-            (Path::new("a/d"), Some(GitFileStatus::Modified)),
-            (Path::new("a/d/e1.txt"), None),
-            (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
-            (Path::new("f"), None),
-            (Path::new("f/no-status.txt"), None),
-            (Path::new("g"), Some(GitFileStatus::Conflict)),
-        ],
-    );
-
-    check_propagated_statuses(
-        &snapshot,
-        &[
-            (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
-            (Path::new("a/b/c2.txt"), None),
-            (Path::new("a/d/e1.txt"), None),
-            (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
-            (Path::new("f/no-status.txt"), None),
-        ],
-    );
-
-    #[track_caller]
-    fn check_propagated_statuses(
-        snapshot: &Snapshot,
-        expected_statuses: &[(&Path, Option<GitFileStatus>)],
-    ) {
-        let mut entries = expected_statuses
-            .iter()
-            .map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone())
-            .collect::<Vec<_>>();
-        snapshot.propagate_git_statuses(&mut entries);
-        assert_eq!(
-            entries
-                .iter()
-                .map(|e| (e.path.as_ref(), e.git_status))
-                .collect::<Vec<_>>(),
-            expected_statuses
-        );
-    }
-}
-
-fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
-    let http_client = FakeHttpClient::with_404_response();
-    cx.update(|cx| Client::new(http_client, cx))
-}
-
-#[track_caller]
-fn git_init(path: &Path) -> git2::Repository {
-    git2::Repository::init(path).expect("Failed to initialize git repository")
-}
-
-#[track_caller]
-fn git_add<P: AsRef<Path>>(path: P, repo: &git2::Repository) {
-    let path = path.as_ref();
-    let mut index = repo.index().expect("Failed to get index");
-    index.add_path(path).expect("Failed to add a.txt");
-    index.write().expect("Failed to write index");
-}
-
-#[track_caller]
-fn git_remove_index(path: &Path, repo: &git2::Repository) {
-    let mut index = repo.index().expect("Failed to get index");
-    index.remove_path(path).expect("Failed to add a.txt");
-    index.write().expect("Failed to write index");
-}
-
-#[track_caller]
-fn git_commit(msg: &'static str, repo: &git2::Repository) {
-    use git2::Signature;
-
-    let signature = Signature::now("test", "test@zed.dev").unwrap();
-    let oid = repo.index().unwrap().write_tree().unwrap();
-    let tree = repo.find_tree(oid).unwrap();
-    if let Some(head) = repo.head().ok() {
-        let parent_obj = head.peel(git2::ObjectType::Commit).unwrap();
-
-        let parent_commit = parent_obj.as_commit().unwrap();
-
-        repo.commit(
-            Some("HEAD"),
-            &signature,
-            &signature,
-            msg,
-            &tree,
-            &[parent_commit],
-        )
-        .expect("Failed to commit with parent");
-    } else {
-        repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[])
-            .expect("Failed to commit");
-    }
-}
-
-#[track_caller]
-fn git_stash(repo: &mut git2::Repository) {
-    use git2::Signature;
-
-    let signature = Signature::now("test", "test@zed.dev").unwrap();
-    repo.stash_save(&signature, "N/A", None)
-        .expect("Failed to stash");
-}
-
-#[track_caller]
-fn git_reset(offset: usize, repo: &git2::Repository) {
-    let head = repo.head().expect("Couldn't get repo head");
-    let object = head.peel(git2::ObjectType::Commit).unwrap();
-    let commit = object.as_commit().unwrap();
-    let new_head = commit
-        .parents()
-        .inspect(|parnet| {
-            parnet.message();
-        })
-        .skip(offset)
-        .next()
-        .expect("Not enough history");
-    repo.reset(&new_head.as_object(), git2::ResetType::Soft, None)
-        .expect("Could not reset");
-}
-
-#[allow(dead_code)]
-#[track_caller]
-fn git_status(repo: &git2::Repository) -> collections::HashMap<String, git2::Status> {
-    repo.statuses(None)
-        .unwrap()
-        .iter()
-        .map(|status| (status.path().unwrap().to_string(), status.status()))
-        .collect()
-}
-
-#[track_caller]
-fn check_worktree_entries(
-    tree: &Worktree,
-    expected_excluded_paths: &[&str],
-    expected_ignored_paths: &[&str],
-    expected_tracked_paths: &[&str],
-) {
-    for path in expected_excluded_paths {
-        let entry = tree.entry_for_path(path);
-        assert!(
-            entry.is_none(),
-            "expected path '{path}' to be excluded, but got entry: {entry:?}",
-        );
-    }
-    for path in expected_ignored_paths {
-        let entry = tree
-            .entry_for_path(path)
-            .unwrap_or_else(|| panic!("Missing entry for expected ignored path '{path}'"));
-        assert!(
-            entry.is_ignored,
-            "expected path '{path}' to be ignored, but got entry: {entry:?}",
-        );
-    }
-    for path in expected_tracked_paths {
-        let entry = tree
-            .entry_for_path(path)
-            .unwrap_or_else(|| panic!("Missing entry for expected tracked path '{path}'"));
-        assert!(
-            !entry.is_ignored,
-            "expected path '{path}' to be tracked, but got entry: {entry:?}",
-        );
-    }
-}
-
-fn init_test(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| {
-        let settings_store = SettingsStore::test(cx);
-        cx.set_global(settings_store);
-        Project::init_settings(cx);
-    });
-}

crates/project_panel/Cargo.toml 🔗

@@ -14,7 +14,7 @@ db = { path = "../db2", package = "db2" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui2", package = "gpui2" }
 menu = { path = "../menu2", package = "menu2" }
-project = { path = "../project2", package = "project2" }
+project = { path = "../project" }
 search = { path = "../search" }
 settings = { path = "../settings2", package = "settings2" }
 theme = { path = "../theme2", package = "theme2" }

crates/project_symbols/Cargo.toml 🔗

@@ -13,7 +13,7 @@ editor = { path = "../editor" }
 fuzzy = { package = "fuzzy2",  path = "../fuzzy2" }
 gpui = { package = "gpui2",  path = "../gpui2" }
 picker = { path = "../picker" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 text = { package = "text2",  path = "../text2" }
 settings = { package = "settings2",  path = "../settings2" }
 workspace = { path = "../workspace" }
@@ -32,6 +32,6 @@ settings = { package = "settings2", path = "../settings2", features = ["test-sup
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
 lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }

crates/search/Cargo.toml 🔗

@@ -15,7 +15,7 @@ editor = { path = "../editor" }
 gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 menu = { package = "menu2", path = "../menu2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }

crates/semantic_index2/Cargo.toml 🔗

@@ -13,7 +13,7 @@ ai = { path = "../ai" }
 collections = { path = "../collections" }
 gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 workspace = { path = "../workspace" }
 util = { path = "../util" }
 rpc = { package = "rpc2", path = "../rpc2" }
@@ -43,7 +43,7 @@ ai = { path = "../ai", features = ["test-support"] }
 collections = { path = "../collections", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"]}

crates/terminal_view/Cargo.toml 🔗

@@ -12,7 +12,7 @@ doctest = false
 editor = { path = "../editor" }
 language = { package = "language2", path = "../language2" }
 gpui = { package = "gpui2", path = "../gpui2" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 # search = { path = "../search" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
@@ -41,6 +41,6 @@ serde_derive.workspace = true
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 client = { package = "client2", path = "../client2", features = ["test-support"]}
-project = { package = "project2", path = "../project2", features = ["test-support"]}
+project = { path = "../project", features = ["test-support"]}
 workspace = { path = "../workspace", features = ["test-support"] }
 rand.workspace = true

crates/vim/Cargo.toml 🔗

@@ -45,7 +45,7 @@ futures.workspace = true
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2" }
 workspace = { path = "../workspace", features = ["test-support"] }

crates/welcome/Cargo.toml 🔗

@@ -19,7 +19,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
 ui = { package = "ui2", path = "../ui2" }
 db = { package = "db2", path = "../db2" }
 install_cli = { path = "../install_cli" }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 theme_selector = { path = "../theme_selector" }

crates/workspace/Cargo.toml 🔗

@@ -30,7 +30,7 @@ install_cli = { path = "../install_cli" }
 language = { path = "../language2", package = "language2" }
 #menu = { path = "../menu" }
 node_runtime = { path = "../node_runtime" }
-project = { path = "../project2", package = "project2" }
+project = { path = "../project" }
 settings = { path = "../settings2", package = "settings2" }
 terminal = { path = "../terminal2", package = "terminal2" }
 theme = { path = "../theme2", package = "theme2" }
@@ -57,7 +57,7 @@ uuid.workspace = true
 call = { path = "../call2", package = "call2", features = ["test-support"] }
 client = { path = "../client2", package = "client2", features = ["test-support"] }
 gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
-project = { path = "../project2", package = "project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
 fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
 db = { path = "../db2", package = "db2", features = ["test-support"] }

crates/zed/Cargo.toml 🔗

@@ -53,7 +53,7 @@ notifications = { package = "notifications2", path = "../notifications2" }
 assistant = { path = "../assistant" }
 outline = { path = "../outline" }
 # plugin_runtime = { path = "../plugin_runtime",optional = true }
-project = { package = "project2", path = "../project2" }
+project = { path = "../project" }
 project_panel = { path = "../project_panel" }
 project_symbols = { path = "../project_symbols" }
 quick_action_bar = { path = "../quick_action_bar" }
@@ -151,7 +151,7 @@ call = { package = "call2", path = "../call2", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
 # lsp = { path = "../lsp", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 # rpc = { path = "../rpc", features = ["test-support"] }
 # settings = { path = "../settings", features = ["test-support"] }
 text = { package = "text2", path = "../text2", features = ["test-support"] }