From bf1153cedd33b0e8f02ffcb8a270a9c26b584b9b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Mar 2022 12:12:18 -0800 Subject: [PATCH 1/6] Add syntax highlighting/auto-indent/outlines for JSON files Co-Authored-By: Nathan Sobo --- Cargo.lock | 11 +++++++++++ crates/zed/Cargo.toml | 1 + crates/zed/languages/c/brackets.scm | 3 +++ crates/zed/languages/json/brackets.scm | 3 +++ crates/zed/languages/json/config.toml | 7 +++++++ crates/zed/languages/json/highlights.scm | 12 ++++++++++++ crates/zed/languages/json/indents.scm | 2 ++ crates/zed/languages/json/outline.scm | 2 ++ crates/zed/src/language.rs | 17 +++++++++++++++++ 9 files changed, 58 insertions(+) create mode 100644 crates/zed/languages/c/brackets.scm create mode 100644 crates/zed/languages/json/brackets.scm create mode 100644 crates/zed/languages/json/config.toml create mode 100644 crates/zed/languages/json/highlights.scm create mode 100644 crates/zed/languages/json/indents.scm create mode 100644 crates/zed/languages/json/outline.scm diff --git a/Cargo.lock b/Cargo.lock index 8214dec7a4..92867463c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5326,6 +5326,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-json" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b04c4e1a92139535eb9fca4ec8fa9666cc96b618005d3ae35f3c957fa92f92" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-markdown" version = "0.0.1" @@ -5935,6 +5945,7 @@ dependencies = [ "toml", "tree-sitter", "tree-sitter-c", + "tree-sitter-json", "tree-sitter-markdown", "tree-sitter-rust", "unindent", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 2298963757..7b8f79380b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -92,6 +92,7 @@ tiny_http = "0.8" toml = "0.5" tree-sitter = "0.20.4" tree-sitter-c = "0.20.1" +tree-sitter-json = "0.19.0" tree-sitter-rust = "0.20.1" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } url = "2.2" diff --git a/crates/zed/languages/c/brackets.scm b/crates/zed/languages/c/brackets.scm new file mode 100644 index 0000000000..9e8c9cd93c --- /dev/null +++ b/crates/zed/languages/c/brackets.scm @@ -0,0 +1,3 @@ +("[" @open "]" @close) +("{" @open "}" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/languages/json/brackets.scm b/crates/zed/languages/json/brackets.scm new file mode 100644 index 0000000000..9e8c9cd93c --- /dev/null +++ b/crates/zed/languages/json/brackets.scm @@ -0,0 +1,3 @@ +("[" @open "]" @close) +("{" @open "}" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/languages/json/config.toml b/crates/zed/languages/json/config.toml new file mode 100644 index 0000000000..782e818676 --- /dev/null +++ b/crates/zed/languages/json/config.toml @@ -0,0 +1,7 @@ +name = "JSON" +path_suffixes = ["json"] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, +] diff --git a/crates/zed/languages/json/highlights.scm b/crates/zed/languages/json/highlights.scm new file mode 100644 index 0000000000..7788c8aecc --- /dev/null +++ b/crates/zed/languages/json/highlights.scm @@ -0,0 +1,12 @@ +(string) @string + +(pair + key: (string) @property) + +(number) @number + +[ + (true) + (false) + (null) +] @constant \ No newline at end of file diff --git a/crates/zed/languages/json/indents.scm b/crates/zed/languages/json/indents.scm new file mode 100644 index 0000000000..b7b2a2e767 --- /dev/null +++ b/crates/zed/languages/json/indents.scm @@ -0,0 +1,2 @@ +(array "]" @end) @indent +(object "}" @end) @indent diff --git a/crates/zed/languages/json/outline.scm b/crates/zed/languages/json/outline.scm new file mode 100644 index 0000000000..43e2743478 --- /dev/null +++ b/crates/zed/languages/json/outline.scm @@ -0,0 +1,2 @@ +(pair + key: (string (string_content) @name)) @item diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index f0311730d4..4285f5f311 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -429,6 +429,7 @@ pub fn build_language_registry() -> LanguageRegistry { .join(".zed"), ); languages.add(Arc::new(c())); + languages.add(Arc::new(json())); languages.add(Arc::new(rust())); languages.add(Arc::new(markdown())); languages @@ -455,6 +456,8 @@ fn c() -> Language { Language::new(config, Some(grammar)) .with_highlights_query(load_query("c/highlights.scm").as_ref()) .unwrap() + .with_brackets_query(load_query("c/brackets.scm").as_ref()) + .unwrap() .with_indents_query(load_query("c/indents.scm").as_ref()) .unwrap() .with_outline_query(load_query("c/outline.scm").as_ref()) @@ -462,6 +465,20 @@ fn c() -> Language { .with_lsp_ext(CLsp) } +fn json() -> Language { + let grammar = tree_sitter_json::language(); + let config = toml::from_slice(&LanguageDir::get("json/config.toml").unwrap().data).unwrap(); + Language::new(config, Some(grammar)) + .with_highlights_query(load_query("json/highlights.scm").as_ref()) + .unwrap() + .with_brackets_query(load_query("json/brackets.scm").as_ref()) + .unwrap() + .with_indents_query(load_query("json/indents.scm").as_ref()) + .unwrap() + .with_outline_query(load_query("json/outline.scm").as_ref()) + .unwrap() +} + fn markdown() -> Language { let grammar = tree_sitter_markdown::language(); let config = toml::from_slice(&LanguageDir::get("markdown/config.toml").unwrap().data).unwrap(); From 0582c557e3d6748ddf4c2ef3e03e2d75f8e20267 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Mar 2022 13:29:25 -0800 Subject: [PATCH 2/6] Add JSON language server Co-Authored-By: Nathan Sobo --- crates/language/src/language.rs | 62 +++++---- crates/lsp/src/lsp.rs | 3 + crates/zed/languages/json/config.toml | 3 + crates/zed/src/language.rs | 186 ++++++++++++++++++++------ 4 files changed, 189 insertions(+), 65 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 31edba7131..f3068b7792 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -6,7 +6,7 @@ pub mod proto; #[cfg(test)] mod tests; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use client::http::{self, HttpClient}; use collections::HashSet; use futures::{ @@ -61,10 +61,11 @@ pub trait ToLspPosition { pub struct LspBinaryVersion { pub name: String, - pub url: http::Url, + pub url: Option, } -pub trait LspExt: 'static + Send + Sync { +pub trait LspAdapter: 'static + Send + Sync { + fn name(&self) -> &'static str; fn fetch_latest_server_version( &self, http: Arc, @@ -73,9 +74,9 @@ pub trait LspExt: 'static + Send + Sync { &self, version: LspBinaryVersion, http: Arc, - download_dir: Arc, + container_dir: PathBuf, ) -> BoxFuture<'static, Result>; - fn cached_server_binary(&self, download_dir: Arc) -> BoxFuture<'static, Option>; + fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option>; fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams); fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option { None @@ -83,6 +84,10 @@ pub trait LspExt: 'static + Send + Sync { fn label_for_symbol(&self, _: &str, _: lsp::SymbolKind, _: &Language) -> Option { None } + + fn server_args(&self) -> &[&str] { + &[] + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -140,7 +145,7 @@ pub struct BracketPair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) lsp_ext: Option>, + pub(crate) adapter: Option>, lsp_binary_path: Mutex>>>>>, } @@ -260,7 +265,7 @@ impl LanguageRegistry { .ok_or_else(|| anyhow!("language server download directory has not been assigned")) .log_err()?; - let lsp_ext = language.lsp_ext.clone()?; + let adapter = language.adapter.clone()?; let background = cx.background().clone(); let server_binary_path = { Some( @@ -269,7 +274,7 @@ impl LanguageRegistry { .lock() .get_or_insert_with(|| { get_server_binary_path( - lsp_ext, + adapter.clone(), language.clone(), http_client, download_dir, @@ -285,7 +290,9 @@ impl LanguageRegistry { }?; Some(cx.background().spawn(async move { let server_binary_path = server_binary_path.await?; - let server = lsp::LanguageServer::new(&server_binary_path, &root_path, background)?; + let server_args = adapter.server_args(); + let server = + lsp::LanguageServer::new(&server_binary_path, server_args, &root_path, background)?; Ok(server) })) } @@ -298,22 +305,29 @@ impl LanguageRegistry { } async fn get_server_binary_path( - lsp_ext: Arc, + adapter: Arc, language: Arc, http_client: Arc, download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { + let container_dir = download_dir.join(adapter.name()); + if !container_dir.exists() { + smol::fs::create_dir_all(&container_dir) + .await + .context("failed to create container directory")?; + } + let path = fetch_latest_server_binary_path( - lsp_ext.clone(), + adapter.clone(), language.clone(), http_client, - download_dir.clone(), + &container_dir, statuses.clone(), ) .await; if path.is_err() { - if let Some(cached_path) = lsp_ext.cached_server_binary(download_dir).await { + if let Some(cached_path) = adapter.cached_server_binary(container_dir).await { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; @@ -328,10 +342,10 @@ async fn get_server_binary_path( } async fn fetch_latest_server_binary_path( - lsp_ext: Arc, + adapter: Arc, language: Arc, http_client: Arc, - download_dir: Arc, + container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { lsp_binary_statuses_tx @@ -340,14 +354,14 @@ async fn fetch_latest_server_binary_path( LanguageServerBinaryStatus::CheckingForUpdate, )) .await?; - let version_info = lsp_ext + let version_info = adapter .fetch_latest_server_version(http_client.clone()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; - let path = lsp_ext - .fetch_server_binary(version_info, http_client, download_dir) + let path = adapter + .fetch_server_binary(version_info, http_client, container_dir.to_path_buf()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) @@ -369,7 +383,7 @@ impl Language { highlight_map: Default::default(), }) }), - lsp_ext: None, + adapter: None, lsp_binary_path: Default::default(), } } @@ -414,8 +428,8 @@ impl Language { Ok(self) } - pub fn with_lsp_ext(mut self, lsp_ext: impl LspExt) -> Self { - self.lsp_ext = Some(Arc::new(lsp_ext)); + pub fn with_lsp_adapter(mut self, lsp_adapter: impl LspAdapter) -> Self { + self.adapter = Some(Arc::new(lsp_adapter)); self } @@ -442,19 +456,19 @@ impl Language { } pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { - if let Some(processor) = self.lsp_ext.as_ref() { + if let Some(processor) = self.adapter.as_ref() { processor.process_diagnostics(diagnostics); } } pub fn label_for_completion(&self, completion: &lsp::CompletionItem) -> Option { - self.lsp_ext + self.adapter .as_ref()? .label_for_completion(completion, self) } pub fn label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option { - self.lsp_ext.as_ref()?.label_for_symbol(name, kind, self) + self.adapter.as_ref()?.label_for_symbol(name, kind, self) } pub fn highlight_text<'a>( diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index dc57c85213..890b6d2fb0 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -102,10 +102,13 @@ struct Error { impl LanguageServer { pub fn new( binary_path: &Path, + args: &[&str], root_path: &Path, background: Arc, ) -> Result> { let mut server = Command::new(binary_path) + .current_dir(root_path) + .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) diff --git a/crates/zed/languages/json/config.toml b/crates/zed/languages/json/config.toml index 782e818676..70201e6877 100644 --- a/crates/zed/languages/json/config.toml +++ b/crates/zed/languages/json/config.toml @@ -5,3 +5,6 @@ brackets = [ { start = "[", end = "]", close = true, newline = true }, { start = "\"", end = "\"", close = true, newline = false }, ] + +[language_server] +disk_based_diagnostic_sources = [] \ No newline at end of file diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 4285f5f311..0e5adb78fc 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -8,21 +8,16 @@ use regex::Regex; use rust_embed::RustEmbed; use serde::Deserialize; use smol::fs::{self, File}; -use std::{ - borrow::Cow, - env::consts, - path::{Path, PathBuf}, - str, - sync::Arc, -}; +use std::{borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; use util::{ResultExt, TryFutureExt}; #[derive(RustEmbed)] #[folder = "languages"] struct LanguageDir; -struct RustLsp; -struct CLsp; +struct RustLspAdapter; +struct CLspAdapter; +struct JsonLspAdapter; #[derive(Deserialize)] struct GithubRelease { @@ -36,7 +31,11 @@ struct GithubReleaseAsset { browser_download_url: http::Url, } -impl LspExt for RustLsp { +impl LspAdapter for RustLspAdapter { + fn name(&self) -> &'static str { + "rust-analyzer" + } + fn fetch_latest_server_version( &self, http: Arc, @@ -67,7 +66,7 @@ impl LspExt for RustLsp { .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?; Ok(LspBinaryVersion { name: release.name, - url: asset.browser_download_url.clone(), + url: Some(asset.browser_download_url.clone()), }) } .boxed() @@ -77,18 +76,15 @@ impl LspExt for RustLsp { &self, version: LspBinaryVersion, http: Arc, - download_dir: Arc, + container_dir: PathBuf, ) -> BoxFuture<'static, Result> { async move { - let destination_dir_path = download_dir.join("rust-analyzer"); - fs::create_dir_all(&destination_dir_path).await?; - let destination_path = - destination_dir_path.join(format!("rust-analyzer-{}", version.name)); + let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name)); if fs::metadata(&destination_path).await.is_err() { let response = http .send( - surf::RequestBuilder::new(Method::Get, version.url) + surf::RequestBuilder::new(Method::Get, version.url.unwrap()) .middleware(surf::middleware::Redirect::default()) .build(), ) @@ -103,7 +99,7 @@ impl LspExt for RustLsp { ) .await?; - if let Some(mut entries) = fs::read_dir(&destination_dir_path).await.log_err() { + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { while let Some(entry) = entries.next().await { if let Some(entry) = entry.log_err() { let entry_path = entry.path(); @@ -120,13 +116,10 @@ impl LspExt for RustLsp { .boxed() } - fn cached_server_binary(&self, download_dir: Arc) -> BoxFuture<'static, Option> { + fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { async move { - let destination_dir_path = download_dir.join("rust-analyzer"); - fs::create_dir_all(&destination_dir_path).await?; - let mut last = None; - let mut entries = fs::read_dir(&destination_dir_path).await?; + let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { last = Some(entry?.path()); } @@ -292,7 +285,11 @@ impl LspExt for RustLsp { } } -impl LspExt for CLsp { +impl LspAdapter for CLspAdapter { + fn name(&self) -> &'static str { + "clangd" + } + fn fetch_latest_server_version( &self, http: Arc, @@ -323,7 +320,7 @@ impl LspExt for CLsp { .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?; Ok(LspBinaryVersion { name: release.name, - url: asset.browser_download_url.clone(), + url: Some(asset.browser_download_url.clone()), }) } .boxed() @@ -333,14 +330,9 @@ impl LspExt for CLsp { &self, version: LspBinaryVersion, http: Arc, - download_dir: Arc, + container_dir: PathBuf, ) -> BoxFuture<'static, Result> { async move { - let container_dir = download_dir.join("clangd"); - fs::create_dir_all(&container_dir) - .await - .context("failed to create container directory")?; - let zip_path = container_dir.join(format!("clangd_{}.zip", version.name)); let version_dir = container_dir.join(format!("clangd_{}", version.name)); let binary_path = version_dir.join("bin/clangd"); @@ -348,7 +340,7 @@ impl LspExt for CLsp { if fs::metadata(&binary_path).await.is_err() { let response = http .send( - surf::RequestBuilder::new(Method::Get, version.url) + surf::RequestBuilder::new(Method::Get, version.url.unwrap()) .middleware(surf::middleware::Redirect::default()) .build(), ) @@ -390,13 +382,10 @@ impl LspExt for CLsp { .boxed() } - fn cached_server_binary(&self, download_dir: Arc) -> BoxFuture<'static, Option> { + fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { async move { - let destination_dir_path = download_dir.join("clangd"); - fs::create_dir_all(&destination_dir_path).await?; - let mut last_clangd_dir = None; - let mut entries = fs::read_dir(&destination_dir_path).await?; + let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { let entry = entry?; if entry.file_type().await?.is_dir() { @@ -421,6 +410,120 @@ impl LspExt for CLsp { fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} } +impl JsonLspAdapter { + const BIN_PATH: &'static str = + "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; +} + +impl LspAdapter for JsonLspAdapter { + fn name(&self) -> &'static str { + "vscode-json-languageserver" + } + + fn server_args(&self) -> &[&str] { + &["--stdio"] + } + + fn fetch_latest_server_version( + &self, + _: Arc, + ) -> BoxFuture<'static, Result> { + async move { + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } + + let output = smol::process::Command::new("npm") + .args(["info", "vscode-json-languageserver", "--json"]) + .output() + .await?; + if !output.status.success() { + Err(anyhow!("failed to execute npm info"))?; + } + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + + Ok(LspBinaryVersion { + name: info + .versions + .pop() + .ok_or_else(|| anyhow!("no versions found in npm info"))?, + url: Default::default(), + }) + } + .boxed() + } + + fn fetch_server_binary( + &self, + version: LspBinaryVersion, + _: Arc, + container_dir: PathBuf, + ) -> BoxFuture<'static, Result> { + async move { + let version_dir = container_dir.join(&version.name); + fs::create_dir_all(&version_dir) + .await + .context("failed to create version directory")?; + let binary_path = version_dir.join(Self::BIN_PATH); + + if fs::metadata(&binary_path).await.is_err() { + let output = smol::process::Command::new("npm") + .current_dir(&version_dir) + .arg("install") + .arg(format!("vscode-json-languageserver@{}", version.name)) + .output() + .await + .context("failed to run npm install")?; + if !output.status.success() { + Err(anyhow!("failed to install vscode-json-languageserver"))?; + } + + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + while let Some(entry) = entries.next().await { + if let Some(entry) = entry.log_err() { + let entry_path = entry.path(); + if entry_path.as_path() != version_dir { + fs::remove_dir_all(&entry_path).await.log_err(); + } + } + } + } + } + + Ok(binary_path) + } + .boxed() + } + + fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { + async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + 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) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + } + .log_err() + .boxed() + } + + fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} +} + pub fn build_language_registry() -> LanguageRegistry { let mut languages = LanguageRegistry::new(); languages.set_language_server_download_dir( @@ -447,7 +550,7 @@ fn rust() -> Language { .unwrap() .with_outline_query(load_query("rust/outline.scm").as_ref()) .unwrap() - .with_lsp_ext(RustLsp) + .with_lsp_adapter(RustLspAdapter) } fn c() -> Language { @@ -462,7 +565,7 @@ fn c() -> Language { .unwrap() .with_outline_query(load_query("c/outline.scm").as_ref()) .unwrap() - .with_lsp_ext(CLsp) + .with_lsp_adapter(CLspAdapter) } fn json() -> Language { @@ -477,6 +580,7 @@ fn json() -> Language { .unwrap() .with_outline_query(load_query("json/outline.scm").as_ref()) .unwrap() + .with_lsp_adapter(JsonLspAdapter) } fn markdown() -> Language { @@ -498,7 +602,7 @@ fn load_query(path: &str) -> Cow<'static, str> { mod tests { use super::*; use gpui::color::Color; - use language::LspExt; + use language::LspAdapter; use theme::SyntaxTheme; #[test] @@ -525,7 +629,7 @@ mod tests { }, ], }; - RustLsp.process_diagnostics(&mut params); + RustLspAdapter.process_diagnostics(&mut params); assert_eq!(params.diagnostics[0].message, "use of moved value `a`"); From 81627a0f14c998fe0a375435b16978e9bd6aef74 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Mar 2022 13:39:40 -0800 Subject: [PATCH 3/6] Avoid sending unhandled LSP requests to JSON language server Co-Authored-By: Nathan Sobo --- crates/project/src/lsp_command.rs | 10 +++++++++- crates/project/src/project.rs | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 3b502fc8fa..57b83e0e25 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -8,7 +8,7 @@ use language::{ proto::{deserialize_anchor, serialize_anchor}, range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToLspPosition, ToPointUtf16, }; -use lsp::DocumentHighlightKind; +use lsp::{DocumentHighlightKind, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path}; #[async_trait(?Send)] @@ -17,6 +17,10 @@ pub(crate) trait LspCommand: 'static + Sized { type LspRequest: 'static + Send + lsp::request::Request; type ProtoRequest: 'static + Send + proto::RequestMessage; + fn check_capabilities(&self, _: &lsp::ServerCapabilities) -> bool { + true + } + fn to_lsp( &self, path: &Path, @@ -610,6 +614,10 @@ impl LspCommand for GetDocumentHighlights { type LspRequest = lsp::request::DocumentHighlightRequest; type ProtoRequest = proto::GetDocumentHighlights; + fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool { + capabilities.document_highlight_provider.is_some() + } + fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::DocumentHighlightParams { lsp::DocumentHighlightParams { text_document_position_params: lsp::TextDocumentPositionParams { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c789bad6c2..108575da7c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1322,6 +1322,7 @@ impl Project { cx: &mut ModelContext, ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); + self.request_lsp(buffer.clone(), GetDocumentHighlights { position }, cx) } @@ -1724,6 +1725,17 @@ impl Project { return Task::ready(Ok(Default::default())); } + if !lang_server + .capabilities() + .borrow() + .as_ref() + .map_or(false, |capabilities| { + capabilities.code_action_provider.is_some() + }) + { + return Task::ready(Ok(Default::default())); + } + let lsp_range = lsp::Range::new( range.start.to_point_utf16(buffer).to_lsp_position(), range.end.to_point_utf16(buffer).to_lsp_position(), @@ -2251,6 +2263,17 @@ impl Project { if let Some((file, language_server)) = file.zip(buffer.language_server().cloned()) { let lsp_params = request.to_lsp(&file.abs_path(cx), cx); return cx.spawn(|this, cx| async move { + if !language_server + .capabilities() + .borrow() + .as_ref() + .map_or(false, |capabilities| { + request.check_capabilities(capabilities) + }) + { + return Ok(Default::default()); + } + let response = language_server .request::(lsp_params) .await From 9999862016e5fdc345e6abd8ac967e58ec172022 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Mar 2022 14:16:56 -0800 Subject: [PATCH 4/6] Enable formatting feature of JSON language server The feature doesn't work yet because the JSON language server only supports *range* formatting, not document formatting. We need to adjust our code to inspect the server's capabilities and send range formatting requests instead when needed. We're going to hold off on doing this right now, because it will create merge conflicts with the `preserve-worktrees` branch (#525) Co-Authored-By: Nathan Sobo --- crates/language/src/language.rs | 16 ++++++++++++++-- crates/lsp/src/lsp.rs | 15 +++++++++++---- crates/zed/src/language.rs | 7 +++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index f3068b7792..ec525d1e4d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -18,6 +18,7 @@ use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use serde::Deserialize; +use serde_json::Value; use std::{ cell::RefCell, ops::Range, @@ -78,9 +79,11 @@ pub trait LspAdapter: 'static + Send + Sync { ) -> BoxFuture<'static, Result>; fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option>; fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams); + fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option { None } + fn label_for_symbol(&self, _: &str, _: lsp::SymbolKind, _: &Language) -> Option { None } @@ -88,6 +91,10 @@ pub trait LspAdapter: 'static + Send + Sync { fn server_args(&self) -> &[&str] { &[] } + + fn initialization_options(&self) -> Option { + None + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -291,8 +298,13 @@ impl LanguageRegistry { Some(cx.background().spawn(async move { let server_binary_path = server_binary_path.await?; let server_args = adapter.server_args(); - let server = - lsp::LanguageServer::new(&server_binary_path, server_args, &root_path, background)?; + let server = lsp::LanguageServer::new( + &server_binary_path, + server_args, + adapter.initialization_options(), + &root_path, + background, + )?; Ok(server) })) } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 890b6d2fb0..1a66d2bb5a 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -103,6 +103,7 @@ impl LanguageServer { pub fn new( binary_path: &Path, args: &[&str], + options: Option, root_path: &Path, background: Arc, ) -> Result> { @@ -115,13 +116,14 @@ impl LanguageServer { .spawn()?; let stdin = server.stdin.take().unwrap(); let stdout = server.stdout.take().unwrap(); - Self::new_internal(stdin, stdout, root_path, background) + Self::new_internal(stdin, stdout, root_path, options, background) } fn new_internal( stdin: Stdin, stdout: Stdout, root_path: &Path, + options: Option, executor: Arc, ) -> Result> where @@ -232,7 +234,7 @@ impl LanguageServer { .spawn({ let this = this.clone(); async move { - if let Some(capabilities) = this.init(root_uri).log_err().await { + if let Some(capabilities) = this.init(root_uri, options).log_err().await { *capabilities_tx.borrow_mut() = Some(capabilities); } @@ -244,13 +246,17 @@ impl LanguageServer { Ok(this) } - async fn init(self: Arc, root_uri: Url) -> Result { + async fn init( + self: Arc, + root_uri: Url, + options: Option, + ) -> Result { #[allow(deprecated)] let params = InitializeParams { process_id: Default::default(), root_path: Default::default(), root_uri: Some(root_uri), - initialization_options: Default::default(), + initialization_options: options, capabilities: ClientCapabilities { text_document: Some(TextDocumentClientCapabilities { definition: Some(GotoCapability { @@ -530,6 +536,7 @@ impl LanguageServer { stdin_writer, stdout_reader, Path::new("/"), + None, cx.background().clone(), ) .unwrap(); diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 0e5adb78fc..7b5d0de8b4 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -7,6 +7,7 @@ use lazy_static::lazy_static; use regex::Regex; use rust_embed::RustEmbed; use serde::Deserialize; +use serde_json::json; use smol::fs::{self, File}; use std::{borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; use util::{ResultExt, TryFutureExt}; @@ -522,6 +523,12 @@ impl LspAdapter for JsonLspAdapter { } fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + + fn initialization_options(&self) -> Option { + Some(json!({ + "provideFormatter": true + })) + } } pub fn build_language_registry() -> LanguageRegistry { From 78d96a05fcc57fe360a8ef4b844570b82aaac334 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Mar 2022 15:42:26 -0800 Subject: [PATCH 5/6] Make fake language servers have full capabilities --- crates/language/src/language.rs | 2 +- crates/lsp/src/lsp.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index ec525d1e4d..2e566b263b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -574,7 +574,7 @@ impl LanguageServerConfig { Self { fake_config: Some(FakeLanguageServerConfig { servers_tx, - capabilities: Default::default(), + capabilities: lsp::LanguageServer::full_capabilities(), initializer: None, }), disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()), diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 1a66d2bb5a..3d9df3469c 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -512,8 +512,16 @@ type FakeLanguageServerHandlers = Arc< #[cfg(any(test, feature = "test-support"))] impl LanguageServer { + pub fn full_capabilities() -> ServerCapabilities { + ServerCapabilities { + document_highlight_provider: Some(OneOf::Left(true)), + code_action_provider: Some(CodeActionProviderCapability::Simple(true)), + ..Default::default() + } + } + pub fn fake(cx: &mut gpui::MutableAppContext) -> (Arc, FakeLanguageServer) { - Self::fake_with_capabilities(Default::default(), cx) + Self::fake_with_capabilities(Self::full_capabilities(), cx) } pub fn fake_with_capabilities( From 3c242a43d2e436d7042a3fd4010aabe94a5103f9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Mar 2022 15:59:03 -0800 Subject: [PATCH 6/6] Wait for LSP capabilities to be initialized before checking them --- crates/project/src/project.rs | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 108575da7c..0199546081 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -22,7 +22,7 @@ use language::{ }; use lsp::{DiagnosticSeverity, DocumentHighlightKind, LanguageServer}; use lsp_command::*; -use postage::watch; +use postage::{prelude::Stream, watch}; use rand::prelude::*; use search::SearchQuery; use sha2::{Digest, Sha256}; @@ -1725,22 +1725,25 @@ impl Project { return Task::ready(Ok(Default::default())); } - if !lang_server - .capabilities() - .borrow() - .as_ref() - .map_or(false, |capabilities| { - capabilities.code_action_provider.is_some() - }) - { - return Task::ready(Ok(Default::default())); - } - let lsp_range = lsp::Range::new( range.start.to_point_utf16(buffer).to_lsp_position(), range.end.to_point_utf16(buffer).to_lsp_position(), ); cx.foreground().spawn(async move { + let mut capabilities = lang_server.capabilities(); + while capabilities.borrow().is_none() { + capabilities.recv().await; + } + if !capabilities + .borrow() + .as_ref() + .map_or(false, |capabilities| { + capabilities.code_action_provider.is_some() + }) + { + return Ok(Default::default()); + } + Ok(lang_server .request::(lsp::CodeActionParams { text_document: lsp::TextDocumentIdentifier::new( @@ -2263,12 +2266,16 @@ impl Project { if let Some((file, language_server)) = file.zip(buffer.language_server().cloned()) { let lsp_params = request.to_lsp(&file.abs_path(cx), cx); return cx.spawn(|this, cx| async move { - if !language_server - .capabilities() + let mut capabilities = language_server.capabilities(); + while capabilities.borrow().is_none() { + capabilities.recv().await; + } + + if !capabilities .borrow() .as_ref() .map_or(false, |capabilities| { - request.check_capabilities(capabilities) + request.check_capabilities(&capabilities) }) { return Ok(Default::default());