Move adapters to remote (#18359)

Conrad Irwin and Mikayla created

Release Notes:

- ssh remoting: run LSP Adapters on host

---------

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

Change summary

Cargo.lock                                   |   1 
crates/languages/Cargo.toml                  |  54 +
crates/languages/src/lib.rs                  |  17 
crates/project/src/lsp_store.rs              | 442 ---------------------
crates/project/src/project.rs                |   3 
crates/proto/proto/zed.proto                 |  74 ---
crates/proto/src/proto.rs                    |  18 
crates/remote_server/Cargo.toml              |   1 
crates/remote_server/src/headless_project.rs |  12 
crates/zed/Cargo.toml                        |   2 
10 files changed, 76 insertions(+), 548 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9122,6 +9122,7 @@ dependencies = [
  "gpui",
  "http_client",
  "language",
+ "languages",
  "log",
  "lsp",
  "node_runtime",

crates/languages/Cargo.toml 🔗

@@ -10,6 +10,25 @@ workspace = true
 
 [features]
 test-support = []
+load-grammars = [
+    "tree-sitter-bash",
+    "tree-sitter-c",
+    "tree-sitter-cpp",
+    "tree-sitter-css",
+    "tree-sitter-go",
+    "tree-sitter-go-mod",
+    "tree-sitter-gowork",
+    "tree-sitter-jsdoc",
+    "tree-sitter-json",
+    "tree-sitter-md",
+    "protols-tree-sitter-proto",
+    "tree-sitter-python",
+    "tree-sitter-regex",
+    "tree-sitter-rust",
+    "tree-sitter-typescript",
+    "tree-sitter-yaml",
+    "tree-sitter"
+]
 
 [dependencies]
 anyhow.workspace = true
@@ -36,25 +55,26 @@ settings.workspace = true
 smol.workspace = true
 task.workspace = true
 toml.workspace = true
-tree-sitter-bash.workspace = true
-tree-sitter-c.workspace = true
-tree-sitter-cpp.workspace = true
-tree-sitter-css.workspace = true
-tree-sitter-go.workspace = true
-tree-sitter-go-mod.workspace = true
-tree-sitter-gowork.workspace = true
-tree-sitter-jsdoc.workspace = true
-tree-sitter-json.workspace = true
-tree-sitter-md.workspace = true
-protols-tree-sitter-proto.workspace = true
-tree-sitter-python.workspace = true
-tree-sitter-regex.workspace = true
-tree-sitter-rust.workspace = true
-tree-sitter-typescript.workspace = true
-tree-sitter-yaml.workspace = true
-tree-sitter.workspace = true
 util.workspace = true
 
+tree-sitter-bash = {workspace = true, optional = true}
+tree-sitter-c = {workspace = true, optional = true}
+tree-sitter-cpp = {workspace = true, optional = true}
+tree-sitter-css = {workspace = true, optional = true}
+tree-sitter-go = {workspace = true, optional = true}
+tree-sitter-go-mod = {workspace = true, optional = true}
+tree-sitter-gowork = {workspace = true, optional = true}
+tree-sitter-jsdoc = {workspace = true, optional = true}
+tree-sitter-json = {workspace = true, optional = true}
+tree-sitter-md = {workspace = true, optional = true}
+protols-tree-sitter-proto = {workspace = true, optional = true}
+tree-sitter-python = {workspace = true, optional = true}
+tree-sitter-regex = {workspace = true, optional = true}
+tree-sitter-rust = {workspace = true, optional = true}
+tree-sitter-typescript = {workspace = true, optional = true}
+tree-sitter-yaml = {workspace = true, optional = true}
+tree-sitter = {workspace = true, optional = true}
+
 [dev-dependencies]
 text.workspace = true
 theme = { workspace = true, features = ["test-support"] }

crates/languages/src/lib.rs 🔗

@@ -31,6 +31,7 @@ mod yaml;
 struct LanguageDir;
 
 pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut AppContext) {
+    #[cfg(feature = "load-grammars")]
     languages.register_native_grammars([
         ("bash", tree_sitter_bash::LANGUAGE),
         ("c", tree_sitter_c::LANGUAGE),
@@ -282,9 +283,21 @@ fn load_config(name: &str) -> LanguageConfig {
     )
     .unwrap();
 
-    ::toml::from_str(&config_toml)
+    #[allow(unused_mut)]
+    let mut config: LanguageConfig = ::toml::from_str(&config_toml)
         .with_context(|| format!("failed to load config.toml for language {name:?}"))
-        .unwrap()
+        .unwrap();
+
+    #[cfg(not(feature = "load-grammars"))]
+    {
+        config = LanguageConfig {
+            name: config.name,
+            matcher: config.matcher,
+            ..Default::default()
+        }
+    }
+
+    config
 }
 
 fn load_queries(name: &str) -> LanguageQueries {

crates/project/src/lsp_store.rs 🔗

@@ -36,10 +36,10 @@ use language::{
     markdown, point_to_lsp, prepare_completion_documentation,
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
-    DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig,
-    LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus,
-    LanguageServerName, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
-    TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
+    DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
+    LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
+    LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
+    Unclipped,
 };
 use lsp::{
     CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
@@ -53,7 +53,7 @@ use parking_lot::{Mutex, RwLock};
 use postage::watch;
 use rand::prelude::*;
 
-use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient};
+use rpc::AnyProtoClient;
 use serde::Serialize;
 use settings::{Settings, SettingsLocation, SettingsStore};
 use sha2::{Digest, Sha256};
@@ -644,16 +644,15 @@ pub struct RemoteLspStore {
 
 impl RemoteLspStore {}
 
-pub struct SshLspStore {
-    upstream_client: AnyProtoClient,
-    current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
-}
+// pub struct SshLspStore {
+//     upstream_client: AnyProtoClient,
+//     current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
+// }
 
 #[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 {
@@ -661,10 +660,6 @@ impl LspStoreMode {
         matches!(self, LspStoreMode::Local(_))
     }
 
-    fn is_ssh(&self) -> bool {
-        matches!(self, LspStoreMode::Ssh(_))
-    }
-
     fn is_remote(&self) -> bool {
         matches!(self, LspStoreMode::Remote(_))
     }
@@ -787,13 +782,6 @@ impl LspStore {
         }
     }
 
-    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),
@@ -810,9 +798,6 @@ impl LspStore {
 
     pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
         match &self.mode {
-            LspStoreMode::Ssh(SshLspStore {
-                upstream_client, ..
-            }) => Some((upstream_client.clone(), SSH_PROJECT_ID)),
             LspStoreMode::Remote(RemoteLspStore {
                 upstream_client,
                 upstream_project_id,
@@ -827,11 +812,7 @@ impl LspStore {
         new_settings: HashMap<LanguageServerName, LspSettings>,
     ) -> Option<HashMap<LanguageServerName, LspSettings>> {
         match &mut self.mode {
-            LspStoreMode::Ssh(SshLspStore {
-                current_lsp_settings,
-                ..
-            })
-            | LspStoreMode::Local(LocalLspStore {
+            LspStoreMode::Local(LocalLspStore {
                 current_lsp_settings,
                 ..
             }) => {
@@ -919,43 +900,6 @@ impl LspStore {
         })
     }
 
-    pub fn new_ssh(
-        buffer_store: Model<BufferStore>,
-        worktree_store: Model<WorktreeStore>,
-        languages: Arc<LanguageRegistry>,
-        upstream_client: AnyProtoClient,
-        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();
-        cx.observe_global::<SettingsStore>(Self::on_settings_changed)
-            .detach();
-
-        Self {
-            mode: LspStoreMode::Ssh(SshLspStore {
-                upstream_client,
-                current_lsp_settings: Default::default(),
-            }),
-            downstream_client: None,
-            buffer_store,
-            worktree_store,
-            languages: languages.clone(),
-            language_server_ids: Default::default(),
-            language_server_statuses: Default::default(),
-            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),
-        }
-    }
-
     pub fn new_remote(
         buffer_store: Model<BufferStore>,
         worktree_store: Model<WorktreeStore>,
@@ -3697,11 +3641,11 @@ impl LspStore {
         mut cx: AsyncAppContext,
     ) -> Result<proto::MultiLspQueryResponse> {
         let response_from_ssh = this.update(&mut cx, |this, _| {
-            let ssh = this.as_ssh()?;
+            let (upstream_client, project_id) = this.upstream_client()?;
             let mut payload = envelope.payload.clone();
-            payload.project_id = SSH_PROJECT_ID;
+            payload.project_id = project_id;
 
-            Some(ssh.upstream_client.request(payload))
+            Some(upstream_client.request(payload))
         })?;
         if let Some(response_from_ssh) = response_from_ssh {
             return response_from_ssh.await;
@@ -5009,165 +4953,6 @@ impl LspStore {
         Ok(proto::Ack {})
     }
 
-    pub async fn handle_create_language_server(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::CreateLanguageServer>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-        let server_name = LanguageServerName::from_proto(envelope.payload.name);
-
-        let binary = envelope
-            .payload
-            .binary
-            .ok_or_else(|| anyhow!("missing binary"))?;
-        let binary = LanguageServerBinary {
-            path: PathBuf::from(binary.path),
-            env: None,
-            arguments: binary.arguments.into_iter().map(Into::into).collect(),
-        };
-        let language = envelope
-            .payload
-            .language
-            .ok_or_else(|| anyhow!("missing language"))?;
-        let language_name = LanguageName::from_proto(language.name);
-        let matcher: LanguageMatcher = serde_json::from_str(&language.matcher)?;
-
-        this.update(&mut cx, |this, cx| {
-            let Some(worktree) = this
-                .worktree_store
-                .read(cx)
-                .worktree_for_id(worktree_id, cx)
-            else {
-                return Err(anyhow!("worktree not found"));
-            };
-
-            this.languages
-                .register_language(language_name.clone(), None, matcher.clone(), {
-                    let language_name = language_name.clone();
-                    move || {
-                        Ok((
-                            LanguageConfig {
-                                name: language_name.clone(),
-                                matcher: matcher.clone(),
-                                ..Default::default()
-                            },
-                            Default::default(),
-                            Default::default(),
-                        ))
-                    }
-                });
-            cx.background_executor()
-                .spawn(this.languages.language_for_name(language_name.0.as_ref()))
-                .detach();
-
-            // host
-            let adapter = this.languages.get_or_register_lsp_adapter(
-                language_name.clone(),
-                server_name.clone(),
-                || {
-                    Arc::new(SshLspAdapter::new(
-                        server_name,
-                        binary,
-                        envelope.payload.initialization_options,
-                        envelope.payload.code_action_kinds,
-                    ))
-                },
-            );
-
-            this.start_language_server(&worktree, adapter, language_name, cx);
-            Ok(())
-        })??;
-        Ok(proto::Ack {})
-    }
-
-    pub async fn handle_which_command(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::WhichCommand>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::WhichCommandResponse> {
-        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-        let command = PathBuf::from(envelope.payload.command);
-        let response = this
-            .update(&mut cx, |this, cx| {
-                let worktree = this.worktree_for_id(worktree_id, cx)?;
-                let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
-                anyhow::Ok(
-                    cx.spawn(|_, _| async move { delegate.which(command.as_os_str()).await }),
-                )
-            })??
-            .await;
-
-        Ok(proto::WhichCommandResponse {
-            path: response.map(|path| path.to_string_lossy().to_string()),
-        })
-    }
-
-    pub async fn handle_shell_env(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ShellEnv>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ShellEnvResponse> {
-        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-        let response = this
-            .update(&mut cx, |this, cx| {
-                let worktree = this.worktree_for_id(worktree_id, cx)?;
-                let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
-                anyhow::Ok(cx.spawn(|_, _| async move { delegate.shell_env().await }))
-            })??
-            .await;
-
-        Ok(proto::ShellEnvResponse {
-            env: response.into_iter().collect(),
-        })
-    }
-    pub async fn handle_try_exec(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::TryExec>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::Ack> {
-        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-        let binary = envelope
-            .payload
-            .binary
-            .ok_or_else(|| anyhow!("missing binary"))?;
-        let binary = LanguageServerBinary {
-            path: PathBuf::from(binary.path),
-            env: None,
-            arguments: binary.arguments.into_iter().map(Into::into).collect(),
-        };
-        this.update(&mut cx, |this, cx| {
-            let worktree = this.worktree_for_id(worktree_id, cx)?;
-            let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
-            anyhow::Ok(cx.spawn(|_, _| async move { delegate.try_exec(binary).await }))
-        })??
-        .await?;
-
-        Ok(proto::Ack {})
-    }
-
-    pub async fn handle_read_text_file(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::ReadTextFile>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ReadTextFileResponse> {
-        let path = envelope
-            .payload
-            .path
-            .ok_or_else(|| anyhow!("missing path"))?;
-        let worktree_id = WorktreeId::from_proto(path.worktree_id);
-        let path = PathBuf::from(path.path);
-        let response = this
-            .update(&mut cx, |this, cx| {
-                let worktree = this.worktree_for_id(worktree_id, cx)?;
-                let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
-                anyhow::Ok(cx.spawn(|_, _| async move { delegate.read_text_file(path).await }))
-            })??
-            .await?;
-
-        Ok(proto::ReadTextFileResponse { text: response })
-    }
-
     async fn handle_apply_additional_edits_for_completion(
         this: Model<Self>,
         envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
@@ -5388,89 +5173,6 @@ impl LspStore {
             .reorder_language_servers(&language, enabled_lsp_adapters);
     }
 
-    fn start_language_server_on_ssh_host(
-        &mut self,
-        worktree: &Model<Worktree>,
-        adapter: Arc<CachedLspAdapter>,
-        language: LanguageName,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let ssh = self.as_ssh().unwrap();
-
-        let delegate = Arc::new(SshLspAdapterDelegate {
-            lsp_store: cx.handle().downgrade(),
-            worktree: worktree.read(cx).snapshot(),
-            upstream_client: ssh.upstream_client.clone(),
-            language_registry: self.languages.clone(),
-        }) as Arc<dyn LspAdapterDelegate>;
-
-        let Some((upstream_client, project_id)) = self.upstream_client() else {
-            return;
-        };
-        let worktree_id = worktree.read(cx).id().to_proto();
-        let name = adapter.name().to_string();
-
-        let Some(available_language) = self.languages.available_language_for_name(&language) else {
-            log::error!("failed to find available language {language}");
-            return;
-        };
-
-        let user_binary_task =
-            self.get_language_server_binary(adapter.clone(), delegate.clone(), false, cx);
-
-        let task = cx.spawn(|_, _| async move {
-            let binary = user_binary_task.await?;
-            let name = adapter.name();
-            let code_action_kinds = adapter
-                .adapter
-                .code_action_kinds()
-                .map(|kinds| serde_json::to_string(&kinds))
-                .transpose()?;
-            let get_options = adapter.adapter.clone().initialization_options(&delegate);
-            let initialization_options = get_options
-                .await?
-                .map(|options| serde_json::to_string(&options))
-                .transpose()?;
-
-            let language_server_command = proto::LanguageServerCommand {
-                path: binary.path.to_string_lossy().to_string(),
-                arguments: binary
-                    .arguments
-                    .iter()
-                    .map(|args| args.to_string_lossy().to_string())
-                    .collect(),
-                env: binary.env.unwrap_or_default().into_iter().collect(),
-            };
-
-            upstream_client
-                .request(proto::CreateLanguageServer {
-                    project_id,
-                    worktree_id,
-                    name: name.0.to_string(),
-                    binary: Some(language_server_command),
-                    initialization_options,
-                    code_action_kinds,
-                    language: Some(proto::AvailableLanguage {
-                        name: language.to_proto(),
-                        matcher: serde_json::to_string(&available_language.matcher())?,
-                    }),
-                })
-                .await
-        });
-        cx.spawn(|this, mut cx| async move {
-            if let Err(e) = task.await {
-                this.update(&mut cx, |_this, cx| {
-                    cx.emit(LspStoreEvent::Notification(format!(
-                        "failed to start {}: {}",
-                        name, e
-                    )))
-                })
-                .ok();
-            }
-        })
-        .detach();
-    }
-
     fn get_language_server_binary(
         &self,
         adapter: Arc<CachedLspAdapter>,
@@ -5558,11 +5260,6 @@ impl LspStore {
             return;
         }
 
-        if self.mode.is_ssh() {
-            self.start_language_server_on_ssh_host(worktree_handle, adapter, language, cx);
-            return;
-        }
-
         let project_settings = ProjectSettings::get(
             Some(SettingsLocation {
                 worktree_id,
@@ -5852,9 +5549,6 @@ impl LspStore {
             } else {
                 Task::ready(Vec::new())
             }
-        } else if self.mode.is_ssh() {
-            // TODO ssh
-            Task::ready(Vec::new())
         } else {
             Task::ready(Vec::new())
         }
@@ -7905,116 +7599,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
     }
 }
 
-struct SshLspAdapterDelegate {
-    lsp_store: WeakModel<LspStore>,
-    worktree: worktree::Snapshot,
-    upstream_client: AnyProtoClient,
-    language_registry: Arc<LanguageRegistry>,
-}
-
-#[async_trait]
-impl LspAdapterDelegate for SshLspAdapterDelegate {
-    fn show_notification(&self, message: &str, cx: &mut AppContext) {
-        self.lsp_store
-            .update(cx, |_, cx| {
-                cx.emit(LspStoreEvent::Notification(message.to_owned()))
-            })
-            .ok();
-    }
-
-    async fn npm_package_installed_version(
-        &self,
-        _package_name: &str,
-    ) -> Result<Option<(PathBuf, String)>> {
-        Ok(None)
-    }
-
-    fn http_client(&self) -> Arc<dyn HttpClient> {
-        Arc::new(BlockedHttpClient)
-    }
-
-    fn worktree_id(&self) -> WorktreeId {
-        self.worktree.id()
-    }
-
-    fn worktree_root_path(&self) -> &Path {
-        self.worktree.abs_path().as_ref()
-    }
-
-    async fn shell_env(&self) -> HashMap<String, String> {
-        use rpc::proto::SSH_PROJECT_ID;
-
-        self.upstream_client
-            .request(proto::ShellEnv {
-                project_id: SSH_PROJECT_ID,
-                worktree_id: self.worktree_id().to_proto(),
-            })
-            .await
-            .map(|response| response.env.into_iter().collect())
-            .unwrap_or_default()
-    }
-
-    async fn which(&self, command: &OsStr) -> Option<PathBuf> {
-        use rpc::proto::SSH_PROJECT_ID;
-
-        self.upstream_client
-            .request(proto::WhichCommand {
-                project_id: SSH_PROJECT_ID,
-                worktree_id: self.worktree_id().to_proto(),
-                command: command.to_string_lossy().to_string(),
-            })
-            .await
-            .log_err()
-            .and_then(|response| response.path)
-            .map(PathBuf::from)
-    }
-
-    async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
-        self.upstream_client
-            .request(proto::TryExec {
-                project_id: rpc::proto::SSH_PROJECT_ID,
-                worktree_id: self.worktree.id().to_proto(),
-                binary: Some(proto::LanguageServerCommand {
-                    path: command.path.to_string_lossy().to_string(),
-                    arguments: command
-                        .arguments
-                        .into_iter()
-                        .map(|s| s.to_string_lossy().to_string())
-                        .collect(),
-                    env: command.env.unwrap_or_default().into_iter().collect(),
-                }),
-            })
-            .await?;
-        Ok(())
-    }
-
-    async fn language_server_download_dir(&self, _: &LanguageServerName) -> Option<Arc<Path>> {
-        None
-    }
-
-    fn update_status(
-        &self,
-        server_name: LanguageServerName,
-        status: language::LanguageServerBinaryStatus,
-    ) {
-        self.language_registry
-            .update_lsp_status(server_name, status);
-    }
-
-    async fn read_text_file(&self, path: PathBuf) -> Result<String> {
-        self.upstream_client
-            .request(proto::ReadTextFile {
-                project_id: rpc::proto::SSH_PROJECT_ID,
-                path: Some(proto::ProjectPath {
-                    worktree_id: self.worktree.id().to_proto(),
-                    path: path.to_string_lossy().to_string(),
-                }),
-            })
-            .await
-            .map(|r| r.text)
-    }
-}
-
 async fn populate_labels_for_symbols(
     symbols: Vec<CoreSymbol>,
     language_registry: &Arc<LanguageRegistry>,

crates/project/src/project.rs 🔗

@@ -706,11 +706,12 @@ impl Project {
 
             let environment = ProjectEnvironment::new(&worktree_store, None, cx);
             let lsp_store = cx.new_model(|cx| {
-                LspStore::new_ssh(
+                LspStore::new_remote(
                     buffer_store.clone(),
                     worktree_store.clone(),
                     languages.clone(),
                     ssh.clone().into(),
+                    SSH_PROJECT_ID,
                     cx,
                 )
             });

crates/proto/proto/zed.proto 🔗

@@ -283,18 +283,6 @@ message Envelope {
         CloseBuffer close_buffer = 245;
         UpdateUserSettings update_user_settings = 246;
 
-        CreateLanguageServer create_language_server = 247;
-
-        WhichCommand which_command = 248;
-        WhichCommandResponse which_command_response = 249;
-
-        ShellEnv shell_env = 250;
-        ShellEnvResponse shell_env_response = 251;
-
-        TryExec try_exec = 252;
-        ReadTextFile read_text_file = 253;
-        ReadTextFileResponse read_text_file_response = 254;
-
         CheckFileExists check_file_exists = 255;
         CheckFileExistsResponse check_file_exists_response = 256; // current max
     }
@@ -302,6 +290,7 @@ message Envelope {
     reserved 158 to 161;
     reserved 166 to 169;
     reserved 224 to 229;
+    reserved 247 to 254;
 }
 
 // Messages
@@ -2517,67 +2506,6 @@ message UpdateUserSettings {
     string content = 2;
 }
 
-message LanguageServerCommand {
-   string path = 1;
-   repeated string arguments = 2;
-   map<string, string> env = 3;
-}
-
-message AvailableLanguage {
-    string name = 7;
-    string matcher = 8;
-}
-
-message CreateLanguageServer {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    string name = 3;
-
-    LanguageServerCommand binary = 4;
-    optional string initialization_options = 5;
-    optional string code_action_kinds = 6;
-
-    AvailableLanguage language = 7;
-}
-
-message WhichCommand {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    string command = 3;
-}
-
-message WhichCommandResponse {
-    optional string path = 1;
-}
-
-message ShellEnv {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-}
-
-message ShellEnvResponse {
-    map<string, string> env = 1;
-}
-
-message ReadTextFile {
-    uint64 project_id = 1;
-    ProjectPath path = 2;
-}
-
-message ReadTextFileResponse {
-    string text = 1;
-}
-
-message TryExec {
-    uint64 project_id = 1;
-    uint64 worktree_id = 2;
-    LanguageServerCommand binary = 3;
-}
-
-message TryExecResponse {
-    string text = 1;
-}
-
 message CheckFileExists {
     uint64 project_id = 1;
     string path = 2;

crates/proto/src/proto.rs 🔗

@@ -365,14 +365,6 @@ messages!(
     (FindSearchCandidatesResponse, Background),
     (CloseBuffer, Foreground),
     (UpdateUserSettings, Foreground),
-    (CreateLanguageServer, Foreground),
-    (WhichCommand, Foreground),
-    (WhichCommandResponse, Foreground),
-    (ShellEnv, Foreground),
-    (ShellEnvResponse, Foreground),
-    (TryExec, Foreground),
-    (ReadTextFile, Foreground),
-    (ReadTextFileResponse, Foreground),
     (CheckFileExists, Background),
     (CheckFileExistsResponse, Background)
 );
@@ -498,11 +490,6 @@ request_messages!(
     (SynchronizeContexts, SynchronizeContextsResponse),
     (LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
     (AddWorktree, AddWorktreeResponse),
-    (CreateLanguageServer, Ack),
-    (WhichCommand, WhichCommandResponse),
-    (ShellEnv, ShellEnvResponse),
-    (ReadTextFile, ReadTextFileResponse),
-    (TryExec, Ack),
     (CheckFileExists, CheckFileExistsResponse)
 );
 
@@ -577,11 +564,6 @@ entity_messages!(
     SynchronizeContexts,
     LspExtSwitchSourceHeader,
     UpdateUserSettings,
-    CreateLanguageServer,
-    WhichCommand,
-    ShellEnv,
-    TryExec,
-    ReadTextFile,
     CheckFileExists,
 );
 

crates/remote_server/Cargo.toml 🔗

@@ -39,6 +39,7 @@ shellexpand.workspace = true
 smol.workspace = true
 worktree.workspace = true
 language.workspace = true
+languages.workspace = true
 util.workspace = true
 
 [dev-dependencies]

crates/remote_server/src/headless_project.rs 🔗

@@ -44,6 +44,10 @@ impl HeadlessProject {
     pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
         let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
 
+        let node_runtime = NodeRuntime::unavailable();
+
+        languages::init(languages.clone(), node_runtime.clone(), cx);
+
         let worktree_store = cx.new_model(|cx| {
             let mut store = WorktreeStore::local(true, fs.clone());
             store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
@@ -56,7 +60,7 @@ impl HeadlessProject {
         });
         let prettier_store = cx.new_model(|cx| {
             PrettierStore::new(
-                NodeRuntime::unavailable(),
+                node_runtime,
                 fs.clone(),
                 languages.clone(),
                 worktree_store.clone(),
@@ -116,12 +120,6 @@ impl HeadlessProject {
         client.add_model_request_handler(BufferStore::handle_update_buffer);
         client.add_model_message_handler(BufferStore::handle_close_buffer);
 
-        client.add_model_request_handler(LspStore::handle_create_language_server);
-        client.add_model_request_handler(LspStore::handle_which_command);
-        client.add_model_request_handler(LspStore::handle_shell_env);
-        client.add_model_request_handler(LspStore::handle_try_exec);
-        client.add_model_request_handler(LspStore::handle_read_text_file);
-
         BufferStore::init(&client);
         WorktreeStore::init(&client);
         SettingsObserver::init(&client);

crates/zed/Cargo.toml 🔗

@@ -64,7 +64,7 @@ language.workspace = true
 language_model.workspace = true
 language_selector.workspace = true
 language_tools.workspace = true
-languages.workspace = true
+languages = {workspace = true, features = ["load-grammars"] }
 libc.workspace = true
 log.workspace = true
 markdown_preview.workspace = true