From 0cceb3fdf1bcc8823688345334ba178d9111cc5c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 20 Sep 2023 06:55:24 -0700 Subject: [PATCH 1/6] Get nextLS running --- Cargo.lock | 1 + assets/settings/default.json | 21 +++++++++++++++ crates/semantic_index/examples/eval.rs | 2 +- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 36 +++++++++++++++++++++----- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 2 +- 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b42e11d326..8a87cb9986 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9861,6 +9861,7 @@ dependencies = [ "rpc", "rsa", "rust-embed", + "schemars", "search", "semantic_index", "serde", diff --git a/assets/settings/default.json b/assets/settings/default.json index 86def54d32..126407a32d 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -373,6 +373,27 @@ "enabled": false, "reindexing_delay_seconds": 600 }, + // Settings specific to our elixir integration + "elixir": { + // Set Zed to use the experimental Next LS LSP server. + // 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 locally running version of the next Next LS LSP server, + // on a specific port: + // "next": { + // "local": { + // "port": 4000 + // } + // }, + // + "next": "off" + }, // Different settings for specific languages. "languages": { "Plain Text": { diff --git a/crates/semantic_index/examples/eval.rs b/crates/semantic_index/examples/eval.rs index 15406cf63e..2994cb29b6 100644 --- a/crates/semantic_index/examples/eval.rs +++ b/crates/semantic_index/examples/eval.rs @@ -456,7 +456,7 @@ fn main() { let languages = Arc::new(languages); let node_runtime = RealNodeRuntime::new(http.clone()); - languages::init(languages.clone(), node_runtime.clone()); + languages::init(languages.clone(), node_runtime.clone(), cx); language::init(cx); project::Project::init(&client, cx); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index b2339f998f..fb41f7a349 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -99,6 +99,7 @@ rust-embed.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true +schemars.workspace = true simplelog = "0.9" smallvec.workspace = true smol.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0b1fa750c0..75674e78e0 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -1,13 +1,17 @@ use anyhow::Context; +use gpui::AppContext; pub use language::*; use node_runtime::NodeRuntime; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; use util::asset_str; +use self::elixir_next::ElixirSettings; + mod c; mod css; mod elixir; +mod elixir_next; mod go; mod html; mod json; @@ -37,7 +41,13 @@ mod yaml; #[exclude = "*.rs"] struct LanguageDir; -pub fn init(languages: Arc, node_runtime: Arc) { +pub fn init( + languages: Arc, + node_runtime: Arc, + cx: &mut AppContext, +) { + settings::register::(cx); + let language = |name, grammar, adapters| { languages.register(name, load_config(name), grammar, adapters, load_queries) }; @@ -61,11 +71,25 @@ pub fn init(languages: Arc, node_runtime: Arc Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); - language( - "elixir", - tree_sitter_elixir::language(), - vec![Arc::new(elixir::ElixirLspAdapter)], - ); + + match settings::get::(cx).next { + elixir_next::ElixirNextSetting::Off => language( + "elixir", + tree_sitter_elixir::language(), + vec![Arc::new(elixir::ElixirLspAdapter)], + ), + elixir_next::ElixirNextSetting::On => language( + "elixir", + tree_sitter_elixir::language(), + vec![Arc::new(elixir_next::BundledNextLspAdapter)], + ), + elixir_next::ElixirNextSetting::Local { port } => unimplemented!(), /*language( + "elixir", + tree_sitter_elixir::language(), + vec![Arc::new(elixir_next::LocalNextLspAdapter { port })], + )*/ + } + language( "go", tree_sitter_go::language(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c800e4e110..7c69bc2db1 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -134,7 +134,7 @@ fn main() { let languages = Arc::new(languages); let node_runtime = RealNodeRuntime::new(http.clone()); - languages::init(languages.clone(), node_runtime.clone()); + languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); let channel_store = cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d968a92646..8a74522df1 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2392,7 +2392,7 @@ mod tests { languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); let node_runtime = node_runtime::FakeNodeRuntime::new(); - languages::init(languages.clone(), node_runtime); + languages::init(languages.clone(), node_runtime, cx); for name in languages.language_names() { languages.language_for_name(&name); } From 02a85b12524ad47fce792c8b17027d6755ec3bbe Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 21 Sep 2023 18:06:00 -0700 Subject: [PATCH 2/6] Add local next LSP adapter --- crates/lsp/src/lsp.rs | 4 +- crates/project/src/project.rs | 20 +- crates/project_symbols/src/project_symbols.rs | 2 +- crates/zed/src/languages.rs | 14 +- crates/zed/src/languages/elixir_next.rs | 178 ++++++++++++++++++ 5 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 crates/zed/src/languages/elixir_next.rs diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index dcfce4f1fb..4aa0e5cd10 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -712,11 +712,11 @@ impl LanguageServer { } } - pub fn name<'a>(self: &'a Arc) -> &'a str { + pub fn name(&self) -> &str { &self.name } - pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { + pub fn capabilities(&self) -> &ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b4e698e08a..98508e1bee 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2267,11 +2267,13 @@ impl Project { }; for (_, _, server) in self.language_servers_for_worktree(worktree_id) { + let text = include_text(server.as_ref()).then(|| buffer.read(cx).text()); + server .notify::( lsp::DidSaveTextDocumentParams { text_document: text_document.clone(), - text: None, + text, }, ) .log_err(); @@ -8274,3 +8276,19 @@ async fn wait_for_loading_buffer( receiver.next().await; } } + +fn include_text(server: &lsp::LanguageServer) -> bool { + server + .capabilities() + .text_document_sync + .as_ref() + .and_then(|sync| match sync { + lsp::TextDocumentSyncCapability::Kind(_) => None, + lsp::TextDocumentSyncCapability::Options(options) => options.save.as_ref(), + }) + .and_then(|save_options| match save_options { + lsp::TextDocumentSyncSaveOptions::Supported(_) => None, + lsp::TextDocumentSyncSaveOptions::SaveOptions(options) => options.include_text, + }) + .unwrap_or(false) +} diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index e88aee5dcf..3273d5c6e6 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -69,7 +69,7 @@ impl ProjectSymbolsDelegate { &self.external_match_candidates, query, false, - MAX_MATCHES - visible_matches.len(), + MAX_MATCHES - visible_matches.len().min(MAX_MATCHES), &Default::default(), cx.background().clone(), )); diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 75674e78e0..0d1c2a9d36 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -72,22 +72,20 @@ pub fn init( ], ); - match settings::get::(cx).next { + match &settings::get::(cx).next { elixir_next::ElixirNextSetting::Off => language( "elixir", tree_sitter_elixir::language(), vec![Arc::new(elixir::ElixirLspAdapter)], ), - elixir_next::ElixirNextSetting::On => language( + elixir_next::ElixirNextSetting::On => todo!(), + elixir_next::ElixirNextSetting::Local { path } => language( "elixir", tree_sitter_elixir::language(), - vec![Arc::new(elixir_next::BundledNextLspAdapter)], + vec![Arc::new(elixir_next::LocalNextLspAdapter { + path: path.clone(), + })], ), - elixir_next::ElixirNextSetting::Local { port } => unimplemented!(), /*language( - "elixir", - tree_sitter_elixir::language(), - vec![Arc::new(elixir_next::LocalNextLspAdapter { port })], - )*/ } language( diff --git a/crates/zed/src/languages/elixir_next.rs b/crates/zed/src/languages/elixir_next.rs new file mode 100644 index 0000000000..a25ada92b8 --- /dev/null +++ b/crates/zed/src/languages/elixir_next.rs @@ -0,0 +1,178 @@ +use anyhow::Result; +use async_trait::async_trait; +pub use language::*; +use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use settings::Setting; +use std::{any::Any, path::PathBuf, sync::Arc}; + +#[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 }, +} + +#[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 LocalNextLspAdapter { + pub path: String, +} + +#[async_trait] +impl LspAdapter for LocalNextLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("elixir-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 { + Ok(LanguageServerBinary { + path: self.path.clone().into(), + arguments: vec!["--stdio".into()], + }) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + Some(LanguageServerBinary { + path: self.path.clone().into(), + arguments: vec!["--stdio".into()], + }) + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + Some(LanguageServerBinary { + path: self.path.clone().into(), + arguments: vec!["--stdio".into()], + }) + } + + async fn label_for_completion( + &self, + completion: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + match completion.kind.zip(completion.detail.as_ref()) { + Some((_, detail)) if detail.starts_with("(function)") => { + let text = detail.strip_prefix("(function) ")?; + let filter_range = 0..text.find('(').unwrap_or(text.len()); + let source = Rope::from(format!("def {text}").as_str()); + let runs = language.highlight_text(&source, 4..4 + text.len()); + return Some(CodeLabel { + text: text.to_string(), + runs, + filter_range, + }); + } + Some((_, detail)) if detail.starts_with("(macro)") => { + let text = detail.strip_prefix("(macro) ")?; + let filter_range = 0..text.find('(').unwrap_or(text.len()); + let source = Rope::from(format!("defmacro {text}").as_str()); + let runs = language.highlight_text(&source, 9..9 + text.len()); + return Some(CodeLabel { + text: text.to_string(), + runs, + filter_range, + }); + } + Some(( + CompletionItemKind::CLASS + | CompletionItemKind::MODULE + | CompletionItemKind::INTERFACE + | CompletionItemKind::STRUCT, + _, + )) => { + let filter_range = 0..completion + .label + .find(" (") + .unwrap_or(completion.label.len()); + let text = &completion.label[filter_range.clone()]; + let source = Rope::from(format!("defmodule {text}").as_str()); + let runs = language.highlight_text(&source, 10..10 + text.len()); + return Some(CodeLabel { + text: completion.label.clone(), + runs, + filter_range, + }); + } + _ => {} + } + + None + } + + async fn label_for_symbol( + &self, + name: &str, + kind: SymbolKind, + language: &Arc, + ) -> Option { + let (text, filter_range, display_range) = match kind { + SymbolKind::METHOD | SymbolKind::FUNCTION => { + let text = format!("def {}", name); + let filter_range = 4..4 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => { + let text = format!("defmodule {}", name); + let filter_range = 10..10 + name.len(); + let display_range = 0..filter_range.end; + (text, filter_range, display_range) + } + _ => return None, + }; + + Some(CodeLabel { + runs: language.highlight_text(&text.as_str().into(), display_range.clone()), + text: text[display_range].to_string(), + filter_range, + }) + } +} From 052cb459a6d1666a5a1419b6c8a20a7e73e5463d Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 24 Sep 2023 04:59:55 -0700 Subject: [PATCH 3/6] Improve lsp log viewer's behavior in the presence of LSP restarts Improve settings interface to local LSP --- Cargo.lock | 1 + assets/settings/default.json | 6 +-- crates/language_tools/src/lsp_log.rs | 50 +++++++++++++++++++++++-- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 3 +- crates/zed/src/languages/elixir_next.rs | 25 +++++++++---- 6 files changed, 71 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a87cb9986..4f43215bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9868,6 +9868,7 @@ dependencies = [ "serde_derive", "serde_json", "settings", + "shellexpand", "simplelog", "smallvec", "smol", diff --git a/assets/settings/default.json b/assets/settings/default.json index 126407a32d..fbc40b4756 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -384,11 +384,11 @@ // "next": "off" // 2. Use a bundled version of the next Next LS LSP server // "next": "on", - // 3. Use a locally running version of the next Next LS LSP server, - // on a specific port: + // 3. Use a local build of the next Next LS LSP server: // "next": { // "local": { - // "port": 4000 + // "path": "~/next-ls/bin/start", + // "arguments": ["--stdio"] // } // }, // diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 587e6ed25a..d2ad8fac90 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -8,8 +8,8 @@ use gpui::{ ParentElement, Stack, }, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, View, ViewContext, - ViewHandle, WeakModelHandle, + AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription, View, + ViewContext, ViewHandle, WeakModelHandle, }; use language::{Buffer, LanguageServerId, LanguageServerName}; use lsp::IoKind; @@ -52,10 +52,12 @@ pub struct LspLogView { current_server_id: Option, is_showing_rpc_trace: bool, project: ModelHandle, + _log_store_subscription: Subscription, } pub struct LspLogToolbarItemView { log_view: Option>, + _log_view_subscription: Option, menu_open: bool, } @@ -346,12 +348,49 @@ impl LspLogView { .get(&project.downgrade()) .and_then(|project| project.servers.keys().copied().next()); let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); + let _log_store_subscription = cx.observe(&log_store, |this, store, cx| { + (|| -> Option<()> { + let project_state = store.read(cx).projects.get(&this.project.downgrade())?; + if let Some(current_lsp) = this.current_server_id { + if !project_state.servers.contains_key(¤t_lsp) { + if let Some(server) = project_state.servers.iter().next() { + if this.is_showing_rpc_trace { + this.show_rpc_trace_for_server(*server.0, cx) + } else { + this.show_logs_for_server(*server.0, cx) + } + } else { + this.current_server_id = None; + this.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + editor.clear(cx); + editor.set_read_only(true); + }); + cx.notify(); + } + } + } else { + if let Some(server) = project_state.servers.iter().next() { + if this.is_showing_rpc_trace { + this.show_rpc_trace_for_server(*server.0, cx) + } else { + this.show_logs_for_server(*server.0, cx) + } + } + } + + Some(()) + })(); + + cx.notify(); + }); let mut this = Self { editor: Self::editor_for_buffer(project.clone(), buffer, cx), project, log_store, current_server_id: None, is_showing_rpc_trace: false, + _log_store_subscription, }; if let Some(server_id) = server_id { this.show_logs_for_server(server_id, cx); @@ -556,18 +595,22 @@ impl ToolbarItemView for LspLogToolbarItemView { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, - _: &mut ViewContext, + cx: &mut ViewContext, ) -> workspace::ToolbarItemLocation { self.menu_open = false; if let Some(item) = active_pane_item { if let Some(log_view) = item.downcast::() { self.log_view = Some(log_view.clone()); + self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| { + cx.notify(); + })); return ToolbarItemLocation::PrimaryLeft { flex: Some((1., false)), }; } } self.log_view = None; + self._log_view_subscription = None; ToolbarItemLocation::Hidden } } @@ -697,6 +740,7 @@ impl LspLogToolbarItemView { Self { menu_open: false, log_view: None, + _log_view_subscription: None, } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index fb41f7a349..ab6844d2de 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -62,6 +62,7 @@ rpc = { path = "../rpc" } settings = { path = "../settings" } feature_flags = { path = "../feature_flags" } sum_tree = { path = "../sum_tree" } +shellexpand = "2.1.0" text = { path = "../text" } terminal_view = { path = "../terminal_view" } theme = { path = "../theme" } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 0d1c2a9d36..2a21e4035f 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -79,11 +79,12 @@ pub fn init( vec![Arc::new(elixir::ElixirLspAdapter)], ), elixir_next::ElixirNextSetting::On => todo!(), - elixir_next::ElixirNextSetting::Local { path } => language( + elixir_next::ElixirNextSetting::Local { path, arguments } => language( "elixir", tree_sitter_elixir::language(), vec![Arc::new(elixir_next::LocalNextLspAdapter { path: path.clone(), + arguments: arguments.clone(), })], ), } diff --git a/crates/zed/src/languages/elixir_next.rs b/crates/zed/src/languages/elixir_next.rs index a25ada92b8..9b12572ec3 100644 --- a/crates/zed/src/languages/elixir_next.rs +++ b/crates/zed/src/languages/elixir_next.rs @@ -5,7 +5,7 @@ use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::Setting; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{any::Any, ops::Deref, path::PathBuf, sync::Arc}; #[derive(Clone, Serialize, Deserialize, JsonSchema)] pub struct ElixirSettings { @@ -17,7 +17,10 @@ pub struct ElixirSettings { pub enum ElixirNextSetting { Off, On, - Local { path: String }, + Local { + path: String, + arguments: Vec, + }, } #[derive(Clone, Serialize, Default, Deserialize, JsonSchema)] @@ -44,6 +47,7 @@ impl Setting for ElixirSettings { pub struct LocalNextLspAdapter { pub path: String, + pub arguments: Vec, } #[async_trait] @@ -69,9 +73,10 @@ impl LspAdapter for LocalNextLspAdapter { _: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { + let path = shellexpand::full(&self.path)?; Ok(LanguageServerBinary { - path: self.path.clone().into(), - arguments: vec!["--stdio".into()], + path: PathBuf::from(path.deref()), + arguments: self.arguments.iter().map(|arg| arg.into()).collect(), }) } @@ -80,19 +85,22 @@ impl LspAdapter for LocalNextLspAdapter { _: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { + let path = shellexpand::full(&self.path).ok()?; Some(LanguageServerBinary { - path: self.path.clone().into(), - arguments: vec!["--stdio".into()], + 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: self.path.clone().into(), - arguments: vec!["--stdio".into()], + path: PathBuf::from(path.deref()), + arguments: self.arguments.iter().map(|arg| arg.into()).collect(), }) } + // TODO: async fn label_for_completion( &self, completion: &lsp::CompletionItem, @@ -147,6 +155,7 @@ impl LspAdapter for LocalNextLspAdapter { None } + // TODO: async fn label_for_symbol( &self, name: &str, From 8b63e45f0baa9d994d29ca5cee203fa5d48d4830 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 24 Sep 2023 05:08:05 -0700 Subject: [PATCH 4/6] Implement LSP adapter methods for syntax highlighting --- crates/zed/src/languages/elixir_next.rs | 82 ++----------------------- 1 file changed, 5 insertions(+), 77 deletions(-) diff --git a/crates/zed/src/languages/elixir_next.rs b/crates/zed/src/languages/elixir_next.rs index 9b12572ec3..cd0d13c712 100644 --- a/crates/zed/src/languages/elixir_next.rs +++ b/crates/zed/src/languages/elixir_next.rs @@ -1,7 +1,7 @@ use anyhow::Result; use async_trait::async_trait; pub use language::*; -use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; +use lsp::{LanguageServerBinary, SymbolKind}; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::Setting; @@ -100,88 +100,16 @@ impl LspAdapter for LocalNextLspAdapter { }) } - // TODO: - async fn label_for_completion( - &self, - completion: &lsp::CompletionItem, - language: &Arc, - ) -> Option { - match completion.kind.zip(completion.detail.as_ref()) { - Some((_, detail)) if detail.starts_with("(function)") => { - let text = detail.strip_prefix("(function) ")?; - let filter_range = 0..text.find('(').unwrap_or(text.len()); - let source = Rope::from(format!("def {text}").as_str()); - let runs = language.highlight_text(&source, 4..4 + text.len()); - return Some(CodeLabel { - text: text.to_string(), - runs, - filter_range, - }); - } - Some((_, detail)) if detail.starts_with("(macro)") => { - let text = detail.strip_prefix("(macro) ")?; - let filter_range = 0..text.find('(').unwrap_or(text.len()); - let source = Rope::from(format!("defmacro {text}").as_str()); - let runs = language.highlight_text(&source, 9..9 + text.len()); - return Some(CodeLabel { - text: text.to_string(), - runs, - filter_range, - }); - } - Some(( - CompletionItemKind::CLASS - | CompletionItemKind::MODULE - | CompletionItemKind::INTERFACE - | CompletionItemKind::STRUCT, - _, - )) => { - let filter_range = 0..completion - .label - .find(" (") - .unwrap_or(completion.label.len()); - let text = &completion.label[filter_range.clone()]; - let source = Rope::from(format!("defmodule {text}").as_str()); - let runs = language.highlight_text(&source, 10..10 + text.len()); - return Some(CodeLabel { - text: completion.label.clone(), - runs, - filter_range, - }); - } - _ => {} - } - - None - } - - // TODO: async fn label_for_symbol( &self, name: &str, - kind: SymbolKind, + _: SymbolKind, language: &Arc, ) -> Option { - let (text, filter_range, display_range) = match kind { - SymbolKind::METHOD | SymbolKind::FUNCTION => { - let text = format!("def {}", name); - let filter_range = 4..4 + name.len(); - let display_range = 0..filter_range.end; - (text, filter_range, display_range) - } - SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => { - let text = format!("defmodule {}", name); - let filter_range = 10..10 + name.len(); - let display_range = 0..filter_range.end; - (text, filter_range, display_range) - } - _ => return None, - }; - Some(CodeLabel { - runs: language.highlight_text(&text.as_str().into(), display_range.clone()), - text: text[display_range].to_string(), - filter_range, + runs: language.highlight_text(&name.into(), 0..name.len()), + text: name.to_string(), + filter_range: 0..name.len(), }) } } From ad7c1f3c81622184f2ac6e9678fe29871ea0bf2a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 25 Sep 2023 10:40:20 -0700 Subject: [PATCH 5/6] Download next-ls automatically from github --- crates/zed/src/languages.rs | 6 +- crates/zed/src/languages/elixir_next.rs | 170 ++++++++++++++++++++++-- 2 files changed, 166 insertions(+), 10 deletions(-) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 2a21e4035f..be8d05256a 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -78,7 +78,11 @@ pub fn init( tree_sitter_elixir::language(), vec![Arc::new(elixir::ElixirLspAdapter)], ), - elixir_next::ElixirNextSetting::On => todo!(), + elixir_next::ElixirNextSetting::On => language( + "elixir", + tree_sitter_elixir::language(), + vec![Arc::new(elixir_next::NextLspAdapter)], + ), elixir_next::ElixirNextSetting::Local { path, arguments } => language( "elixir", tree_sitter_elixir::language(), diff --git a/crates/zed/src/languages/elixir_next.rs b/crates/zed/src/languages/elixir_next.rs index cd0d13c712..6293d4bfb9 100644 --- a/crates/zed/src/languages/elixir_next.rs +++ b/crates/zed/src/languages/elixir_next.rs @@ -1,11 +1,19 @@ -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; +use async_compression::futures::bufread::GzipDecoder; +use async_tar::Archive; use async_trait::async_trait; pub use language::*; use lsp::{LanguageServerBinary, SymbolKind}; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::Setting; -use std::{any::Any, ops::Deref, path::PathBuf, sync::Arc}; +use smol::{fs, io::BufReader, 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 { @@ -45,6 +53,146 @@ impl Setting for ElixirSettings { } } +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, @@ -53,7 +201,7 @@ pub struct LocalNextLspAdapter { #[async_trait] impl LspAdapter for LocalNextLspAdapter { async fn name(&self) -> LanguageServerName { - LanguageServerName("elixir-next-ls".into()) + LanguageServerName("local-next-ls".into()) } fn short_name(&self) -> &'static str { @@ -103,13 +251,17 @@ impl LspAdapter for LocalNextLspAdapter { async fn label_for_symbol( &self, name: &str, - _: SymbolKind, + symbol: SymbolKind, language: &Arc, ) -> Option { - Some(CodeLabel { - runs: language.highlight_text(&name.into(), 0..name.len()), - text: name.to_string(), - filter_range: 0..name.len(), - }) + 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(), + }) +} From c2fca054ae32c2ee0fd6646dc598f517ec2796d7 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 25 Sep 2023 10:46:09 -0700 Subject: [PATCH 6/6] Fix compile and test errors --- crates/zed/src/languages/elixir_next.rs | 5 ++--- crates/zed/src/zed.rs | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zed/src/languages/elixir_next.rs b/crates/zed/src/languages/elixir_next.rs index 6293d4bfb9..f5a77c7568 100644 --- a/crates/zed/src/languages/elixir_next.rs +++ b/crates/zed/src/languages/elixir_next.rs @@ -1,13 +1,12 @@ use anyhow::{anyhow, bail, Result}; -use async_compression::futures::bufread::GzipDecoder; -use async_tar::Archive; + 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, io::BufReader, stream::StreamExt}; +use smol::{fs, stream::StreamExt}; use std::{any::Any, env::consts, ops::Deref, path::PathBuf, sync::Arc}; use util::{ async_iife, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8a74522df1..58eb1e453e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2388,6 +2388,7 @@ mod tests { #[gpui::test] fn test_bundled_languages(cx: &mut AppContext) { + cx.set_global(SettingsStore::test(cx)); let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background().clone()); let languages = Arc::new(languages);