diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 5c51a5c120..7957079f51 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result}; use collections::HashMap; use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt}; use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task}; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use postage::{barrier, prelude::Stream}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{json, value::RawValue, Value}; @@ -24,6 +24,7 @@ use std::{ ffi::OsString, fmt, io::Write, + ops::DerefMut, path::PathBuf, pin::Pin, sync::{ @@ -69,7 +70,7 @@ pub struct LanguageServer { next_id: AtomicI32, outbound_tx: channel::Sender, name: Arc, - capabilities: ServerCapabilities, + capabilities: RwLock, code_action_kinds: Option>, notification_handlers: Arc>>, response_handlers: Arc>>>, @@ -640,10 +641,13 @@ impl LanguageServer { ..Default::default() }), formatting: Some(DynamicRegistrationClientCapabilities { - dynamic_registration: None, + dynamic_registration: Some(true), + }), + range_formatting: Some(DynamicRegistrationClientCapabilities { + dynamic_registration: Some(true), }), on_type_formatting: Some(DynamicRegistrationClientCapabilities { - dynamic_registration: None, + dynamic_registration: Some(true), }), signature_help: Some(SignatureHelpClientCapabilities { signature_information: Some(SignatureInformationSettings { @@ -693,7 +697,7 @@ impl LanguageServer { if let Some(info) = response.server_info { self.name = info.name.into(); } - self.capabilities = response.capabilities; + self.capabilities = RwLock::new(response.capabilities); self.notify::(InitializedParams {})?; Ok(Arc::new(self)) @@ -908,8 +912,12 @@ impl LanguageServer { } /// Get the reported capabilities of the running language server. - pub fn capabilities(&self) -> &ServerCapabilities { - &self.capabilities + pub fn capabilities(&self) -> ServerCapabilities { + self.capabilities.read().clone() + } + + pub fn update_capabilities(&self, update: impl FnOnce(&mut ServerCapabilities)) { + update(self.capabilities.write().deref_mut()); } /// Get the id of the running language server. diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index b81f1acf7a..73caf1a59f 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -2596,7 +2596,7 @@ impl LspCommand for InlayHints { lsp_adapter.name.0.as_ref() == "typescript-language-server"; let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| { - let resolve_state = if InlayHints::can_resolve_inlays(lsp_server.capabilities()) { + let resolve_state = if InlayHints::can_resolve_inlays(&lsp_server.capabilities()) { ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone()) } else { ResolveState::Resolved diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 241dc1cc43..c884696b38 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3174,7 +3174,7 @@ impl Project { } async fn setup_pending_language_server( - this: WeakModel, + project: WeakModel, override_options: Option, pending_server: PendingLanguageServer, delegate: Arc, @@ -3193,7 +3193,7 @@ impl Project { language_server .on_notification::({ let adapter = adapter.clone(); - let this = this.clone(); + let this = project.clone(); move |mut params, mut cx| { let adapter = adapter.clone(); if let Some(this) = this.upgrade() { @@ -3247,7 +3247,7 @@ impl Project { // to these requests when initializing. language_server .on_request::({ - let this = this.clone(); + let this = project.clone(); move |params, mut cx| { let this = this.clone(); async move { @@ -3268,20 +3268,103 @@ impl Project { language_server .on_request::({ - let this = this.clone(); + let project = project.clone(); move |params, mut cx| { - let this = this.clone(); + let project = project.clone(); async move { for reg in params.registrations { - if reg.method == "workspace/didChangeWatchedFiles" { - if let Some(options) = reg.register_options { - let options = serde_json::from_value(options)?; - this.update(&mut cx, |this, cx| { - this.on_lsp_did_change_watched_files( - server_id, ®.id, options, cx, - ); - })?; + match reg.method.as_str() { + "workspace/didChangeWatchedFiles" => { + if let Some(options) = reg.register_options { + let options = serde_json::from_value(options)?; + project.update(&mut cx, |project, cx| { + project.on_lsp_did_change_watched_files( + server_id, ®.id, options, cx, + ); + })?; + } } + "textDocument/rangeFormatting" => { + project.update(&mut cx, |project, _| { + if let Some(server) = + project.language_server_for_id(server_id) + { + let options = reg + .register_options + .map(|options| { + serde_json::from_value::< + lsp::DocumentRangeFormattingOptions, + >( + options + ) + }) + .transpose()?; + let provider = match options { + None => OneOf::Left(true), + Some(options) => OneOf::Right(options), + }; + server.update_capabilities(|capabilities| { + capabilities.document_range_formatting_provider = + Some(provider); + }) + } + anyhow::Ok(()) + })??; + } + "textDocument/onTypeFormatting" => { + project.update(&mut cx, |project, _| { + if let Some(server) = + project.language_server_for_id(server_id) + { + let options = reg + .register_options + .map(|options| { + serde_json::from_value::< + lsp::DocumentOnTypeFormattingOptions, + >( + options + ) + }) + .transpose()?; + if let Some(options) = options { + server.update_capabilities(|capabilities| { + capabilities + .document_on_type_formatting_provider = + Some(options); + }) + } + } + anyhow::Ok(()) + })??; + } + "textDocument/formatting" => { + project.update(&mut cx, |project, _| { + if let Some(server) = + project.language_server_for_id(server_id) + { + let options = reg + .register_options + .map(|options| { + serde_json::from_value::< + lsp::DocumentFormattingOptions, + >( + options + ) + }) + .transpose()?; + let provider = match options { + None => OneOf::Left(true), + Some(options) => OneOf::Right(options), + }; + server.update_capabilities(|capabilities| { + capabilities.document_formatting_provider = + Some(provider); + }) + } + anyhow::Ok(()) + })??; + } + _ => log::warn!("unhandled capability registration: {reg:?}"), } } Ok(()) @@ -3292,17 +3375,55 @@ impl Project { language_server .on_request::({ - let this = this.clone(); + let this = project.clone(); move |params, mut cx| { - let this = this.clone(); + let project = this.clone(); async move { for unreg in params.unregisterations.iter() { - if unreg.method == "workspace/didChangeWatchedFiles" { - this.update(&mut cx, |this, cx| { - this.on_lsp_unregister_did_change_watched_files( - server_id, &unreg.id, cx, - ); - })?; + match unreg.method.as_str() { + "workspace/didChangeWatchedFiles" => { + project.update(&mut cx, |project, cx| { + project.on_lsp_unregister_did_change_watched_files( + server_id, &unreg.id, cx, + ); + })?; + } + "textDocument/rangeFormatting" => { + project.update(&mut cx, |project, _| { + if let Some(server) = + project.language_server_for_id(server_id) + { + server.update_capabilities(|capabilities| { + capabilities.document_range_formatting_provider = + None + }) + } + })?; + } + "textDocument/onTypeFormatting" => { + project.update(&mut cx, |project, _| { + if let Some(server) = + project.language_server_for_id(server_id) + { + server.update_capabilities(|capabilities| { + capabilities.document_on_type_formatting_provider = + None; + }) + } + })?; + } + "textDocument/formatting" => { + project.update(&mut cx, |project, _| { + if let Some(server) = + project.language_server_for_id(server_id) + { + server.update_capabilities(|capabilities| { + capabilities.document_formatting_provider = None; + }) + } + })?; + } + _ => log::warn!("unhandled capability unregistration: {unreg:?}"), } } Ok(()) @@ -3314,7 +3435,7 @@ impl Project { language_server .on_request::({ let adapter = adapter.clone(); - let this = this.clone(); + let this = project.clone(); move |params, cx| { Self::on_lsp_workspace_edit( this.clone(), @@ -3329,7 +3450,7 @@ impl Project { language_server .on_request::({ - let this = this.clone(); + let this = project.clone(); move |(), mut cx| { let this = this.clone(); async move { @@ -3348,7 +3469,7 @@ impl Project { language_server .on_request::({ - let this = this.clone(); + let this = project.clone(); let name = name.to_string(); move |params, mut cx| { let this = this.clone(); @@ -3387,7 +3508,7 @@ impl Project { language_server .on_notification::({ - let this = this.clone(); + let this = project.clone(); let name = name.to_string(); move |params, mut cx| { let this = this.clone(); @@ -3430,7 +3551,7 @@ impl Project { .detach(); language_server .on_notification::({ - let this = this.clone(); + let this = project.clone(); let name = name.to_string(); move |params, mut cx| { let this = this.clone(); @@ -3457,7 +3578,7 @@ impl Project { .detach(); language_server .on_notification::(move |params, mut cx| { - if let Some(this) = this.upgrade() { + if let Some(this) = project.upgrade() { this.update(&mut cx, |this, cx| { this.on_lsp_progress( params, @@ -6774,7 +6895,7 @@ impl Project { } else { return Task::ready(Ok(hint)); }; - if !InlayHints::can_resolve_inlays(lang_server.capabilities()) { + if !InlayHints::can_resolve_inlays(&lang_server.capabilities()) { return Task::ready(Ok(hint)); } @@ -7186,7 +7307,7 @@ impl Project { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); let status = request.status(); return cx.spawn(move |this, cx| async move { - if !request.check_capabilities(language_server.capabilities()) { + if !request.check_capabilities(&language_server.capabilities()) { return Ok(Default::default()); } @@ -7280,7 +7401,7 @@ impl Project { let scope = position.and_then(|position| snapshot.language_scope_at(position)); let mut response_results = self .language_servers_for_buffer(buffer.read(cx), cx) - .filter(|(_, server)| server_capabilities_check(server.capabilities())) + .filter(|(_, server)| server_capabilities_check(&server.capabilities())) .filter(|(adapter, _)| { scope .as_ref()