Revert "Refactor lsp store (#17435)" (#17484)

Thorsten Ball and Bennet created

This reverts commit 8a1e8e37bb659e261526d7d5d6340840294cf290 (PR #17435)
because it creates a panic when joining a collab project.

Stack trace of the panic:

```
Thread "main" panicked with "ProjectLspAdapterDelegate cannot be constructedd on an ssh-remote yet" at crates/project/src/lsp_store.rs:6332:13
   0: backtrace::backtrace::libunwind::trace
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.73/src/backtrace/libunwind.rs:116:5
      backtrace::backtrace::trace_unsynchronized::<<backtrace::capture::Backtrace>::create::{closure#0}>
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.73/src/backtrace/mod.rs:66:5
   1: backtrace::backtrace::trace::<<backtrace::capture::Backtrace>::create::{closure#0}>
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.73/src/backtrace/mod.rs:53:14
   2: <backtrace::capture::Backtrace>::create
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.73/src/capture.rs:197:9
   3: <backtrace::capture::Backtrace>::new
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.73/src/capture.rs:162:22
   4: zed::reliability::init_panic_hook::{closure#0}
             at /Users/thorstenball/work/zed/crates/zed/src/reliability.rs:58:29
   5: <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/alloc/src/boxed.rs:2084:9
      std::panicking::rust_panic_with_hook
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:808:13
   6: std::panicking::begin_panic_handler::{{closure}}
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:667:13
   7: std::sys::backtrace::__rust_end_short_backtrace
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/sys/backtrace.rs:168:18
   8: rust_begin_unwind
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:665:5
   9: core::panicking::panic_fmt
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/panicking.rs:74:14
  10: <project::lsp_store::ProjectLspAdapterDelegate>::new
             at /Users/thorstenball/work/zed/crates/project/src/lsp_store.rs:6332:13
  11: assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}::{closure#1}
             at /Users/thorstenball/work/zed/crates/assistant/src/assistant_panel.rs:5159:16
  12: <gpui::app::AppContext as gpui::Context>::update_model::<project::lsp_store::LspStore, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}::{closure#1}>::{closure#0}
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:1365:26
  13: <gpui::app::AppContext>::update::<core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, <gpui::app::AppContext as gpui::Context>::update_model<project::lsp_store::LspStore, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}::{closure#1}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:362:22
  14: <gpui::app::AppContext as gpui::Context>::update_model::<project::lsp_store::LspStore, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}::{closure#1}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:1363:9
  15: <gpui::app::model_context::ModelContext<project::Project> as gpui::Context>::update_model::<project::lsp_store::LspStore, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}::{closure#1}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app/model_context.rs:250:9
  16: <gpui::app::entity_map::Model<project::lsp_store::LspStore>>::update::<gpui::app::model_context::ModelContext<project::Project>, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}::{closure#1}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app/entity_map.rs:422:9
  17: assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}
             at /Users/thorstenball/work/zed/crates/assistant/src/assistant_panel.rs:5158:9
  18: <gpui::app::AppContext as gpui::Context>::update_model::<project::Project, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}>::{closure#0}
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:1365:26
  19: <gpui::app::AppContext>::update::<core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, <gpui::app::AppContext as gpui::Context>::update_model<project::Project, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:362:22
  20: <gpui::app::AppContext as gpui::Context>::update_model::<project::Project, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:1363:9
  21: <gpui::app::entity_map::Model<project::Project>>::update::<gpui::app::AppContext, core::result::Result<alloc::sync::Arc<dyn language::LspAdapterDelegate>, anyhow::Error>, assistant::assistant_panel::make_lsp_adapter_delegate::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app/entity_map.rs:422:9
  22: assistant::assistant_panel::make_lsp_adapter_delegate
             at /Users/thorstenball/work/zed/crates/assistant/src/assistant_panel.rs:5152:5
  23: <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}
             at /Users/thorstenball/work/zed/crates/assistant/src/assistant_panel.rs:960:48
  24: <gpui::window::WindowContext as gpui::VisualContext>::update_view::<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/window.rs:3940:22
  25: <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view::<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}
             at /Users/thorstenball/work/zed/crates/gpui/src/app/async_context.rs:387:35
  26: <gpui::app::AppContext as gpui::Context>::update_window::<core::result::Result<(), anyhow::Error>, <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:1396:26
  27: <gpui::app::AppContext>::update::<core::result::Result<core::result::Result<(), anyhow::Error>, anyhow::Error>, <gpui::app::AppContext as gpui::Context>::update_window<core::result::Result<(), anyhow::Error>, <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:362:22
  28: <gpui::app::AppContext as gpui::Context>::update_window::<core::result::Result<(), anyhow::Error>, <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:1387:9
  29: <gpui::app::async_context::AsyncAppContext as gpui::Context>::update_window::<core::result::Result<(), anyhow::Error>, <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app/async_context.rs:91:9
  30: <gpui::app::async_context::AsyncWindowContext as gpui::Context>::update_window::<core::result::Result<(), anyhow::Error>, <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app/async_context.rs:354:9
  31: <gpui::window::AnyWindowHandle>::update::<gpui::app::async_context::AsyncWindowContext, core::result::Result<(), anyhow::Error>, <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/window.rs:4800:9
  32: <gpui::app::async_context::AsyncWindowContext as gpui::VisualContext>::update_view::<assistant::assistant_panel::AssistantPanel, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app/async_context.rs:386:9
  33: <gpui::view::View<assistant::assistant_panel::AssistantPanel>>::update::<gpui::app::async_context::AsyncWindowContext, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/view.rs:76:9
  34: <gpui::view::WeakView<assistant::assistant_panel::AssistantPanel>>::update::<gpui::app::async_context::AsyncWindowContext, core::result::Result<(), anyhow::Error>, <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}::{closure#0}>
             at /Users/thorstenball/work/zed/crates/gpui/src/view.rs:192:12
  35: <assistant::assistant_panel::AssistantPanel>::new_context::{closure#1}::{closure#0}
             at /Users/thorstenball/work/zed/crates/assistant/src/assistant_panel.rs:957:17
  36: <core::pin::Pin<alloc::boxed::Box<dyn core::future::future::Future<Output = core::result::Result<(), anyhow::Error>>>> as core::future::future::Future>::poll
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/future/future.rs:123:9
  37: <<async_task::runnable::Builder<_>>::spawn_local::Checked<core::pin::Pin<alloc::boxed::Box<dyn core::future::future::Future<Output = core::result::Result<(), anyhow::Error>>>>> as core::future::future::Future>::poll
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.1/src/runnable.rs:455:26
  38: <async_task::raw::RawTask<<async_task::runnable::Builder<_>>::spawn_local::Checked<core::pin::Pin<alloc::boxed::Box<dyn core::future::future::Future<Output = core::result::Result<(), anyhow::Error>>>>>, core::result::Result<(), anyhow::Error>, <gpui::executor::ForegroundExecutor>::spawn::inner<core::result::Result<(), anyhow::Error>>::{closure#0}, ()>>::run
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.1/src/raw.rs:557:17
  39: <async_task::runnable::Runnable>::run
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.1/src/runnable.rs:781:18
  40: gpui::platform::mac::dispatcher::trampoline
             at /Users/thorstenball/work/zed/crates/gpui/src/platform/mac/dispatcher.rs:106:5
  41: <unknown>
  42: <unknown>
  43: <unknown>
  44: <unknown>
  45: <unknown>
  46: <unknown>
  47: <unknown>
  48: <unknown>
  49: <unknown>
  50: <unknown>
  51: <unknown>
  52: <unknown>
  53: <() as objc::message::MessageArguments>::invoke::<()>
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/objc-0.2.7/src/message/mod.rs:128:17
  54: objc::message::platform::send_unverified::<objc::runtime::Object, (), ()>
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/objc-0.2.7/src/message/apple/mod.rs:27:9
  55: objc::message::send_message::<objc::runtime::Object, (), ()>
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/objc-0.2.7/src/message/mod.rs:178:5
      <*mut objc::runtime::Object as cocoa::appkit::NSApplication>::run
             at /Users/thorstenball/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cocoa-0.26.0/src/appkit.rs:628:9
  56: <gpui::platform::mac::platform::MacPlatform as gpui::platform::Platform>::run
             at /Users/thorstenball/work/zed/crates/gpui/src/platform/mac/platform.rs:427:13
  57: <gpui::app::App>::run::<zed::main::{closure#3}>
             at /Users/thorstenball/work/zed/crates/gpui/src/app.rs:159:9
  58: zed::main
             at /Users/thorstenball/work/zed/crates/zed/src/main.rs:439:5
  59: <fn() as core::ops::function::FnOnce<()>>::call_once
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/ops/function.rs:250:5
  60: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/sys/backtrace.rs:152:18
  61: std::rt::lang_start::<()>::{closure#0}
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/rt.rs:162:18
  62: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/ops/function.rs:284:13
      std::panicking
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:557:40
      std::panicking::try
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:521:19
      std::panic::catch_unwind
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panic.rs:350:14
      std::rt::lang_start_internal::{{closure}}
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/rt.rs:141:48
      std::panicking::try::do_call
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:557:40
      std::panicking::try
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:521:19
      std::panic::catch_unwind
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panic.rs:350:14
      std::rt::lang_start_internal
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/rt.rs:141:20
  63: std::rt::lang_start::<()>
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/rt.rs:161:17
  64: _main
```

Closes #ISSUE

Release Notes:

- Added/Fixed/Improved ...

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.

### Or...

Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>

Change summary

crates/language_tools/src/lsp_log.rs         |   2 
crates/project/src/lsp_store.rs              | 721 ++++++++-------------
crates/project/src/project.rs                | 111 --
crates/remote_server/src/headless_project.rs |  11 
4 files changed, 309 insertions(+), 536 deletions(-)

Detailed changes

crates/language_tools/src/lsp_log.rs 🔗

@@ -688,7 +688,7 @@ impl LspLogView {
                 self.project
                     .read(cx)
                     .supplementary_language_servers(cx)
-                    .filter_map(|(server_id, name)| {
+                    .filter_map(|(&server_id, name)| {
                         let state = log_store.language_servers.get(&server_id)?;
                         Some(LogMenuItem {
                             server_id,

crates/project/src/lsp_store.rs 🔗

@@ -85,82 +85,27 @@ const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
 const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
 pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);
 
-pub struct LocalLspStore {
-    http_client: Option<Arc<dyn HttpClient>>,
-    environment: Model<ProjectEnvironment>,
-    fs: Arc<dyn Fs>,
-    yarn: Model<YarnPathStore>,
-    language_servers: HashMap<LanguageServerId, LanguageServerState>,
-    last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
-    language_server_watched_paths: HashMap<LanguageServerId, HashMap<WorktreeId, GlobSet>>,
-    language_server_watcher_registrations:
-        HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
-    supplementary_language_servers:
-        HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
-    _subscription: gpui::Subscription,
-}
-
-impl LocalLspStore {
-    fn shutdown_language_servers(
-        &mut self,
-        _cx: &mut ModelContext<LspStore>,
-    ) -> 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;
-        }
-    }
-}
-
-pub struct RemoteLspStore {
-    upstream_client: AnyProtoClient,
-}
-
-impl RemoteLspStore {}
-
-pub struct SshLspStore {
-    upstream_client: AnyProtoClient,
-}
-
-#[allow(clippy::large_enum_variant)]
-pub enum LspStoreMode {
-    Local(LocalLspStore),   // ssh host and collab host
-    Remote(RemoteLspStore), // collab guest
-    Ssh(SshLspStore),       // ssh client
-}
-
-impl LspStoreMode {
-    fn is_local(&self) -> bool {
-        matches!(self, LspStoreMode::Local(_))
-    }
-
-    fn is_ssh(&self) -> bool {
-        matches!(self, LspStoreMode::Ssh(_))
-    }
-}
-
 pub struct LspStore {
-    mode: LspStoreMode,
     downstream_client: Option<AnyProtoClient>,
+    upstream_client: Option<AnyProtoClient>,
     project_id: u64,
+    http_client: Option<Arc<dyn HttpClient>>,
+    fs: Arc<dyn Fs>,
     nonce: u128,
     buffer_store: Model<BufferStore>,
     worktree_store: Model<WorktreeStore>,
     buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
+    environment: Option<Model<ProjectEnvironment>>,
+    supplementary_language_servers:
+        HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
     languages: Arc<LanguageRegistry>,
+    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>,
+    language_server_watched_paths: HashMap<LanguageServerId, HashMap<WorktreeId, GlobSet>>,
+    language_server_watcher_registrations:
+        HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
     active_entry: Option<ProjectEntryId>,
     _maintain_workspace_config: Task<Result<()>>,
     _maintain_buffer_languages: Task<()>,
@@ -177,6 +122,8 @@ pub struct LspStore {
             )>,
         >,
     >,
+    yarn: Model<YarnPathStore>,
+    _subscription: gpui::Subscription,
 }
 
 pub enum LspStoreEvent {
@@ -262,53 +209,17 @@ impl LspStore {
         client.add_model_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
     }
 
-    pub fn as_remote(&self) -> Option<&RemoteLspStore> {
-        match &self.mode {
-            LspStoreMode::Remote(remote_lsp_store) => Some(remote_lsp_store),
-            _ => None,
-        }
-    }
-
-    pub fn as_ssh(&self) -> Option<&SshLspStore> {
-        match &self.mode {
-            LspStoreMode::Ssh(ssh_lsp_store) => Some(ssh_lsp_store),
-            _ => None,
-        }
-    }
-
-    pub fn as_local(&self) -> Option<&LocalLspStore> {
-        match &self.mode {
-            LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
-            _ => None,
-        }
-    }
-
-    pub fn as_local_mut(&mut self) -> Option<&mut LocalLspStore> {
-        match &mut self.mode {
-            LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
-            _ => None,
-        }
-    }
-
-    pub fn upstream_client(&self) -> Option<AnyProtoClient> {
-        match &self.mode {
-            LspStoreMode::Ssh(SshLspStore {
-                upstream_client, ..
-            })
-            | LspStoreMode::Remote(RemoteLspStore {
-                upstream_client, ..
-            }) => Some(upstream_client.clone()),
-            LspStoreMode::Local(_) => None,
-        }
-    }
-
-    pub fn new_local(
+    #[allow(clippy::too_many_arguments)]
+    pub fn new(
         buffer_store: Model<BufferStore>,
         worktree_store: Model<WorktreeStore>,
-        environment: Model<ProjectEnvironment>,
+        environment: Option<Model<ProjectEnvironment>>,
         languages: Arc<LanguageRegistry>,
         http_client: Option<Arc<dyn HttpClient>>,
         fs: Arc<dyn Fs>,
+        downstream_client: Option<AnyProtoClient>,
+        upstream_client: Option<AnyProtoClient>,
+        remote_id: Option<u64>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let yarn = YarnPathStore::new(fs.clone(), cx);
@@ -318,85 +229,32 @@ impl LspStore {
             .detach();
 
         Self {
-            mode: LspStoreMode::Local(LocalLspStore {
-                supplementary_language_servers: Default::default(),
-                language_servers: Default::default(),
-                last_workspace_edits_by_language_server: Default::default(),
-                language_server_watched_paths: Default::default(),
-                language_server_watcher_registrations: Default::default(),
-                environment,
-                http_client,
-                fs,
-                yarn,
-                _subscription: cx.on_app_quit(|this, cx| {
-                    this.as_local_mut().unwrap().shutdown_language_servers(cx)
-                }),
-            }),
-            downstream_client: None,
-            project_id: 0,
+            downstream_client,
+            upstream_client,
+            http_client,
+            fs,
+            project_id: remote_id.unwrap_or(0),
             buffer_store,
             worktree_store,
             languages: languages.clone(),
-            language_server_ids: Default::default(),
-            language_server_statuses: Default::default(),
+            environment,
             nonce: StdRng::from_entropy().gen(),
             buffer_snapshots: Default::default(),
-            next_diagnostic_group_id: Default::default(),
-            diagnostic_summaries: Default::default(),
-            diagnostics: Default::default(),
-            active_entry: None,
-            _maintain_workspace_config: Self::maintain_workspace_config(cx),
-            _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
-        }
-    }
-
-    fn send_lsp_proto_request<R: LspCommand>(
-        &self,
-        buffer: Model<Buffer>,
-        client: AnyProtoClient,
-        request: R,
-        cx: &mut ModelContext<'_, LspStore>,
-    ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
-        let message = request.to_proto(self.project_id, buffer.read(cx));
-        cx.spawn(move |this, cx| async move {
-            let response = client.request(message).await?;
-            let this = this.upgrade().context("project dropped")?;
-            request
-                .response_from_proto(response, this, buffer, cx)
-                .await
-        })
-    }
-
-    pub fn new_remote(
-        buffer_store: Model<BufferStore>,
-        worktree_store: Model<WorktreeStore>,
-        languages: Arc<LanguageRegistry>,
-        upstream_client: AnyProtoClient,
-        project_id: u64,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        cx.subscribe(&buffer_store, Self::on_buffer_store_event)
-            .detach();
-        cx.subscribe(&worktree_store, Self::on_worktree_store_event)
-            .detach();
-
-        Self {
-            mode: LspStoreMode::Remote(RemoteLspStore { upstream_client }),
-            downstream_client: None,
-            project_id,
-            buffer_store,
-            worktree_store,
-            languages: languages.clone(),
+            supplementary_language_servers: Default::default(),
+            language_servers: Default::default(),
             language_server_ids: Default::default(),
             language_server_statuses: Default::default(),
-            nonce: StdRng::from_entropy().gen(),
-            buffer_snapshots: Default::default(),
+            last_workspace_edits_by_language_server: Default::default(),
+            language_server_watched_paths: Default::default(),
+            language_server_watcher_registrations: Default::default(),
             next_diagnostic_group_id: Default::default(),
             diagnostic_summaries: Default::default(),
             diagnostics: Default::default(),
             active_entry: None,
+            yarn,
             _maintain_workspace_config: Self::maintain_workspace_config(cx),
             _maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
+            _subscription: cx.on_app_quit(Self::shutdown_language_servers),
         }
     }
 
@@ -636,6 +494,27 @@ impl LspStore {
         self.active_entry = active_entry;
     }
 
+    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;
+        }
+    }
+
     pub(crate) fn send_diagnostic_summaries(
         &self,
         worktree: &mut Worktree,
@@ -668,11 +547,9 @@ impl LspStore {
         <R::LspRequest as lsp::request::Request>::Params: Send,
     {
         let buffer = buffer_handle.read(cx);
-
-        if let Some(upstream_client) = self.upstream_client() {
-            return self.send_lsp_proto_request(buffer_handle, upstream_client, request, cx);
+        if self.upstream_client.is_some() {
+            return self.send_lsp_proto_request(buffer_handle, self.project_id, request, cx);
         }
-
         let language_server = match server {
             LanguageServerToQuery::Primary => {
                 match self.primary_language_server_for_buffer(buffer, cx) {
@@ -758,6 +635,26 @@ impl LspStore {
         Task::ready(Ok(Default::default()))
     }
 
+    fn send_lsp_proto_request<R: LspCommand>(
+        &self,
+        buffer: Model<Buffer>,
+        project_id: u64,
+        request: R,
+        cx: &mut ModelContext<'_, Self>,
+    ) -> Task<anyhow::Result<<R as LspCommand>::Response>> {
+        let Some(upstream_client) = self.upstream_client.clone() else {
+            return Task::ready(Err(anyhow!("disconnected before completing request")));
+        };
+        let message = request.to_proto(project_id, buffer.read(cx));
+        cx.spawn(move |this, cx| async move {
+            let response = upstream_client.request(message).await?;
+            let this = this.upgrade().context("project dropped")?;
+            request
+                .response_from_proto(response, this, buffer, cx)
+                .await
+        })
+    }
+
     pub async fn execute_code_actions_on_servers(
         this: &WeakModel<LspStore>,
         adapters_and_servers: &Vec<(Arc<CachedLspAdapter>, Arc<LanguageServer>)>,
@@ -805,10 +702,8 @@ impl LspStore {
 
                 if let Some(command) = action.lsp_action.command {
                     this.update(cx, |this, _| {
-                        if let LspStoreMode::Local(mode) = &mut this.mode {
-                            mode.last_workspace_edits_by_language_server
-                                .remove(&language_server.server_id());
-                        }
+                        this.last_workspace_edits_by_language_server
+                            .remove(&language_server.server_id());
                     })?;
 
                     language_server
@@ -820,14 +715,12 @@ impl LspStore {
                         .await?;
 
                     this.update(cx, |this, _| {
-                        if let LspStoreMode::Local(mode) = &mut this.mode {
-                            project_transaction.0.extend(
-                                mode.last_workspace_edits_by_language_server
-                                    .remove(&language_server.server_id())
-                                    .unwrap_or_default()
-                                    .0,
-                            )
-                        }
+                        project_transaction.0.extend(
+                            this.last_workspace_edits_by_language_server
+                                .remove(&language_server.server_id())
+                                .unwrap_or_default()
+                                .0,
+                        )
                     })?;
                 }
             }
@@ -860,7 +753,7 @@ impl LspStore {
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<ProjectTransaction>> {
-        if let Some(upstream_client) = self.upstream_client() {
+        if let Some(upstream_client) = self.upstream_client.clone() {
             let request = proto::ApplyCodeAction {
                 project_id: self.project_id,
                 buffer_id: buffer_handle.read(cx).remote_id().into(),
@@ -909,9 +802,7 @@ impl LspStore {
 
                 if let Some(command) = action.lsp_action.command {
                     this.update(&mut cx, |this, _| {
-                        this.as_local_mut()
-                            .unwrap()
-                            .last_workspace_edits_by_language_server
+                        this.last_workspace_edits_by_language_server
                             .remove(&lang_server.server_id());
                     })?;
 
@@ -929,9 +820,7 @@ impl LspStore {
                     }
 
                     return this.update(&mut cx, |this, _| {
-                        this.as_local_mut()
-                            .unwrap()
-                            .last_workspace_edits_by_language_server
+                        this.last_workspace_edits_by_language_server
                             .remove(&lang_server.server_id())
                             .unwrap_or_default()
                     });
@@ -949,7 +838,7 @@ impl LspStore {
         server_id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) -> Task<anyhow::Result<InlayHint>> {
-        if let Some(upstream_client) = self.upstream_client() {
+        if let Some(upstream_client) = self.upstream_client.clone() {
             let request = proto::ResolveInlayHint {
                 project_id: self.project_id,
                 buffer_id: buffer_handle.read(cx).remote_id().into(),
@@ -1027,7 +916,7 @@ impl LspStore {
             .map(|(_, server)| LanguageServerToQuery::Other(server.server_id()))
             .next()
             .or_else(|| {
-                self.upstream_client()
+                self.upstream_client
                     .is_some()
                     .then_some(LanguageServerToQuery::Primary)
             })
@@ -1060,7 +949,7 @@ impl LspStore {
         trigger: String,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Option<Transaction>>> {
-        if let Some(client) = self.upstream_client() {
+        if let Some(client) = self.upstream_client.clone() {
             let request = proto::OnTypeFormatting {
                 project_id: self.project_id,
                 buffer_id: buffer.read(cx).remote_id().into(),
@@ -1210,7 +1099,7 @@ impl LspStore {
         range: Range<Anchor>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Vec<CodeAction>> {
-        if let Some(upstream_client) = self.upstream_client() {
+        if let Some(upstream_client) = self.upstream_client.as_ref() {
             let request_task = upstream_client.request(proto::MultiLspQuery {
                 buffer_id: buffer_handle.read(cx).remote_id().into(),
                 version: serialize_version(&buffer_handle.read(cx).version()),
@@ -1290,10 +1179,10 @@ impl LspStore {
     ) -> Task<Result<Vec<Completion>>> {
         let language_registry = self.languages.clone();
 
-        if let Some(upstream_client) = self.upstream_client() {
+        if let Some(_) = self.upstream_client.clone() {
             let task = self.send_lsp_proto_request(
                 buffer.clone(),
-                upstream_client,
+                self.project_id,
                 GetCompletions { position, context },
                 cx,
             );
@@ -1384,7 +1273,7 @@ impl LspStore {
         completions: Arc<RwLock<Box<[Completion]>>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<bool>> {
-        let client = self.upstream_client();
+        let client = self.upstream_client.clone();
         let language_registry = self.languages.clone();
         let project_id = self.project_id;
 
@@ -1593,7 +1482,7 @@ impl LspStore {
         let buffer = buffer_handle.read(cx);
         let buffer_id = buffer.remote_id();
 
-        if let Some(client) = self.upstream_client() {
+        if let Some(client) = self.upstream_client.clone() {
             let project_id = self.project_id;
             cx.spawn(move |_, mut cx| async move {
                 let response = client
@@ -1709,7 +1598,7 @@ impl LspStore {
         let buffer_id = buffer.remote_id().into();
         let lsp_request = InlayHints { range };
 
-        if let Some(client) = self.upstream_client() {
+        if let Some(client) = self.upstream_client.clone() {
             let request = proto::InlayHints {
                 project_id: self.project_id,
                 buffer_id,
@@ -1759,7 +1648,7 @@ impl LspStore {
     ) -> Task<Vec<SignatureHelp>> {
         let position = position.to_point_utf16(buffer.read(cx));
 
-        if let Some(client) = self.upstream_client() {
+        if let Some(client) = self.upstream_client.clone() {
             let request_task = client.request(proto::MultiLspQuery {
                 buffer_id: buffer.read(cx).remote_id().into(),
                 version: serialize_version(&buffer.read(cx).version()),
@@ -1831,7 +1720,7 @@ impl LspStore {
         position: PointUtf16,
         cx: &mut ModelContext<Self>,
     ) -> Task<Vec<Hover>> {
-        if let Some(client) = self.upstream_client() {
+        if let Some(client) = self.upstream_client.clone() {
             let request_task = client.request(proto::MultiLspQuery {
                 buffer_id: buffer.read(cx).remote_id().into(),
                 version: serialize_version(&buffer.read(cx).version()),
@@ -1905,7 +1794,7 @@ impl LspStore {
     pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
         let language_registry = self.languages.clone();
 
-        if let Some(upstream_client) = self.upstream_client().as_ref() {
+        if let Some(upstream_client) = self.upstream_client.as_ref() {
             let request = upstream_client.request(proto::GetProjectSymbols {
                 project_id: self.project_id,
                 query: query.to_string(),
@@ -1952,17 +1841,16 @@ impl LspStore {
                 }
                 let worktree_abs_path = worktree.abs_path().clone();
 
-                let (lsp_adapter, language, server) =
-                    match self.as_local().unwrap().language_servers.get(server_id) {
-                        Some(LanguageServerState::Running {
-                            adapter,
-                            language,
-                            server,
-                            ..
-                        }) => (adapter.clone(), language.clone(), server),
+                let (lsp_adapter, language, server) = match self.language_servers.get(server_id) {
+                    Some(LanguageServerState::Running {
+                        adapter,
+                        language,
+                        server,
+                        ..
+                    }) => (adapter.clone(), language.clone(), server),
 
-                        _ => continue,
-                    };
+                    _ => continue,
+                };
 
                 requests.push(
                     server
@@ -2264,7 +2152,7 @@ impl LspStore {
                                 .worktree_store
                                 .read(cx)
                                 .worktree_for_id(*worktree_id, cx)?;
-                            let state = this.as_local()?.language_servers.get(server_id)?;
+                            let state = this.language_servers.get(server_id)?;
                             let delegate = ProjectLspAdapterDelegate::new(this, &worktree, cx);
                             match state {
                                 LanguageServerState::Starting(_) => None,
@@ -2330,7 +2218,7 @@ impl LspStore {
                         language,
                         server,
                         ..
-                    }) = self.as_local()?.language_servers.get(id)
+                    }) = self.language_servers.get(id)
                     {
                         return Some((adapter, language, server));
                     }
@@ -2357,17 +2245,11 @@ impl LspStore {
             self.language_server_ids
                 .remove(&(id_to_remove, server_name));
             self.language_server_statuses.remove(&server_id_to_remove);
-            if let Some(local_lsp_store) = self.as_local_mut() {
-                local_lsp_store
-                    .language_server_watched_paths
-                    .remove(&server_id_to_remove);
-                local_lsp_store
-                    .last_workspace_edits_by_language_server
-                    .remove(&server_id_to_remove);
-                local_lsp_store
-                    .language_servers
-                    .remove(&server_id_to_remove);
-            }
+            self.language_server_watched_paths
+                .remove(&server_id_to_remove);
+            self.last_workspace_edits_by_language_server
+                .remove(&server_id_to_remove);
+            self.language_servers.remove(&server_id_to_remove);
             cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));
         }
     }
@@ -2463,7 +2345,7 @@ impl LspStore {
                     let server = self
                         .language_server_ids
                         .get(&(worktree_id, adapter.name.clone()))
-                        .and_then(|id| self.as_local()?.language_servers.get(id))
+                        .and_then(|id| self.language_servers.get(id))
                         .and_then(|server_state| {
                             if let LanguageServerState::Running { server, .. } = server_state {
                                 Some(server.clone())
@@ -2659,7 +2541,7 @@ impl LspStore {
         symbol: &Symbol,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Model<Buffer>>> {
-        if let Some(client) = self.upstream_client() {
+        if let Some(client) = self.upstream_client.clone() {
             let request = client.request(proto::OpenBufferForSymbol {
                 project_id: self.project_id,
                 symbol: Some(Self::serialize_symbol(symbol)),
@@ -2727,7 +2609,7 @@ impl LspStore {
             let p = abs_path.clone();
             let yarn_worktree = this
                 .update(&mut cx, move |this, cx| {
-                    this.as_local().unwrap().yarn.update(cx, |_, cx| {
+                    this.yarn.update(cx, |_, cx| {
                         cx.spawn(|this, mut cx| async move {
                             let t = this
                                 .update(&mut cx, |this, cx| {
@@ -2877,7 +2759,7 @@ impl LspStore {
         <R::LspRequest as lsp::request::Request>::Result: Send,
         <R::LspRequest as lsp::request::Request>::Params: Send,
     {
-        debug_assert!(self.upstream_client().is_none());
+        debug_assert!(self.upstream_client.is_none());
 
         let snapshot = buffer.read(cx).snapshot();
         let scope = position.and_then(|position| snapshot.language_scope_at(position));
@@ -3306,9 +3188,7 @@ impl LspStore {
             simulate_disk_based_diagnostics_completion,
             adapter,
             ..
-        }) = self
-            .as_local_mut()
-            .and_then(|local_store| local_store.language_servers.get_mut(&language_server_id))
+        }) = self.language_servers.get_mut(&language_server_id)
         else {
             return;
         };
@@ -3329,9 +3209,8 @@ impl LspStore {
                     if let Some(LanguageServerState::Running {
                         simulate_disk_based_diagnostics_completion,
                         ..
-                    }) = this.as_local_mut().and_then(|local_store| {
-                        local_store.language_servers.get_mut(&language_server_id)
-                    }) {
+                    }) = this.language_servers.get_mut(&language_server_id)
+                    {
                         *simulate_disk_based_diagnostics_completion = None;
                     }
                 })
@@ -3357,24 +3236,21 @@ impl LspStore {
         language_server_id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) {
-        let worktrees = self.worktree_store.read(cx).worktrees().collect::<Vec<_>>();
-        let local_lsp_store = self.as_local_mut().unwrap();
-
-        let Some(watchers) = local_lsp_store
+        let Some(watchers) = self
             .language_server_watcher_registrations
             .get(&language_server_id)
         else {
             return;
         };
 
-        let watched_paths = local_lsp_store
+        let watched_paths = self
             .language_server_watched_paths
             .entry(language_server_id)
             .or_default();
 
         let mut builders = HashMap::default();
         for watcher in watchers.values().flatten() {
-            for worktree in worktrees.iter() {
+            for worktree in self.worktree_store.read(cx).worktrees().collect::<Vec<_>>() {
                 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 {
@@ -3428,18 +3304,10 @@ impl LspStore {
     }
 
     pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
-        if let Some(local_lsp_store) = self.as_local() {
-            if let Some(LanguageServerState::Running { server, .. }) =
-                local_lsp_store.language_servers.get(&id)
-            {
-                Some(server.clone())
-            } else if let Some((_, server)) =
-                local_lsp_store.supplementary_language_servers.get(&id)
-            {
-                Some(Arc::clone(server))
-            } else {
-                None
-            }
+        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
         }
@@ -3470,9 +3338,7 @@ impl LspStore {
         .log_err();
         this.update(&mut cx, |this, _| {
             if let Some(transaction) = transaction {
-                this.as_local_mut()
-                    .unwrap()
-                    .last_workspace_edits_by_language_server
+                this.last_workspace_edits_by_language_server
                     .insert(server_id, transaction);
             }
         })?;
@@ -3657,16 +3523,14 @@ impl LspStore {
         params: DidChangeWatchedFilesRegistrationOptions,
         cx: &mut ModelContext<Self>,
     ) {
-        if let Some(local) = self.as_local_mut() {
-            let registrations = local
-                .language_server_watcher_registrations
-                .entry(language_server_id)
-                .or_default();
+        let registrations = self
+            .language_server_watcher_registrations
+            .entry(language_server_id)
+            .or_default();
 
-            registrations.insert(registration_id.to_string(), params.watchers);
+        registrations.insert(registration_id.to_string(), params.watchers);
 
-            self.rebuild_watched_paths(language_server_id, cx);
-        }
+        self.rebuild_watched_paths(language_server_id, cx);
     }
 
     fn on_lsp_unregister_did_change_watched_files(
@@ -3675,28 +3539,26 @@ impl LspStore {
         registration_id: &str,
         cx: &mut ModelContext<Self>,
     ) {
-        if let Some(local) = self.as_local_mut() {
-            let registrations = local
-                .language_server_watcher_registrations
-                .entry(language_server_id)
-                .or_default();
+        let registrations = self
+            .language_server_watcher_registrations
+            .entry(language_server_id)
+            .or_default();
 
-            if registrations.remove(registration_id).is_some() {
-                log::info!(
+        if registrations.remove(registration_id).is_some() {
+            log::info!(
                 "language server {}: unregistered workspace/DidChangeWatchedFiles capability with id {}",
                 language_server_id,
                 registration_id
             );
-            } else {
-                log::warn!(
+        } else {
+            log::warn!(
                 "language server {}: failed to unregister workspace/DidChangeWatchedFiles capability with id {}. not registered.",
                 language_server_id,
                 registration_id
             );
-            }
-
-            self.rebuild_watched_paths(language_server_id, cx);
         }
+
+        self.rebuild_watched_paths(language_server_id, cx);
     }
 
     #[allow(clippy::type_complexity)]
@@ -4213,133 +4075,120 @@ impl LspStore {
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
-        if self.mode.is_local() {
-            if adapter.reinstall_attempt_count.load(SeqCst) > MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
-                return;
-            }
+        if adapter.reinstall_attempt_count.load(SeqCst) > MAX_SERVER_REINSTALL_ATTEMPT_COUNT {
+            return;
+        }
 
-            let worktree = worktree_handle.read(cx);
-            let worktree_id = worktree.id();
-            let worktree_path = worktree.abs_path();
-            let key = (worktree_id, adapter.name.clone());
-            if self.language_server_ids.contains_key(&key) {
-                return;
-            }
+        let worktree = worktree_handle.read(cx);
+        let worktree_id = worktree.id();
+        let worktree_path = worktree.abs_path();
+        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 lsp_adapter_delegate = ProjectLspAdapterDelegate::new(self, worktree_handle, cx);
-            let cli_environment = self
-                .as_local()
-                .unwrap()
-                .environment
-                .read(cx)
-                .get_cli_environment();
-
-            let pending_server = match self.languages.create_pending_language_server(
-                stderr_capture.clone(),
-                language.clone(),
-                adapter.clone(),
-                Arc::clone(&worktree_path),
-                lsp_adapter_delegate.clone(),
-                cli_environment,
-                cx,
-            ) {
-                Some(pending_server) => pending_server,
-                None => return,
-            };
+        let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
+        let lsp_adapter_delegate = ProjectLspAdapterDelegate::new(self, worktree_handle, cx);
+        let cli_environment = self
+            .environment
+            .as_ref()
+            .and_then(|environment| environment.read(cx).get_cli_environment());
+        let pending_server = match self.languages.create_pending_language_server(
+            stderr_capture.clone(),
+            language.clone(),
+            adapter.clone(),
+            Arc::clone(&worktree_path),
+            lsp_adapter_delegate.clone(),
+            cli_environment,
+            cx,
+        ) {
+            Some(pending_server) => pending_server,
+            None => return,
+        };
 
-            let project_settings = ProjectSettings::get(
-                Some(SettingsLocation {
-                    worktree_id: worktree_id.to_proto() as usize,
-                    path: Path::new(""),
-                }),
-                cx,
-            );
-            let lsp = project_settings.lsp.get(&adapter.name.0);
-            let override_options = lsp.and_then(|s| s.initialization_options.clone());
+        let project_settings = ProjectSettings::get(
+            Some(SettingsLocation {
+                worktree_id: worktree_id.to_proto() as usize,
+                path: Path::new(""),
+            }),
+            cx,
+        );
+        let lsp = project_settings.lsp.get(&adapter.name.0);
+        let override_options = lsp.and_then(|s| s.initialization_options.clone());
 
-            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();
+        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(),
-                        lsp_adapter_delegate,
-                        override_options,
-                        pending_server,
-                        adapter.clone(),
-                        language.clone(),
-                        server_id,
-                        key,
-                        &mut cx,
-                    )
-                    .await;
+            cx.spawn(move |this, mut cx| async move {
+                let result = Self::setup_and_insert_language_server(
+                    this.clone(),
+                    lsp_adapter_delegate,
+                    override_options,
+                    pending_server,
+                    adapter.clone(),
+                    language.clone(),
+                    server_id,
+                    key,
+                    &mut cx,
+                )
+                .await;
 
-                    match result {
-                        Ok(server) => {
-                            stderr_capture.lock().take();
-                            server
-                        }
+                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());
+                    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 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 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;
+                        }
 
-                            log::info!(
-                                "retrying installation of language server {server_name:?} in {}s",
-                                SERVER_REINSTALL_DEBOUNCE_TIMEOUT.as_secs()
-                            );
-                            cx.background_executor()
-                                .timer(SERVER_REINSTALL_DEBOUNCE_TIMEOUT)
-                                .await;
-
-                            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();
+                        log::info!(
+                            "retrying installation of language server {server_name:?} in {}s",
+                            SERVER_REINSTALL_DEBOUNCE_TIMEOUT.as_secs()
+                        );
+                        cx.background_executor()
+                            .timer(SERVER_REINSTALL_DEBOUNCE_TIMEOUT)
+                            .await;
 
-                            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.as_local_mut()
-                .unwrap()
-                .language_servers
-                .insert(server_id, state);
-            self.language_server_ids.insert(key, server_id);
-        } else if self.mode.is_ssh() {
-            // TODO ssh
-        }
+        self.language_servers.insert(server_id, state);
+        self.language_server_ids.insert(key, server_id);
     }
 
     #[allow(clippy::too_many_arguments)]
@@ -4393,56 +4242,44 @@ impl LspStore {
     ) -> Option<Task<()>> {
         log::info!("beginning to reinstall server");
 
-        if let Some(local) = self.as_local_mut() {
-            let existing_server = match local.language_servers.remove(&server_id) {
-                Some(LanguageServerState::Running { server, .. }) => Some(server),
-                _ => None,
-            };
-
-            self.worktree_store.update(cx, |store, cx| {
-                for worktree in store.worktrees() {
-                    let key = (worktree.read(cx).id(), adapter.name.clone());
-                    self.language_server_ids.remove(&key);
-                }
-            });
+        let existing_server = match self.language_servers.remove(&server_id) {
+            Some(LanguageServerState::Running { server, .. }) => Some(server),
+            _ => None,
+        };
 
-            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;
-                }
+        self.worktree_store.update(cx, |store, cx| {
+            for worktree in store.worktrees() {
+                let key = (worktree.read(cx).id(), adapter.name.clone());
+                self.language_server_ids.remove(&key);
+            }
+        });
 
-                // 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;
-                };
+        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;
+            }
 
-                this.update(&mut cx, |this, cx| {
-                    for worktree in this.worktree_store.read(cx).worktrees().collect::<Vec<_>>() {
-                        this.start_language_server(
-                            &worktree,
-                            adapter.clone(),
-                            language.clone(),
-                            cx,
-                        );
-                    }
+            // 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)
                 })
-                .ok();
-            }))
-        } else if let Some(_ssh_store) = self.as_ssh() {
-            // TODO
-            None
-        } else {
-            None
-        }
+                .log_err()
+            else {
+                return;
+            };
+            task.await;
+
+            this.update(&mut cx, |this, cx| {
+                for worktree in this.worktree_store.read(cx).worktrees().collect::<Vec<_>>() {
+                    this.start_language_server(&worktree, adapter.clone(), language.clone(), cx);
+                }
+            })
+            .ok();
+        }))
     }
 
     async fn shutdown_language_server(

crates/project/src/project.rs 🔗

@@ -643,13 +643,16 @@ impl Project {
 
             let environment = ProjectEnvironment::new(&worktree_store, env, cx);
             let lsp_store = cx.new_model(|cx| {
-                LspStore::new_local(
+                LspStore::new(
                     buffer_store.clone(),
                     worktree_store.clone(),
-                    environment.clone(),
+                    Some(environment.clone()),
                     languages.clone(),
                     Some(client.http_client()),
                     fs.clone(),
+                    None,
+                    None,
+                    None,
                     cx,
                 )
             });
@@ -709,89 +712,16 @@ impl Project {
         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 tasks = Inventory::new(cx);
-            let global_snippets_dir = paths::config_dir().join("snippets");
-            let snippets =
-                SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
-
-            let worktree_store = cx.new_model(|_| {
-                let mut worktree_store = WorktreeStore::new(false, fs.clone());
-                worktree_store.set_upstream_client(ssh.clone().into());
-                worktree_store
-            });
-            cx.subscribe(&worktree_store, Self::on_worktree_store_event)
-                .detach();
-
-            let buffer_store =
-                cx.new_model(|cx| BufferStore::new(worktree_store.clone(), None, cx));
-            cx.subscribe(&buffer_store, Self::on_buffer_store_event)
-                .detach();
+        let this = Self::local(client, node, user_store, languages, fs, None, cx);
+        this.update(cx, |this, cx| {
+            let client: AnyProtoClient = ssh.clone().into();
 
-            let settings_observer = cx.new_model(|cx| {
-                SettingsObserver::new_ssh(ssh.clone().into(), worktree_store.clone(), cx)
+            this.worktree_store.update(cx, |store, _cx| {
+                store.set_upstream_client(client.clone());
             });
-
-            let environment = ProjectEnvironment::new(&worktree_store, None, cx);
-            let lsp_store = cx.new_model(|cx| {
-                LspStore::new_remote(
-                    buffer_store.clone(),
-                    worktree_store.clone(),
-                    languages.clone(),
-                    ssh.clone().into(),
-                    0,
-                    cx,
-                )
+            this.settings_observer = cx.new_model(|cx| {
+                SettingsObserver::new_ssh(ssh.clone().into(), this.worktree_store.clone(), cx)
             });
-            cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
-
-            let this = Self {
-                buffer_ordered_messages_tx: tx,
-                collaborators: Default::default(),
-                worktree_store,
-                buffer_store,
-                lsp_store,
-                current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
-                join_project_response_message_id: 0,
-                client_state: ProjectClientState::Local,
-                client_subscriptions: Vec::new(),
-                _subscriptions: vec![
-                    cx.observe_global::<SettingsStore>(Self::on_settings_changed),
-                    cx.on_release(Self::release),
-                ],
-                active_entry: None,
-                snippets,
-                languages,
-                client,
-                user_store,
-                settings_observer,
-                fs,
-                ssh_session: Some(ssh.clone()),
-                buffers_needing_diff: Default::default(),
-                git_diff_debouncer: DebouncedDelay::new(),
-                terminals: Terminals {
-                    local_handles: Vec::new(),
-                },
-                node: Some(node),
-                default_prettier: DefaultPrettier::default(),
-                prettiers_per_worktree: HashMap::default(),
-                prettier_instances: HashMap::default(),
-                tasks,
-                hosted_project_id: None,
-                dev_server_project_id: None,
-                search_history: Self::new_search_history(),
-                environment,
-                remotely_created_buffers: Default::default(),
-                last_formatting_failure: None,
-                buffers_being_formatted: Default::default(),
-                search_included_history: Self::new_search_history(),
-                search_excluded_history: Self::new_search_history(),
-            };
-
-            let client: AnyProtoClient = ssh.clone().into();
 
             ssh.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
             ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
@@ -805,8 +735,9 @@ impl Project {
             LspStore::init(&client);
             SettingsObserver::init(&client);
 
-            this
-        })
+            this.ssh_session = Some(ssh);
+        });
+        this
     }
 
     pub async fn remote(
@@ -889,12 +820,16 @@ impl Project {
             cx.new_model(|cx| BufferStore::new(worktree_store.clone(), Some(remote_id), cx))?;
 
         let lsp_store = cx.new_model(|cx| {
-            let mut lsp_store = LspStore::new_remote(
+            let mut lsp_store = LspStore::new(
                 buffer_store.clone(),
                 worktree_store.clone(),
+                None,
                 languages.clone(),
-                client.clone().into(),
-                remote_id,
+                Some(client.http_client()),
+                fs.clone(),
+                None,
+                Some(client.clone().into()),
+                Some(remote_id),
                 cx,
             );
             lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);
@@ -4850,7 +4785,7 @@ impl Project {
     pub fn supplementary_language_servers<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName)> {
+    ) -> impl '_ + Iterator<Item = (&'a LanguageServerId, &'a LanguageServerName)> {
         self.lsp_store.read(cx).supplementary_language_servers()
     }
 

crates/remote_server/src/headless_project.rs 🔗

@@ -57,17 +57,18 @@ impl HeadlessProject {
         });
         let environment = project::ProjectEnvironment::new(&worktree_store, None, cx);
         let lsp_store = cx.new_model(|cx| {
-            let mut lsp_store = LspStore::new_local(
+            LspStore::new(
                 buffer_store.clone(),
                 worktree_store.clone(),
-                environment,
+                Some(environment),
                 languages,
                 None,
                 fs.clone(),
+                Some(session.clone().into()),
+                None,
+                Some(0),
                 cx,
-            );
-            lsp_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
-            lsp_store
+            )
         });
 
         let client: AnyProtoClient = session.clone().into();