From 752bc5dcddabd99e5e64ba8e582f02b4f8a3830c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 29 Sep 2023 14:06:32 -0700 Subject: [PATCH] Refactor elixir LSP settings --- assets/settings/default.json | 16 +- crates/zed/src/languages.rs | 17 +- crates/zed/src/languages/elixir.rs | 298 +++++++++++++++++++++++- crates/zed/src/languages/elixir_next.rs | 266 --------------------- 4 files changed, 310 insertions(+), 287 deletions(-) delete mode 100644 crates/zed/src/languages/elixir_next.rs diff --git a/assets/settings/default.json b/assets/settings/default.json index 95f99a78e9..7785f5dd44 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -379,24 +379,24 @@ }, // Settings specific to our elixir integration "elixir": { - // Set Zed to use the experimental Next LS LSP server. + // Change the LSP zed uses for elixir. // Note that changing this setting requires a restart of Zed // to take effect. // // May take 3 values: - // 1. Use the standard elixir-ls LSP server - // "next": "off" - // 2. Use a bundled version of the next Next LS LSP server - // "next": "on", - // 3. Use a local build of the next Next LS LSP server: - // "next": { + // 1. Use the standard ElixirLS, this is the default + // "lsp": "elixir_ls" + // 2. Use the experimental NextLs + // "lsp": "next_ls", + // 3. Use a language server installed locally on your machine: + // "lsp": { // "local": { // "path": "~/next-ls/bin/start", // "arguments": ["--stdio"] // } // }, // - "next": "off" + "lsp": "elixir_ls" }, // Different settings for specific languages. "languages": { diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index be8d05256a..04e5292a7d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -6,12 +6,11 @@ use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; use util::asset_str; -use self::elixir_next::ElixirSettings; +use self::elixir::ElixirSettings; mod c; mod css; mod elixir; -mod elixir_next; mod go; mod html; mod json; @@ -46,7 +45,7 @@ pub fn init( node_runtime: Arc, cx: &mut AppContext, ) { - settings::register::(cx); + settings::register::(cx); let language = |name, grammar, adapters| { languages.register(name, load_config(name), grammar, adapters, load_queries) @@ -72,21 +71,21 @@ pub fn init( ], ); - match &settings::get::(cx).next { - elixir_next::ElixirNextSetting::Off => language( + match &settings::get::(cx).lsp { + elixir::ElixirLspSetting::ElixirLs => language( "elixir", tree_sitter_elixir::language(), vec![Arc::new(elixir::ElixirLspAdapter)], ), - elixir_next::ElixirNextSetting::On => language( + elixir::ElixirLspSetting::NextLs => language( "elixir", tree_sitter_elixir::language(), - vec![Arc::new(elixir_next::NextLspAdapter)], + vec![Arc::new(elixir::NextLspAdapter)], ), - elixir_next::ElixirNextSetting::Local { path, arguments } => language( + elixir::ElixirLspSetting::Local { path, arguments } => language( "elixir", tree_sitter_elixir::language(), - vec![Arc::new(elixir_next::LocalNextLspAdapter { + vec![Arc::new(elixir::LocalLspAdapter { path: path.clone(), arguments: arguments.clone(), })], diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index b166feda76..9d2ebb7f47 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -1,12 +1,17 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use futures::StreamExt; use gpui::{AsyncAppContext, Task}; pub use language::*; use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use settings::Setting; use smol::fs::{self, File}; use std::{ any::Any, + env::consts, + ops::Deref, path::PathBuf, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -14,11 +19,50 @@ use std::{ }, }; use util::{ + async_iife, fs::remove_matching, github::{latest_github_release, GitHubLspBinaryVersion}, ResultExt, }; +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct ElixirSettings { + pub lsp: ElixirLspSetting, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ElixirLspSetting { + ElixirLs, + NextLs, + Local { + path: String, + arguments: Vec, + }, +} + +#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)] +pub struct ElixirSettingsContent { + lsp: Option, +} + +impl Setting for ElixirSettings { + const KEY: Option<&'static str> = Some("elixir"); + + type FileContent = ElixirSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> Result + where + Self: Sized, + { + Self::load_via_json_merge(default_value, user_values) + } +} + pub struct ElixirLspAdapter; #[async_trait] @@ -144,14 +188,14 @@ impl LspAdapter for ElixirLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - get_cached_server_binary(container_dir).await + get_cached_server_binary_elixir_ls(container_dir).await } async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option { - get_cached_server_binary(container_dir).await + get_cached_server_binary_elixir_ls(container_dir).await } async fn label_for_completion( @@ -238,7 +282,9 @@ impl LspAdapter for ElixirLspAdapter { } } -async fn get_cached_server_binary(container_dir: PathBuf) -> Option { +async fn get_cached_server_binary_elixir_ls( + container_dir: PathBuf, +) -> Option { (|| async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; @@ -254,3 +300,247 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option LanguageServerName { + LanguageServerName("next-ls".into()) + } + + fn short_name(&self) -> &'static str { + "next-ls" + } + + async fn fetch_latest_server_version( + &self, + delegate: &dyn LspAdapterDelegate, + ) -> Result> { + let release = + latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?; + let version = release.name.clone(); + let platform = match consts::ARCH { + "x86_64" => "darwin_arm64", + "aarch64" => "darwin_amd64", + other => bail!("Running on unsupported platform: {other}"), + }; + let asset_name = format!("next_ls_{}", platform); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { + name: version, + url: asset.browser_download_url.clone(), + }; + Ok(Box::new(version) as Box<_>) + } + + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + + let binary_path = container_dir.join("next-ls"); + + if fs::metadata(&binary_path).await.is_err() { + let mut response = delegate + .http_client() + .get(&version.url, Default::default(), true) + .await + .map_err(|err| anyhow!("error downloading release: {}", err))?; + + let mut file = smol::fs::File::create(&binary_path).await?; + if !response.status().is_success() { + Err(anyhow!( + "download failed with status {}", + response.status().to_string() + ))?; + } + futures::io::copy(response.body_mut(), &mut file).await?; + + fs::set_permissions( + &binary_path, + ::from_mode(0o755), + ) + .await?; + } + + Ok(LanguageServerBinary { + path: binary_path, + arguments: vec!["--stdio".into()], + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary_next(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--stdio".into()]; + binary + }) + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary_next(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) + } + + async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + label_for_completion_elixir(completion, language) + } + + async fn label_for_symbol( + &self, + name: &str, + symbol_kind: SymbolKind, + language: &Arc, + ) -> Option { + label_for_symbol_elixir(name, symbol_kind, language) + } +} + +async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option { + async_iife!({ + let mut last_binary_path = 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_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name == "next-ls") + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: Vec::new(), + }) + } else { + Err(anyhow!("no cached binary")) + } + }) + .await + .log_err() +} + +pub struct LocalLspAdapter { + pub path: String, + pub arguments: Vec, +} + +#[async_trait] +impl LspAdapter for LocalLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("local-ls".into()) + } + + fn short_name(&self) -> &'static str { + "local-ls" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(()) as Box<_>) + } + + async fn fetch_server_binary( + &self, + _: Box, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let path = shellexpand::full(&self.path)?; + Ok(LanguageServerBinary { + path: PathBuf::from(path.deref()), + arguments: self.arguments.iter().map(|arg| arg.into()).collect(), + }) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + let path = shellexpand::full(&self.path).ok()?; + Some(LanguageServerBinary { + path: PathBuf::from(path.deref()), + arguments: self.arguments.iter().map(|arg| arg.into()).collect(), + }) + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + let path = shellexpand::full(&self.path).ok()?; + Some(LanguageServerBinary { + path: PathBuf::from(path.deref()), + arguments: self.arguments.iter().map(|arg| arg.into()).collect(), + }) + } + + async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + label_for_completion_elixir(completion, language) + } + + async fn label_for_symbol( + &self, + name: &str, + symbol: SymbolKind, + language: &Arc, + ) -> Option { + label_for_symbol_elixir(name, symbol, language) + } +} + +fn label_for_completion_elixir( + completion: &lsp::CompletionItem, + language: &Arc, +) -> Option { + return Some(CodeLabel { + runs: language.highlight_text(&completion.label.clone().into(), 0..completion.label.len()), + text: completion.label.clone(), + filter_range: 0..completion.label.len(), + }); +} + +fn label_for_symbol_elixir( + name: &str, + _: SymbolKind, + language: &Arc, +) -> Option { + Some(CodeLabel { + runs: language.highlight_text(&name.into(), 0..name.len()), + text: name.to_string(), + filter_range: 0..name.len(), + }) +} diff --git a/crates/zed/src/languages/elixir_next.rs b/crates/zed/src/languages/elixir_next.rs deleted file mode 100644 index f5a77c7568..0000000000 --- a/crates/zed/src/languages/elixir_next.rs +++ /dev/null @@ -1,266 +0,0 @@ -use anyhow::{anyhow, bail, Result}; - -use async_trait::async_trait; -pub use language::*; -use lsp::{LanguageServerBinary, SymbolKind}; -use schemars::JsonSchema; -use serde_derive::{Deserialize, Serialize}; -use settings::Setting; -use smol::{fs, stream::StreamExt}; -use std::{any::Any, env::consts, ops::Deref, path::PathBuf, sync::Arc}; -use util::{ - async_iife, - github::{latest_github_release, GitHubLspBinaryVersion}, - ResultExt, -}; - -#[derive(Clone, Serialize, Deserialize, JsonSchema)] -pub struct ElixirSettings { - pub next: ElixirNextSetting, -} - -#[derive(Clone, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ElixirNextSetting { - Off, - On, - Local { - path: String, - arguments: Vec, - }, -} - -#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)] -pub struct ElixirSettingsContent { - next: Option, -} - -impl Setting for ElixirSettings { - const KEY: Option<&'static str> = Some("elixir"); - - type FileContent = ElixirSettingsContent; - - fn load( - default_value: &Self::FileContent, - user_values: &[&Self::FileContent], - _: &gpui::AppContext, - ) -> Result - where - Self: Sized, - { - Self::load_via_json_merge(default_value, user_values) - } -} - -pub struct NextLspAdapter; - -#[async_trait] -impl LspAdapter for NextLspAdapter { - async fn name(&self) -> LanguageServerName { - LanguageServerName("next-ls".into()) - } - - fn short_name(&self) -> &'static str { - "next-ls" - } - - async fn fetch_latest_server_version( - &self, - delegate: &dyn LspAdapterDelegate, - ) -> Result> { - let release = - latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?; - let version = release.name.clone(); - let platform = match consts::ARCH { - "x86_64" => "darwin_arm64", - "aarch64" => "darwin_amd64", - other => bail!("Running on unsupported platform: {other}"), - }; - let asset_name = format!("next_ls_{}", platform); - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; - let version = GitHubLspBinaryVersion { - name: version, - url: asset.browser_download_url.clone(), - }; - Ok(Box::new(version) as Box<_>) - } - - async fn fetch_server_binary( - &self, - version: Box, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let version = version.downcast::().unwrap(); - - let binary_path = container_dir.join("next-ls"); - - if fs::metadata(&binary_path).await.is_err() { - let mut response = delegate - .http_client() - .get(&version.url, Default::default(), true) - .await - .map_err(|err| anyhow!("error downloading release: {}", err))?; - - let mut file = smol::fs::File::create(&binary_path).await?; - if !response.status().is_success() { - Err(anyhow!( - "download failed with status {}", - response.status().to_string() - ))?; - } - futures::io::copy(response.body_mut(), &mut file).await?; - - fs::set_permissions( - &binary_path, - ::from_mode(0o755), - ) - .await?; - } - - Ok(LanguageServerBinary { - path: binary_path, - arguments: vec!["--stdio".into()], - }) - } - - async fn cached_server_binary( - &self, - container_dir: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Option { - get_cached_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--stdio".into()]; - binary - }) - } - - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--help".into()]; - binary - }) - } - - async fn label_for_symbol( - &self, - name: &str, - symbol_kind: SymbolKind, - language: &Arc, - ) -> Option { - label_for_symbol_next(name, symbol_kind, language) - } -} - -async fn get_cached_server_binary(container_dir: PathBuf) -> Option { - async_iife!({ - let mut last_binary_path = 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_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name == "next-ls") - { - last_binary_path = Some(entry.path()); - } - } - - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: Vec::new(), - }) - } else { - Err(anyhow!("no cached binary")) - } - }) - .await - .log_err() -} - -pub struct LocalNextLspAdapter { - pub path: String, - pub arguments: Vec, -} - -#[async_trait] -impl LspAdapter for LocalNextLspAdapter { - async fn name(&self) -> LanguageServerName { - LanguageServerName("local-next-ls".into()) - } - - fn short_name(&self) -> &'static str { - "next-ls" - } - - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - ) -> Result> { - Ok(Box::new(()) as Box<_>) - } - - async fn fetch_server_binary( - &self, - _: Box, - _: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Result { - let path = shellexpand::full(&self.path)?; - Ok(LanguageServerBinary { - path: PathBuf::from(path.deref()), - arguments: self.arguments.iter().map(|arg| arg.into()).collect(), - }) - } - - async fn cached_server_binary( - &self, - _: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Option { - let path = shellexpand::full(&self.path).ok()?; - Some(LanguageServerBinary { - path: PathBuf::from(path.deref()), - arguments: self.arguments.iter().map(|arg| arg.into()).collect(), - }) - } - - async fn installation_test_binary(&self, _: PathBuf) -> Option { - let path = shellexpand::full(&self.path).ok()?; - Some(LanguageServerBinary { - path: PathBuf::from(path.deref()), - arguments: self.arguments.iter().map(|arg| arg.into()).collect(), - }) - } - - async fn label_for_symbol( - &self, - name: &str, - symbol: SymbolKind, - language: &Arc, - ) -> Option { - label_for_symbol_next(name, symbol, language) - } -} - -fn label_for_symbol_next(name: &str, _: SymbolKind, language: &Arc) -> Option { - Some(CodeLabel { - runs: language.highlight_text(&name.into(), 0..name.len()), - text: name.to_string(), - filter_range: 0..name.len(), - }) -}