Remove `server_args` from `LspAdapter`

Julia and Antonio Scandurra created

Prepare to remove concept of a runtime from greater server startup code,
which is important for future language server extensibility

Co-Authored-By: Antonio Scandurra <me@as-cii.com>

Change summary

crates/language/src/language.rs             | 68 +++++++++++-----------
crates/zed/src/languages/c.rs               | 14 +++-
crates/zed/src/languages/elixir.rs          | 15 +++-
crates/zed/src/languages/go.rs              | 27 ++++++---
crates/zed/src/languages/html.rs            | 24 +++++---
crates/zed/src/languages/installation.rs    |  8 +-
crates/zed/src/languages/json.rs            | 26 +++++---
crates/zed/src/languages/language_plugin.rs | 26 ++------
crates/zed/src/languages/lua.rs             | 30 ++++++----
crates/zed/src/languages/python.rs          | 24 +++++---
crates/zed/src/languages/ruby.rs            | 15 ++--
crates/zed/src/languages/rust.rs            | 14 +++-
crates/zed/src/languages/typescript.rs      | 35 +++++++----
crates/zed/src/languages/yaml.rs            | 24 +++++---
14 files changed, 204 insertions(+), 146 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -83,12 +83,17 @@ pub enum ServerExecutionKind {
     Node,
 }
 
+#[derive(Debug, Clone, Deserialize)]
+pub struct LanguageServerBinary {
+    pub path: PathBuf,
+    pub arguments: Vec<String>,
+}
+
 /// Represents a Language Server, with certain cached sync properties.
 /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
 /// once at startup, and caches the results.
 pub struct CachedLspAdapter {
     pub name: LanguageServerName,
-    pub server_args: Vec<String>,
     pub initialization_options: Option<Value>,
     pub disk_based_diagnostic_sources: Vec<String>,
     pub disk_based_diagnostics_progress_token: Option<String>,
@@ -99,7 +104,6 @@ pub struct CachedLspAdapter {
 impl CachedLspAdapter {
     pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
         let name = adapter.name().await;
-        let server_args = adapter.server_args().await;
         let initialization_options = adapter.initialization_options().await;
         let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
         let disk_based_diagnostics_progress_token =
@@ -108,7 +112,6 @@ impl CachedLspAdapter {
 
         Arc::new(CachedLspAdapter {
             name,
-            server_args,
             initialization_options,
             disk_based_diagnostic_sources,
             disk_based_diagnostics_progress_token,
@@ -129,13 +132,16 @@ impl CachedLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         self.adapter
             .fetch_server_binary(version, http, container_dir)
             .await
     }
 
-    pub async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    pub async fn cached_server_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
         self.adapter.cached_server_binary(container_dir).await
     }
 
@@ -190,9 +196,9 @@ pub trait LspAdapter: 'static + Send + Sync {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf>;
+    ) -> Result<LanguageServerBinary>;
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf>;
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary>;
 
     async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
@@ -215,10 +221,6 @@ pub trait LspAdapter: 'static + Send + Sync {
         None
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        Vec::new()
-    }
-
     async fn initialization_options(&self) -> Option<Value> {
         None
     }
@@ -497,7 +499,7 @@ pub struct LanguageRegistry {
     lsp_binary_paths: Mutex<
         HashMap<
             LanguageServerName,
-            Shared<BoxFuture<'static, Result<PathBuf, Arc<anyhow::Error>>>>,
+            Shared<BoxFuture<'static, Result<LanguageServerBinary, Arc<anyhow::Error>>>>,
         >,
     >,
     executor: Option<Arc<Background>>,
@@ -810,12 +812,12 @@ impl LanguageRegistry {
             login_shell_env_loaded.await;
             let node_path = node_path.await;
 
-            let server_binary_path = this
+            let server_binary = this
                 .lsp_binary_paths
                 .lock()
                 .entry(adapter.name.clone())
                 .or_insert_with(|| {
-                    get_server_binary_path(
+                    get_server_binary(
                         adapter.clone(),
                         language.clone(),
                         http_client,
@@ -829,8 +831,9 @@ impl LanguageRegistry {
                 .clone()
                 .map_err(|e| anyhow!(e));
 
-            let server_binary_path = server_binary_path.await?;
-            let server_name = server_binary_path
+            let server_binary = server_binary.await?;
+            let server_name = server_binary
+                .path
                 .file_name()
                 .map(|name| name.to_string_lossy().to_string());
 
@@ -839,16 +842,15 @@ impl LanguageRegistry {
                     let node_path = node_path
                         .ok_or(anyhow!("Missing Node path for Node based language server"))?;
                     let node_binary = node_path.join("bin/node");
-                    dbg!(&node_binary);
                     let mut command = smol::process::Command::new(node_binary);
-                    command.arg(dbg!(server_binary_path));
+                    command.arg(dbg!(server_binary.path));
                     command
                 }
 
-                ServerExecutionKind::Launch => smol::process::Command::new(server_binary_path),
+                ServerExecutionKind::Launch => smol::process::Command::new(server_binary.path),
             };
 
-            command.args(&adapter.server_args);
+            command.args(dbg!(&server_binary.arguments));
             let server = lsp::LanguageServer::new(server_id, server_name, command, &root_path, cx)?;
 
             Ok(server)
@@ -880,13 +882,13 @@ impl Default for LanguageRegistry {
     }
 }
 
-async fn get_server_binary_path(
+async fn get_server_binary(
     adapter: Arc<CachedLspAdapter>,
     language: Arc<Language>,
     http_client: Arc<dyn HttpClient>,
     download_dir: Arc<Path>,
     statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
-) -> Result<PathBuf> {
+) -> Result<LanguageServerBinary> {
     let container_dir = download_dir.join(adapter.name.0.as_ref());
     if !container_dir.exists() {
         smol::fs::create_dir_all(&container_dir)
@@ -894,7 +896,7 @@ async fn get_server_binary_path(
             .context("failed to create container directory")?;
     }
 
-    let path = fetch_latest_server_binary_path(
+    let binary = fetch_latest_server_binary(
         adapter.clone(),
         language.clone(),
         http_client,
@@ -902,12 +904,12 @@ async fn get_server_binary_path(
         statuses.clone(),
     )
     .await;
-    if let Err(error) = path.as_ref() {
-        if let Some(cached_path) = adapter.cached_server_binary(container_dir).await {
+    if let Err(error) = binary.as_ref() {
+        if let Some(cached) = adapter.cached_server_binary(container_dir).await {
             statuses
                 .broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
                 .await?;
-            return Ok(cached_path);
+            return Ok(cached);
         } else {
             statuses
                 .broadcast((
@@ -919,16 +921,16 @@ async fn get_server_binary_path(
                 .await?;
         }
     }
-    path
+    binary
 }
 
-async fn fetch_latest_server_binary_path(
+async fn fetch_latest_server_binary(
     adapter: Arc<CachedLspAdapter>,
     language: Arc<Language>,
     http_client: Arc<dyn HttpClient>,
     container_dir: &Path,
     lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
-) -> Result<PathBuf> {
+) -> Result<LanguageServerBinary> {
     let container_dir: Arc<Path> = container_dir.into();
     lsp_binary_statuses_tx
         .broadcast((
@@ -942,13 +944,13 @@ async fn fetch_latest_server_binary_path(
     lsp_binary_statuses_tx
         .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
         .await?;
-    let path = adapter
+    let binary = adapter
         .fetch_server_binary(version_info, http_client, container_dir.to_path_buf())
         .await?;
     lsp_binary_statuses_tx
         .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
         .await?;
-    Ok(path)
+    Ok(binary)
 }
 
 impl Language {
@@ -1485,11 +1487,11 @@ impl LspAdapter for Arc<FakeLspAdapter> {
         _: Box<dyn 'static + Send + Any>,
         _: Arc<dyn HttpClient>,
         _: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         unreachable!();
     }
 
-    async fn cached_server_binary(&self, _: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
         unreachable!();
     }
 

crates/zed/src/languages/c.rs 🔗

@@ -43,7 +43,7 @@ impl super::LspAdapter for CLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
         let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
         let version_dir = container_dir.join(format!("clangd_{}", version.name));
@@ -85,10 +85,13 @@ impl super::LspAdapter for CLspAdapter {
             }
         }
 
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: vec![],
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last_clangd_dir = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -101,7 +104,10 @@ impl super::LspAdapter for CLspAdapter {
             let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
             let clangd_bin = clangd_dir.join("bin/clangd");
             if clangd_bin.exists() {
-                Ok(clangd_bin)
+                Ok(LanguageServerBinary {
+                    path: clangd_bin,
+                    arguments: vec![],
+                })
             } else {
                 Err(anyhow!(
                     "missing clangd binary in directory {:?}",

crates/zed/src/languages/elixir.rs 🔗

@@ -44,7 +44,7 @@ impl LspAdapter for ElixirLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
         let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
         let version_dir = container_dir.join(format!("elixir-ls_{}", version.name));
@@ -98,17 +98,24 @@ impl LspAdapter for ElixirLspAdapter {
             }
         }
 
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: vec![],
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last = None;
             let mut entries = fs::read_dir(&container_dir).await?;
             while let Some(entry) = entries.next().await {
                 last = Some(entry?.path());
             }
-            last.ok_or_else(|| anyhow!("no cached binary"))
+            last.map(|path| LanguageServerBinary {
+                path,
+                arguments: vec![],
+            })
+            .ok_or_else(|| anyhow!("no cached binary"))
         })()
         .await
         .log_err()

crates/zed/src/languages/go.rs 🔗

@@ -10,6 +10,10 @@ use smol::{fs, process};
 use std::{any::Any, ops::Range, path::PathBuf, str, sync::Arc};
 use util::ResultExt;
 
+fn server_binary_arguments() -> Vec<String> {
+    vec!["-mode=stdio".into()]
+}
+
 #[derive(Copy, Clone)]
 pub struct GoLspAdapter;
 
@@ -27,10 +31,6 @@ impl super::LspAdapter for GoLspAdapter {
         ServerExecutionKind::Launch
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec!["-mode=stdio".into()]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -51,7 +51,7 @@ impl super::LspAdapter for GoLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         _: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<Option<String>>().unwrap();
         let this = *self;
 
@@ -72,7 +72,10 @@ impl super::LspAdapter for GoLspAdapter {
                         }
                     }
 
-                    return Ok(binary_path.to_path_buf());
+                    return Ok(LanguageServerBinary {
+                        path: binary_path.to_path_buf(),
+                        arguments: server_binary_arguments(),
+                    });
                 }
             }
         } else if let Some(path) = this.cached_server_binary(container_dir.clone()).await {
@@ -106,10 +109,13 @@ impl super::LspAdapter for GoLspAdapter {
         let binary_path = container_dir.join(&format!("gopls_{version}"));
         fs::rename(&installed_binary_path, &binary_path).await?;
 
-        Ok(binary_path.to_path_buf())
+        Ok(LanguageServerBinary {
+            path: binary_path.to_path_buf(),
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last_binary_path = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -126,7 +132,10 @@ impl super::LspAdapter for GoLspAdapter {
             }
 
             if let Some(path) = last_binary_path {
-                Ok(path)
+                Ok(LanguageServerBinary {
+                    path,
+                    arguments: server_binary_arguments(),
+                })
             } else {
                 Err(anyhow!("no cached binary"))
             }

crates/zed/src/languages/html.rs 🔗

@@ -3,12 +3,16 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client::http::HttpClient;
 use futures::StreamExt;
-use language::{LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind};
 use serde_json::json;
 use smol::fs;
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::ResultExt;
 
+fn server_binary_arguments() -> Vec<String> {
+    vec!["--stdio".into()]
+}
+
 pub struct HtmlLspAdapter;
 
 impl HtmlLspAdapter {
@@ -26,10 +30,6 @@ impl LspAdapter for HtmlLspAdapter {
         ServerExecutionKind::Node
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec!["--stdio".into()]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -45,7 +45,7 @@ impl LspAdapter for HtmlLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<String>().unwrap();
         let version_dir = container_dir.join(version.as_str());
         fs::create_dir_all(&version_dir)
@@ -73,10 +73,13 @@ impl LspAdapter for HtmlLspAdapter {
             }
         }
 
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last_version_dir = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -89,7 +92,10 @@ impl LspAdapter for HtmlLspAdapter {
             let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
             let bin_path = last_version_dir.join(Self::BIN_PATH);
             if bin_path.exists() {
-                Ok(bin_path)
+                Ok(LanguageServerBinary {
+                    path: bin_path,
+                    arguments: server_binary_arguments(),
+                })
             } else {
                 Err(anyhow!(
                     "missing executable in directory {:?}",

crates/zed/src/languages/installation.rs 🔗

@@ -48,8 +48,8 @@ pub async fn ensure_node_installation_dir(http: Arc<dyn HttpClient>) -> Result<P
     let arch = "arm64";
 
     let folder_name = format!("node-{version}-darwin-{arch}");
-    let node_containing_dir = dbg!(util::paths::SUPPORT_DIR.join("node"));
-    let node_dir = dbg!(node_containing_dir.join(folder_name));
+    let node_containing_dir = util::paths::SUPPORT_DIR.join("node");
+    let node_dir = node_containing_dir.join(folder_name);
     let node_binary = node_dir.join("bin/node");
 
     if fs::metadata(&node_binary).await.is_err() {
@@ -59,7 +59,6 @@ pub async fn ensure_node_installation_dir(http: Arc<dyn HttpClient>) -> Result<P
             .context("error creating node containing dir")?;
 
         let url = format!("https://nodejs.org/dist/{version}/node-{version}-darwin-{arch}.tar.gz");
-        dbg!(&url);
         let mut response = http
             .get(&url, Default::default(), true)
             .await
@@ -71,8 +70,7 @@ pub async fn ensure_node_installation_dir(http: Arc<dyn HttpClient>) -> Result<P
         eprintln!("unpacked");
     }
 
-    eprintln!("returning");
-    Ok(dbg!(node_dir))
+    Ok(node_dir)
 }
 
 pub async fn npm_package_latest_version(http: Arc<dyn HttpClient>, name: &str) -> Result<String> {

crates/zed/src/languages/json.rs 🔗

@@ -6,7 +6,9 @@ use client::http::HttpClient;
 use collections::HashMap;
 use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt};
 use gpui::MutableAppContext;
-use language::{LanguageRegistry, LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{
+    LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind,
+};
 use serde_json::json;
 use settings::{keymap_file_json_schema, settings_file_json_schema};
 use smol::fs::{self, File};
@@ -20,6 +22,10 @@ use std::{
 use theme::ThemeRegistry;
 use util::{paths, ResultExt, StaffMode};
 
+fn server_binary_arguments() -> Vec<String> {
+    vec!["--stdio".into()]
+}
+
 pub struct JsonLspAdapter {
     languages: Arc<LanguageRegistry>,
     themes: Arc<ThemeRegistry>,
@@ -41,10 +47,6 @@ impl LspAdapter for JsonLspAdapter {
         ServerExecutionKind::Node
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec!["--stdio".into()]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -68,7 +70,7 @@ impl LspAdapter for JsonLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
         let destination_path = container_dir.join(format!(
             "json-language-server-{}-{}",
@@ -102,17 +104,23 @@ impl LspAdapter for JsonLspAdapter {
             }
         }
 
-        Ok(destination_path)
+        Ok(LanguageServerBinary {
+            path: destination_path,
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last = None;
             let mut entries = fs::read_dir(&container_dir).await?;
             while let Some(entry) = entries.next().await {
                 last = Some(entry?.path());
             }
-            last.ok_or_else(|| anyhow!("no cached binary"))
+            anyhow::Ok(LanguageServerBinary {
+                path: last.ok_or_else(|| anyhow!("no cached binary"))?,
+                arguments: server_binary_arguments(),
+            })
         })()
         .await
         .log_err()

crates/zed/src/languages/language_plugin.rs 🔗

@@ -4,7 +4,7 @@ use client::http::HttpClient;
 use collections::HashMap;
 use futures::lock::Mutex;
 use gpui::executor::Background;
-use language::{LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind};
 use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::ResultExt;
@@ -33,10 +33,9 @@ pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
 pub struct PluginLspAdapter {
     name: WasiFn<(), String>,
     server_execution_kind: WasiFn<(), ServerExecutionKind>,
-    server_args: WasiFn<(), Vec<String>>,
     fetch_latest_server_version: WasiFn<(), Option<String>>,
-    fetch_server_binary: WasiFn<(PathBuf, String), Result<PathBuf, String>>,
-    cached_server_binary: WasiFn<PathBuf, Option<PathBuf>>,
+    fetch_server_binary: WasiFn<(PathBuf, String), Result<LanguageServerBinary, String>>,
+    cached_server_binary: WasiFn<PathBuf, Option<LanguageServerBinary>>,
     initialization_options: WasiFn<(), String>,
     language_ids: WasiFn<(), Vec<(String, String)>>,
     executor: Arc<Background>,
@@ -49,7 +48,6 @@ impl PluginLspAdapter {
         Ok(Self {
             name: plugin.function("name")?,
             server_execution_kind: plugin.function("server_execution_kind")?,
-            server_args: plugin.function("server_args")?,
             fetch_latest_server_version: plugin.function("fetch_latest_server_version")?,
             fetch_server_binary: plugin.function("fetch_server_binary")?,
             cached_server_binary: plugin.function("cached_server_binary")?,
@@ -83,15 +81,6 @@ impl LspAdapter for PluginLspAdapter {
             .unwrap()
     }
 
-    async fn server_args<'a>(&'a self) -> Vec<String> {
-        self.runtime
-            .lock()
-            .await
-            .call(&self.server_args, ())
-            .await
-            .unwrap()
-    }
-
     async fn fetch_latest_server_version(
         &self,
         _: Arc<dyn HttpClient>,
@@ -116,7 +105,7 @@ impl LspAdapter for PluginLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         _: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = *version.downcast::<String>().unwrap();
         let runtime = self.runtime.clone();
         let function = self.fetch_server_binary;
@@ -124,7 +113,7 @@ impl LspAdapter for PluginLspAdapter {
             .spawn(async move {
                 let mut runtime = runtime.lock().await;
                 let handle = runtime.attach_path(&container_dir)?;
-                let result: Result<PathBuf, String> =
+                let result: Result<LanguageServerBinary, String> =
                     runtime.call(&function, (container_dir, version)).await?;
                 runtime.remove_resource(handle)?;
                 result.map_err(|e| anyhow!("{}", e))
@@ -132,7 +121,7 @@ impl LspAdapter for PluginLspAdapter {
             .await
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         let runtime = self.runtime.clone();
         let function = self.cached_server_binary;
 
@@ -140,7 +129,8 @@ impl LspAdapter for PluginLspAdapter {
             .spawn(async move {
                 let mut runtime = runtime.lock().await;
                 let handle = runtime.attach_path(&container_dir).ok()?;
-                let result: Option<PathBuf> = runtime.call(&function, container_dir).await.ok()?;
+                let result: Option<LanguageServerBinary> =
+                    runtime.call(&function, container_dir).await.ok()?;
                 runtime.remove_resource(handle).ok()?;
                 result
             })

crates/zed/src/languages/lua.rs 🔗

@@ -6,7 +6,7 @@ use async_tar::Archive;
 use async_trait::async_trait;
 use client::http::HttpClient;
 use futures::{io::BufReader, StreamExt};
-use language::{LanguageServerName, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, ServerExecutionKind};
 use smol::fs;
 use util::{async_iife, ResultExt};
 
@@ -15,6 +15,13 @@ use super::installation::{latest_github_release, GitHubLspBinaryVersion};
 #[derive(Copy, Clone)]
 pub struct LuaLspAdapter;
 
+fn server_binary_arguments() -> Vec<String> {
+    vec![
+        "--logpath=~/lua-language-server.log".into(),
+        "--loglevel=trace".into(),
+    ]
+}
+
 #[async_trait]
 impl super::LspAdapter for LuaLspAdapter {
     async fn name(&self) -> LanguageServerName {
@@ -25,13 +32,6 @@ impl super::LspAdapter for LuaLspAdapter {
         ServerExecutionKind::Launch
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec![
-            "--logpath=~/lua-language-server.log".into(),
-            "--loglevel=trace".into(),
-        ]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -61,7 +61,7 @@ impl super::LspAdapter for LuaLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
 
         let binary_path = container_dir.join("bin/lua-language-server");
@@ -81,10 +81,13 @@ impl super::LspAdapter for LuaLspAdapter {
             <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
         )
         .await?;
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         async_iife!({
             let mut last_binary_path = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -101,7 +104,10 @@ impl super::LspAdapter for LuaLspAdapter {
             }
 
             if let Some(path) = last_binary_path {
-                Ok(path)
+                Ok(LanguageServerBinary {
+                    path,
+                    arguments: server_binary_arguments(),
+                })
             } else {
                 Err(anyhow!("no cached binary"))
             }

crates/zed/src/languages/python.rs 🔗

@@ -3,13 +3,17 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client::http::HttpClient;
 use futures::StreamExt;
-use language::{LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind};
 use smol::fs;
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::ResultExt;
 
 pub struct PythonLspAdapter;
 
+fn server_binary_arguments() -> Vec<String> {
+    vec!["--stdio".into()]
+}
+
 impl PythonLspAdapter {
     const BIN_PATH: &'static str = "node_modules/pyright/langserver.index.js";
 }
@@ -24,10 +28,6 @@ impl LspAdapter for PythonLspAdapter {
         ServerExecutionKind::Node
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec!["--stdio".into()]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -40,7 +40,7 @@ impl LspAdapter for PythonLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<String>().unwrap();
         let version_dir = container_dir.join(version.as_str());
         fs::create_dir_all(&version_dir)
@@ -63,10 +63,13 @@ impl LspAdapter for PythonLspAdapter {
             }
         }
 
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last_version_dir = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -79,7 +82,10 @@ impl LspAdapter for PythonLspAdapter {
             let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
             let bin_path = last_version_dir.join(Self::BIN_PATH);
             if bin_path.exists() {
-                Ok(bin_path)
+                Ok(LanguageServerBinary {
+                    path: bin_path,
+                    arguments: server_binary_arguments(),
+                })
             } else {
                 Err(anyhow!(
                     "missing executable in directory {:?}",

crates/zed/src/languages/ruby.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use client::http::HttpClient;
-use language::{LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind};
 use std::{any::Any, path::PathBuf, sync::Arc};
 
 pub struct RubyLanguageServer;
@@ -16,10 +16,6 @@ impl LspAdapter for RubyLanguageServer {
         ServerExecutionKind::Launch
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec!["stdio".into()]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         _: Arc<dyn HttpClient>,
@@ -32,12 +28,15 @@ impl LspAdapter for RubyLanguageServer {
         _version: Box<dyn 'static + Send + Any>,
         _: Arc<dyn HttpClient>,
         _container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         Err(anyhow!("solargraph must be installed manually"))
     }
 
-    async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option<PathBuf> {
-        Some("solargraph".into())
+    async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option<LanguageServerBinary> {
+        Some(LanguageServerBinary {
+            path: "solargraph".into(),
+            arguments: vec!["stdio".into()],
+        })
     }
 
     async fn label_for_completion(

crates/zed/src/languages/rust.rs 🔗

@@ -46,7 +46,7 @@ impl LspAdapter for RustLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
         let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));
 
@@ -76,17 +76,23 @@ impl LspAdapter for RustLspAdapter {
             }
         }
 
-        Ok(destination_path)
+        Ok(LanguageServerBinary {
+            path: destination_path,
+            arguments: Default::default(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last = None;
             let mut entries = fs::read_dir(&container_dir).await?;
             while let Some(entry) = entries.next().await {
                 last = Some(entry?.path());
             }
-            last.ok_or_else(|| anyhow!("no cached binary"))
+            anyhow::Ok(LanguageServerBinary {
+                path: last.ok_or_else(|| anyhow!("no cached binary"))?,
+                arguments: Default::default(),
+            })
         })()
         .await
         .log_err()

crates/zed/src/languages/typescript.rs 🔗

@@ -3,12 +3,19 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client::http::HttpClient;
 use futures::StreamExt;
-use language::{LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind};
 use serde_json::json;
 use smol::fs;
 use std::{any::Any, path::PathBuf, sync::Arc};
 use util::ResultExt;
 
+fn server_binary_arguments() -> Vec<String> {
+    ["--stdio", "--tsserver-path", "node_modules/typescript/lib"]
+        .into_iter()
+        .map(Into::into)
+        .collect()
+}
+
 pub struct TypeScriptLspAdapter;
 
 impl TypeScriptLspAdapter {
@@ -31,13 +38,6 @@ impl LspAdapter for TypeScriptLspAdapter {
         ServerExecutionKind::Node
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        ["--stdio", "--tsserver-path", "node_modules/typescript/lib"]
-            .into_iter()
-            .map(str::to_string)
-            .collect()
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -53,7 +53,7 @@ impl LspAdapter for TypeScriptLspAdapter {
         versions: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let versions = versions.downcast::<Versions>().unwrap();
         let version_dir = container_dir.join(&format!(
             "typescript-{}:server-{}",
@@ -90,10 +90,13 @@ impl LspAdapter for TypeScriptLspAdapter {
             }
         }
 
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last_version_dir = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -107,9 +110,15 @@ impl LspAdapter for TypeScriptLspAdapter {
             let old_bin_path = last_version_dir.join(Self::OLD_BIN_PATH);
             let new_bin_path = last_version_dir.join(Self::NEW_BIN_PATH);
             if new_bin_path.exists() {
-                Ok(new_bin_path)
+                Ok(LanguageServerBinary {
+                    path: new_bin_path,
+                    arguments: server_binary_arguments(),
+                })
             } else if old_bin_path.exists() {
-                Ok(old_bin_path)
+                Ok(LanguageServerBinary {
+                    path: old_bin_path,
+                    arguments: server_binary_arguments(),
+                })
             } else {
                 Err(anyhow!(
                     "missing executable in directory {:?}",

crates/zed/src/languages/yaml.rs 🔗

@@ -3,7 +3,7 @@ use async_trait::async_trait;
 use client::http::HttpClient;
 use futures::{future::BoxFuture, FutureExt, StreamExt};
 use gpui::MutableAppContext;
-use language::{LanguageServerName, LspAdapter, ServerExecutionKind};
+use language::{LanguageServerBinary, LanguageServerName, LspAdapter, ServerExecutionKind};
 use serde_json::Value;
 use settings::Settings;
 use smol::fs;
@@ -12,6 +12,10 @@ use util::ResultExt;
 
 use super::installation::{npm_install_packages, npm_package_latest_version};
 
+fn server_binary_arguments() -> Vec<String> {
+    vec!["--stdio".into()]
+}
+
 pub struct YamlLspAdapter;
 
 impl YamlLspAdapter {
@@ -28,10 +32,6 @@ impl LspAdapter for YamlLspAdapter {
         ServerExecutionKind::Node
     }
 
-    async fn server_args(&self) -> Vec<String> {
-        vec!["--stdio".into()]
-    }
-
     async fn fetch_latest_server_version(
         &self,
         http: Arc<dyn HttpClient>,
@@ -44,7 +44,7 @@ impl LspAdapter for YamlLspAdapter {
         version: Box<dyn 'static + Send + Any>,
         http: Arc<dyn HttpClient>,
         container_dir: PathBuf,
-    ) -> Result<PathBuf> {
+    ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<String>().unwrap();
         let version_dir = container_dir.join(version.as_str());
         fs::create_dir_all(&version_dir)
@@ -72,10 +72,13 @@ impl LspAdapter for YamlLspAdapter {
             }
         }
 
-        Ok(binary_path)
+        Ok(LanguageServerBinary {
+            path: binary_path,
+            arguments: server_binary_arguments(),
+        })
     }
 
-    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<PathBuf> {
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
         (|| async move {
             let mut last_version_dir = None;
             let mut entries = fs::read_dir(&container_dir).await?;
@@ -88,7 +91,10 @@ impl LspAdapter for YamlLspAdapter {
             let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
             let bin_path = last_version_dir.join(Self::BIN_PATH);
             if bin_path.exists() {
-                Ok(bin_path)
+                Ok(LanguageServerBinary {
+                    path: bin_path,
+                    arguments: server_binary_arguments(),
+                })
             } else {
                 Err(anyhow!(
                     "missing executable in directory {:?}",