clangd_ext.rs

 1use std::sync::Arc;
 2
 3use ::serde::{Deserialize, Serialize};
 4use gpui::WeakEntity;
 5use language::{CachedLspAdapter, Diagnostic, DiagnosticSourceKind};
 6use lsp::LanguageServer;
 7use util::ResultExt as _;
 8
 9use crate::LspStore;
10
11pub const CLANGD_SERVER_NAME: &str = "clangd";
12const INACTIVE_REGION_MESSAGE: &str = "inactive region";
13const INACTIVE_DIAGNOSTIC_SEVERITY: lsp::DiagnosticSeverity = lsp::DiagnosticSeverity::INFORMATION;
14
15#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct InactiveRegionsParams {
18    pub text_document: lsp::OptionalVersionedTextDocumentIdentifier,
19    pub regions: Vec<lsp::Range>,
20}
21
22/// InactiveRegions is a clangd extension that marks regions of inactive code.
23pub struct InactiveRegions;
24
25impl lsp::notification::Notification for InactiveRegions {
26    type Params = InactiveRegionsParams;
27    const METHOD: &'static str = "textDocument/inactiveRegions";
28}
29
30pub fn is_inactive_region(diag: &Diagnostic) -> bool {
31    diag.is_unnecessary
32        && diag.severity == INACTIVE_DIAGNOSTIC_SEVERITY
33        && diag.message == INACTIVE_REGION_MESSAGE
34        && diag
35            .source
36            .as_ref()
37            .is_some_and(|v| v == CLANGD_SERVER_NAME)
38}
39
40pub fn is_lsp_inactive_region(diag: &lsp::Diagnostic) -> bool {
41    diag.severity == Some(INACTIVE_DIAGNOSTIC_SEVERITY)
42        && diag.message == INACTIVE_REGION_MESSAGE
43        && diag
44            .source
45            .as_ref()
46            .is_some_and(|v| v == CLANGD_SERVER_NAME)
47}
48
49pub fn register_notifications(
50    lsp_store: WeakEntity<LspStore>,
51    language_server: &LanguageServer,
52    adapter: Arc<CachedLspAdapter>,
53) {
54    if language_server.name().0 != CLANGD_SERVER_NAME {
55        return;
56    }
57    let server_id = language_server.server_id();
58
59    language_server
60        .on_notification::<InactiveRegions, _>({
61            let adapter = adapter.clone();
62            let this = lsp_store;
63
64            move |params: InactiveRegionsParams, cx| {
65                let adapter = adapter.clone();
66                this.update(cx, |this, cx| {
67                    let diagnostics = params
68                        .regions
69                        .into_iter()
70                        .map(|range| lsp::Diagnostic {
71                            range,
72                            severity: Some(INACTIVE_DIAGNOSTIC_SEVERITY),
73                            source: Some(CLANGD_SERVER_NAME.to_string()),
74                            message: INACTIVE_REGION_MESSAGE.to_string(),
75                            tags: Some(vec![lsp::DiagnosticTag::UNNECESSARY]),
76                            ..lsp::Diagnostic::default()
77                        })
78                        .collect();
79                    let mapped_diagnostics = lsp::PublishDiagnosticsParams {
80                        uri: params.text_document.uri,
81                        version: params.text_document.version,
82                        diagnostics,
83                    };
84                    this.merge_diagnostics(
85                        server_id,
86                        mapped_diagnostics,
87                        None,
88                        DiagnosticSourceKind::Pushed,
89                        &adapter.disk_based_diagnostic_sources,
90                        |_, diag, _| !is_inactive_region(diag),
91                        cx,
92                    )
93                    .log_err();
94                })
95                .ok();
96            }
97        })
98        .detach();
99}