From 829ecda370226b21cf22d895732e1773c6838d40 Mon Sep 17 00:00:00 2001 From: "Naim A." <227396+naim94a@users.noreply.github.com> Date: Thu, 6 Mar 2025 23:30:05 +0200 Subject: [PATCH] lsp: Add support for clangd's `inactiveRegions` extension (#26146) Closes #13089 Here we use `experimental` to advertise our support for `inactiveRegions`. Note that clangd does not currently have a stable release that reads the `experimental` object (PR https://github.com/llvm/llvm-project/pull/116531), this can be tested with one of clangd's recent "unstable snapshots" in their [releases](https://github.com/clangd/clangd/releases). Release Notes: - Added support for clangd's `inactiveRegions` extension. ![Screen Recording 2025-03-05 at 22 39 58](https://github.com/user-attachments/assets/ceade8bd-4d8e-43c3-9520-ad44efa50d2f) --------- Co-authored-by: Peter Tripp Co-authored-by: Kirill Bulatov --- crates/editor/src/clangd_ext.rs | 4 +- crates/editor/src/rust_analyzer_ext.rs | 6 +- crates/languages/src/c.rs | 3 + crates/lsp/src/lsp.rs | 28 ------- crates/project/src/lsp_store.rs | 56 ++----------- crates/project/src/lsp_store/clangd_ext.rs | 75 +++++++++++++++++ .../src/{ => lsp_store}/lsp_ext_command.rs | 0 .../src/lsp_store/rust_analyzer_ext.rs | 83 +++++++++++++++++++ crates/project/src/project.rs | 1 - 9 files changed, 174 insertions(+), 82 deletions(-) create mode 100644 crates/project/src/lsp_store/clangd_ext.rs rename crates/project/src/{ => lsp_store}/lsp_ext_command.rs (100%) create mode 100644 crates/project/src/lsp_store/rust_analyzer_ext.rs diff --git a/crates/editor/src/clangd_ext.rs b/crates/editor/src/clangd_ext.rs index d85e6bedf5..4a145f0d33 100644 --- a/crates/editor/src/clangd_ext.rs +++ b/crates/editor/src/clangd_ext.rs @@ -7,7 +7,7 @@ use crate::lsp_ext::find_specific_language_server_in_selection; use crate::{element::register_action, Editor, SwitchSourceHeader}; -const CLANGD_SERVER_NAME: &str = "clangd"; +use project::lsp_store::clangd_ext::CLANGD_SERVER_NAME; fn is_c_language(language: &Language) -> bool { return language.name() == "C++".into() || language.name() == "C".into(); @@ -46,7 +46,7 @@ pub fn switch_source_header( project.request_lsp( buffer, project::LanguageServerToQuery::Other(server_to_query), - project::lsp_ext_command::SwitchSourceHeader, + project::lsp_store::lsp_ext_command::SwitchSourceHeader, cx, ) }); diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index d2a450bd15..0db5ab85e1 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -4,7 +4,7 @@ use anyhow::Context as _; use gpui::{App, AppContext as _, Context, Entity, Window}; use language::{Capability, Language}; use multi_buffer::MultiBuffer; -use project::lsp_ext_command::ExpandMacro; +use project::lsp_store::{lsp_ext_command::ExpandMacro, rust_analyzer_ext::RUST_ANALYZER_NAME}; use text::ToPointUtf16; use crate::{ @@ -12,8 +12,6 @@ use crate::{ ExpandMacroRecursively, OpenDocs, }; -const RUST_ANALYZER_NAME: &str = "rust-analyzer"; - fn is_rust_language(language: &Language) -> bool { language.name() == "Rust".into() } @@ -131,7 +129,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu project.request_lsp( buffer, project::LanguageServerToQuery::Other(server_to_query), - project::lsp_ext_command::OpenDocs { position }, + project::lsp_store::lsp_ext_command::OpenDocs { position }, cx, ) }); diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index f9bd5bb209..88be737e2b 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -278,6 +278,9 @@ impl super::LspAdapter for CLspAdapter { "textDocument": { "completion" : { "editsNearCursor": true + }, + "inactiveRegionsCapabilities": { + "inactiveRegions": true, } } }); diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d2bfbc10c5..961bb523f9 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -299,34 +299,6 @@ pub struct AdapterServerCapabilities { pub code_action_kinds: Option>, } -/// Experimental: Informs the end user about the state of the server -/// -/// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status) -#[derive(Debug)] -pub enum ServerStatus {} - -/// Other(String) variant to handle unknown values due to this still being experimental -#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub enum ServerHealthStatus { - Ok, - Warning, - Error, - Other(String), -} - -#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ServerStatusParams { - pub health: ServerHealthStatus, - pub message: Option, -} - -impl lsp_types::notification::Notification for ServerStatus { - type Params = ServerStatusParams; - const METHOD: &'static str = "experimental/serverStatus"; -} - impl LanguageServer { /// Starts a language server process. #[allow(clippy::too_many_arguments)] diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index e7e2708e2e..11e5384f60 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1,9 +1,12 @@ +pub mod clangd_ext; +pub mod lsp_ext_command; +pub mod rust_analyzer_ext; + use crate::{ buffer_store::{BufferStore, BufferStoreEvent}, deserialize_code_actions, environment::ProjectEnvironment, lsp_command::{self, *}, - lsp_ext_command, prettier_store::{self, PrettierStore, PrettierStoreEvent}, project_settings::{LspSettings, ProjectSettings}, project_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ProjectTree}, @@ -48,8 +51,8 @@ use lsp::{ FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher, InsertTextFormat, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, LspRequestFuture, MessageActionItem, MessageType, OneOf, - RenameFilesParams, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, WillRenameFiles, - WorkDoneProgressCancelParams, WorkspaceFolder, + RenameFilesParams, SymbolKind, TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, + WorkspaceFolder, }; use node_runtime::read_package_installed_version; use parking_lot::Mutex; @@ -841,50 +844,6 @@ impl LocalLspStore { } }) .detach(); - - language_server - .on_notification::({ - let this = this.clone(); - let name = name.to_string(); - move |params, mut cx| { - let this = this.clone(); - let name = name.to_string(); - if let Some(ref message) = params.message { - let message = message.trim(); - if !message.is_empty() { - let formatted_message = format!( - "Language server {name} (id {server_id}) status update: {message}" - ); - match params.health { - ServerHealthStatus::Ok => log::info!("{}", formatted_message), - ServerHealthStatus::Warning => log::warn!("{}", formatted_message), - ServerHealthStatus::Error => { - log::error!("{}", formatted_message); - let (tx, _rx) = smol::channel::bounded(1); - let request = LanguageServerPromptRequest { - level: PromptLevel::Critical, - message: params.message.unwrap_or_default(), - actions: Vec::new(), - response_channel: tx, - lsp_name: name.clone(), - }; - let _ = this - .update(&mut cx, |_, cx| { - cx.emit(LspStoreEvent::LanguageServerPrompt(request)); - }) - .ok(); - } - ServerHealthStatus::Other(status) => { - log::info!( - "Unknown server health: {status}\n{formatted_message}" - ) - } - } - } - } - } - }) - .detach(); language_server .on_notification::({ let this = this.clone(); @@ -970,6 +929,9 @@ impl LocalLspStore { } }) .detach(); + + rust_analyzer_ext::register_notifications(this.clone(), language_server); + clangd_ext::register_notifications(this, language_server, adapter); } fn shutdown_language_servers( diff --git a/crates/project/src/lsp_store/clangd_ext.rs b/crates/project/src/lsp_store/clangd_ext.rs new file mode 100644 index 0000000000..c8ce07e8b6 --- /dev/null +++ b/crates/project/src/lsp_store/clangd_ext.rs @@ -0,0 +1,75 @@ +use std::sync::Arc; + +use ::serde::{Deserialize, Serialize}; +use gpui::WeakEntity; +use language::CachedLspAdapter; +use lsp::LanguageServer; +use util::ResultExt as _; + +use crate::LspStore; + +pub const CLANGD_SERVER_NAME: &str = "clangd"; + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InactiveRegionsParams { + pub text_document: lsp::OptionalVersionedTextDocumentIdentifier, + pub regions: Vec, +} + +/// InactiveRegions is a clangd extension that marks regions of inactive code. +pub struct InactiveRegions; + +impl lsp::notification::Notification for InactiveRegions { + type Params = InactiveRegionsParams; + const METHOD: &'static str = "textDocument/inactiveRegions"; +} + +pub fn register_notifications( + lsp_store: WeakEntity, + language_server: &LanguageServer, + adapter: Arc, +) { + if language_server.name().0 != CLANGD_SERVER_NAME { + return; + } + let server_id = language_server.server_id(); + + language_server + .on_notification::({ + let adapter = adapter.clone(); + let this = lsp_store; + + move |params: InactiveRegionsParams, mut cx| { + let adapter = adapter.clone(); + this.update(&mut cx, |this, cx| { + let diagnostics = params + .regions + .into_iter() + .map(|range| lsp::Diagnostic { + range, + severity: Some(lsp::DiagnosticSeverity::INFORMATION), + source: Some(CLANGD_SERVER_NAME.to_string()), + message: "inactive region".to_string(), + tags: Some(vec![lsp::DiagnosticTag::UNNECESSARY]), + ..Default::default() + }) + .collect(); + let mapped_diagnostics = lsp::PublishDiagnosticsParams { + uri: params.text_document.uri, + version: params.text_document.version, + diagnostics, + }; + this.update_diagnostics( + server_id, + mapped_diagnostics, + &adapter.disk_based_diagnostic_sources, + cx, + ) + .log_err(); + }) + .ok(); + } + }) + .detach(); +} diff --git a/crates/project/src/lsp_ext_command.rs b/crates/project/src/lsp_store/lsp_ext_command.rs similarity index 100% rename from crates/project/src/lsp_ext_command.rs rename to crates/project/src/lsp_store/lsp_ext_command.rs diff --git a/crates/project/src/lsp_store/rust_analyzer_ext.rs b/crates/project/src/lsp_store/rust_analyzer_ext.rs new file mode 100644 index 0000000000..f69a213fa1 --- /dev/null +++ b/crates/project/src/lsp_store/rust_analyzer_ext.rs @@ -0,0 +1,83 @@ +use ::serde::{Deserialize, Serialize}; +use gpui::{PromptLevel, WeakEntity}; +use lsp::LanguageServer; + +use crate::{LanguageServerPromptRequest, LspStore, LspStoreEvent}; + +pub const RUST_ANALYZER_NAME: &str = "rust-analyzer"; + +/// Experimental: Informs the end user about the state of the server +/// +/// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status) +#[derive(Debug)] +enum ServerStatus {} + +/// Other(String) variant to handle unknown values due to this still being experimental +#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +enum ServerHealthStatus { + Ok, + Warning, + Error, + Other(String), +} + +#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +struct ServerStatusParams { + pub health: ServerHealthStatus, + pub message: Option, +} + +impl lsp::notification::Notification for ServerStatus { + type Params = ServerStatusParams; + const METHOD: &'static str = "experimental/serverStatus"; +} + +pub fn register_notifications(lsp_store: WeakEntity, language_server: &LanguageServer) { + let name = language_server.name(); + let server_id = language_server.server_id(); + + let this = lsp_store; + + language_server + .on_notification::({ + let name = name.to_string(); + move |params, mut cx| { + let this = this.clone(); + let name = name.to_string(); + if let Some(ref message) = params.message { + let message = message.trim(); + if !message.is_empty() { + let formatted_message = format!( + "Language server {name} (id {server_id}) status update: {message}" + ); + match params.health { + ServerHealthStatus::Ok => log::info!("{}", formatted_message), + ServerHealthStatus::Warning => log::warn!("{}", formatted_message), + ServerHealthStatus::Error => { + log::error!("{}", formatted_message); + let (tx, _rx) = smol::channel::bounded(1); + let request = LanguageServerPromptRequest { + level: PromptLevel::Critical, + message: params.message.unwrap_or_default(), + actions: Vec::new(), + response_channel: tx, + lsp_name: name.clone(), + }; + let _ = this + .update(&mut cx, |_, cx| { + cx.emit(LspStoreEvent::LanguageServerPrompt(request)); + }) + .ok(); + } + ServerHealthStatus::Other(status) => { + log::info!("Unknown server health: {status}\n{formatted_message}") + } + } + } + } + } + }) + .detach(); +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 09b495502f..bdc8404e6b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5,7 +5,6 @@ pub mod debounced_delay; pub mod git; pub mod image_store; pub mod lsp_command; -pub mod lsp_ext_command; pub mod lsp_store; pub mod prettier_store; pub mod project_settings;