From 02a85b12524ad47fce792c8b17027d6755ec3bbe Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 21 Sep 2023 18:06:00 -0700 Subject: [PATCH] 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, + }) + } +}