Move lsp configuration into language crate

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/language/build.rs              |  6 ++++
crates/language/src/language.rs       | 40 ++++++++++++++++++++++++++++
crates/lsp/build.rs                   | 10 -------
crates/lsp/src/lib.rs                 | 39 +++++++---------------------
crates/project/src/lib.rs             | 20 ++++++++++----
crates/project/src/worktree.rs        |  3 -
crates/zed/languages/rust/config.toml |  4 ++
script/download-rust-analyzer         |  4 ++
8 files changed, 79 insertions(+), 47 deletions(-)

Detailed changes

crates/language/build.rs 🔗

@@ -0,0 +1,6 @@
+fn main() {
+    if let Ok(bundled) = std::env::var("ZED_BUNDLE") {
+        println!("cargo:rustc-env=ZED_BUNDLE={}", bundled);
+    }
+}
+

crates/language/src/language.rs 🔗

@@ -1,5 +1,6 @@
 use crate::HighlightMap;
 use anyhow::Result;
+use gpui::AppContext;
 use parking_lot::Mutex;
 use serde::Deserialize;
 use std::{path::Path, str, sync::Arc};
@@ -12,6 +13,13 @@ pub struct LanguageConfig {
     pub name: String,
     pub path_suffixes: Vec<String>,
     pub brackets: Vec<BracketPair>,
+    pub language_server: Option<LanguageServerConfig>,
+}
+
+#[derive(Deserialize)]
+pub struct LanguageServerConfig {
+    pub binary: String,
+    pub disk_based_diagnostic_sources: Vec<String>,
 }
 
 #[derive(Clone, Debug, Deserialize)]
@@ -51,6 +59,12 @@ impl LanguageRegistry {
         }
     }
 
+    pub fn get_language(&self, name: &str) -> Option<&Arc<Language>> {
+        self.languages
+            .iter()
+            .find(|language| language.name() == name)
+    }
+
     pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
         let path = path.as_ref();
         let filename = path.file_name().and_then(|name| name.to_str());
@@ -97,6 +111,32 @@ impl Language {
         self.config.name.as_str()
     }
 
+    pub fn start_server(
+        &self,
+        root_path: &Path,
+        cx: &AppContext,
+    ) -> Result<Option<Arc<lsp::LanguageServer>>> {
+        if let Some(config) = &self.config.language_server {
+            const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
+            let binary_path = if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
+                cx.platform()
+                    .path_for_resource(Some(&config.binary), None)?
+            } else {
+                Path::new(&config.binary).to_path_buf()
+            };
+            lsp::LanguageServer::new(&binary_path, root_path, cx.background()).map(Some)
+        } else {
+            Ok(None)
+        }
+    }
+
+    pub fn disk_based_diagnostic_sources(&self) -> &[String] {
+        self.config
+            .language_server
+            .as_ref()
+            .map_or(&[], |config| &config.disk_based_diagnostic_sources)
+    }
+
     pub fn brackets(&self) -> &[BracketPair] {
         &self.config.brackets
     }

crates/lsp/build.rs 🔗

@@ -1,10 +0,0 @@
-use std::env;
-
-fn main() {
-    let target = env::var("TARGET").unwrap();
-    println!("cargo:rustc-env=ZED_TARGET={}", target);
-
-    if let Ok(bundled) = env::var("ZED_BUNDLE") {
-        println!("cargo:rustc-env=ZED_BUNDLE={}", bundled);
-    }
-}

crates/lsp/src/lib.rs 🔗

@@ -1,6 +1,6 @@
 use anyhow::{anyhow, Context, Result};
 use futures::{io::BufWriter, AsyncRead, AsyncWrite};
-use gpui::{executor, AppContext, Task};
+use gpui::{executor, Task};
 use parking_lot::{Mutex, RwLock};
 use postage::{barrier, oneshot, prelude::Stream, sink::Sink};
 use serde::{Deserialize, Serialize};
@@ -86,47 +86,25 @@ struct Error {
 }
 
 impl LanguageServer {
-    pub fn rust(root_path: &Path, cx: &AppContext) -> Result<Arc<Self>> {
-        const ZED_BUNDLE: Option<&'static str> = option_env!("ZED_BUNDLE");
-        const ZED_TARGET: &'static str = env!("ZED_TARGET");
-
-        let rust_analyzer_name = format!("rust-analyzer-{}", ZED_TARGET);
-        if ZED_BUNDLE.map_or(Ok(false), |b| b.parse())? {
-            let rust_analyzer_path = cx
-                .platform()
-                .path_for_resource(Some(&rust_analyzer_name), None)?;
-            Self::new(root_path, &rust_analyzer_path, &[], cx.background())
-        } else {
-            Self::new(
-                root_path,
-                Path::new(&rust_analyzer_name),
-                &[],
-                cx.background(),
-            )
-        }
-    }
-
     pub fn new(
+        binary_path: &Path,
         root_path: &Path,
-        server_path: &Path,
-        server_args: &[&str],
         background: &executor::Background,
     ) -> Result<Arc<Self>> {
-        let mut server = Command::new(server_path)
-            .args(server_args)
+        let mut server = Command::new(binary_path)
             .stdin(Stdio::piped())
             .stdout(Stdio::piped())
             .stderr(Stdio::inherit())
             .spawn()?;
         let stdin = server.stdin.take().unwrap();
         let stdout = server.stdout.take().unwrap();
-        Self::new_internal(root_path, stdin, stdout, background)
+        Self::new_internal(stdin, stdout, root_path, background)
     }
 
     fn new_internal<Stdin, Stdout>(
-        root_path: &Path,
         stdin: Stdin,
         stdout: Stdout,
+        root_path: &Path,
         background: &executor::Background,
     ) -> Result<Arc<Self>>
     where
@@ -410,7 +388,7 @@ impl LanguageServer {
             buffer: Vec::new(),
         };
 
-        let server = Self::new_internal(Path::new("/"), stdin.0, stdout.1, executor).unwrap();
+        let server = Self::new_internal(stdin.0, stdout.1, Path::new("/"), executor).unwrap();
 
         let (init_id, _) = fake.receive_request::<request::Initialize>().await;
         fake.respond(init_id, InitializeResult::default()).await;
@@ -535,7 +513,10 @@ mod tests {
         let lib_file_uri =
             lsp_types::Url::from_file_path(root_dir.path().join("src/lib.rs")).unwrap();
 
-        let server = cx.read(|cx| LanguageServer::rust(root_dir.path(), cx).unwrap());
+        let server = cx.read(|cx| {
+            LanguageServer::new(Path::new("rust-analyzer"), root_dir.path(), cx.background())
+                .unwrap()
+        });
         server.next_idle_notification().await;
 
         server

crates/project/src/lib.rs 🔗

@@ -8,12 +8,11 @@ use futures::Future;
 use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
 use language::LanguageRegistry;
-use lsp::LanguageServer;
 use std::{
     path::Path,
     sync::{atomic::AtomicBool, Arc},
 };
-use util::TryFutureExt as _;
+use util::{ResultExt, TryFutureExt as _};
 
 pub use fs::*;
 pub use worktree::*;
@@ -74,11 +73,20 @@ impl Project {
         let rpc = self.client.clone();
         let languages = self.languages.clone();
         let path = Arc::from(abs_path);
-        let language_server = LanguageServer::rust(&path, cx);
+        let language_server = languages
+            .get_language("Rust")
+            .unwrap()
+            .start_server(&path, cx);
         cx.spawn(|this, mut cx| async move {
-            let worktree =
-                Worktree::open_local(rpc, path, fs, languages, Some(language_server?), &mut cx)
-                    .await?;
+            let worktree = Worktree::open_local(
+                rpc,
+                path,
+                fs,
+                languages,
+                language_server.log_err().flatten(),
+                &mut cx,
+            )
+            .await?;
             this.update(&mut cx, |this, cx| {
                 this.add_worktree(worktree.clone(), cx);
             });

crates/project/src/worktree.rs 🔗

@@ -295,7 +295,7 @@ impl Worktree {
         }
     }
 
-    pub fn language_server(&self) -> Option<&Arc<lsp::LanguageServer>> {
+    pub fn language_server(&self) -> Option<&Arc<LanguageServer>> {
         match self {
             Worktree::Local(worktree) => worktree.language_server.as_ref(),
             Worktree::Remote(_) => None,
@@ -2872,7 +2872,6 @@ mod tests {
     use anyhow::Result;
     use client::test::FakeServer;
     use fs::RealFs;
-    use language::Point;
     use lsp::Url;
     use rand::prelude::*;
     use serde_json::json;

crates/zed/languages/rust/config.toml 🔗

@@ -8,3 +8,7 @@ brackets = [
     { start = "\"", end = "\"", close = true, newline = false },
     { start = "/*", end = " */", close = true, newline = false },
 ]
+
+[language_server]
+binary = "rust-analyzer"
+disk_based_diagnostic_sources = ["rustc"]

script/download-rust-analyzer 🔗

@@ -13,3 +13,7 @@ function download {
 mkdir -p vendor/bin
 download "x86_64-apple-darwin"
 download "aarch64-apple-darwin"
+
+cd vendor/bin
+lipo -create rust-analyzer-* -output rust-analyzer
+rm rust-analyzer-*