diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index bcdd045d70..22910ee4c6 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -173,21 +173,6 @@ pub(crate) fn fetch_native_diagnostics( let _p = tracing::info_span!("fetch_native_diagnostics").entered(); let _ctx = stdx::panic_context::enter("fetch_native_diagnostics".to_owned()); - let convert_diagnostic = - |line_index: &crate::line_index::LineIndex, d: ide::Diagnostic| lsp_types::Diagnostic { - range: lsp::to_proto::range(line_index, d.range.range), - severity: Some(lsp::to_proto::diagnostic_severity(d.severity)), - code: Some(lsp_types::NumberOrString::String(d.code.as_str().to_owned())), - code_description: Some(lsp_types::CodeDescription { - href: lsp_types::Url::parse(&d.code.url()).unwrap(), - }), - source: Some("rust-analyzer".to_owned()), - message: d.message, - related_information: None, - tags: d.unused.then(|| vec![lsp_types::DiagnosticTag::UNNECESSARY]), - data: None, - }; - // the diagnostics produced may point to different files not requested by the concrete request, // put those into here and filter later let mut odd_ones = Vec::new(); @@ -248,3 +233,22 @@ pub(crate) fn fetch_native_diagnostics( } diagnostics } + +pub(crate) fn convert_diagnostic( + line_index: &crate::line_index::LineIndex, + d: ide::Diagnostic, +) -> lsp_types::Diagnostic { + lsp_types::Diagnostic { + range: lsp::to_proto::range(line_index, d.range.range), + severity: Some(lsp::to_proto::diagnostic_severity(d.severity)), + code: Some(lsp_types::NumberOrString::String(d.code.as_str().to_owned())), + code_description: Some(lsp_types::CodeDescription { + href: lsp_types::Url::parse(&d.code.url()).unwrap(), + }), + source: Some("rust-analyzer".to_owned()), + message: d.message, + related_information: None, + tags: d.unused.then(|| vec![lsp_types::DiagnosticTag::UNNECESSARY]), + data: None, + } +} diff --git a/crates/rust-analyzer/src/handlers/dispatch.rs b/crates/rust-analyzer/src/handlers/dispatch.rs index ed7bf27843..21f95a945d 100644 --- a/crates/rust-analyzer/src/handlers/dispatch.rs +++ b/crates/rust-analyzer/src/handlers/dispatch.rs @@ -120,6 +120,30 @@ impl RequestDispatcher<'_> { self.on_with_thread_intent::(ThreadIntent::Worker, f) } + /// Dispatches a non-latency-sensitive request onto the thread pool. When the VFS is marked not + /// ready this will return a default constructed [`R::Result`]. + pub(crate) fn on_or( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result, + default: impl FnOnce() -> R::Result, + ) -> &mut Self + where + R: lsp_types::request::Request< + Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + Result: Serialize, + > + 'static, + { + if !self.global_state.vfs_done { + if let Some(lsp_server::Request { id, .. }) = + self.req.take_if(|it| it.method == R::METHOD) + { + self.global_state.respond(lsp_server::Response::new_ok(id, default())); + } + return self; + } + self.on_with_thread_intent::(ThreadIntent::Worker, f) + } + /// Dispatches a non-latency-sensitive request onto the thread pool. When the VFS is marked not /// ready this will return the parameter as is. pub(crate) fn on_identity( diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 5b4b678c0c..35c61a336e 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -4,6 +4,7 @@ use std::{ fs, io::Write as _, + ops::Not, process::{self, Stdio}, }; @@ -14,7 +15,7 @@ use ide::{ FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, }; -use ide_db::SymbolKind; +use ide_db::{FxHashMap, SymbolKind}; use itertools::Itertools; use lsp_server::ErrorCode; use lsp_types::{ @@ -36,6 +37,7 @@ use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ config::{Config, RustfmtConfig, WorkspaceSymbolConfig}, + diagnostics::convert_diagnostic, global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot}, hack_recover_crate_name, line_index::LineEndings, @@ -473,6 +475,68 @@ pub(crate) fn handle_on_type_formatting( Ok(Some(change)) } +pub(crate) fn handle_document_diagnostics( + snap: GlobalStateSnapshot, + params: lsp_types::DocumentDiagnosticParams, +) -> anyhow::Result { + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let source_root = snap.analysis.source_root_id(file_id)?; + let line_index = snap.file_line_index(file_id)?; + let config = snap.config.diagnostics(Some(source_root)); + if !config.enabled { + return Ok(lsp_types::DocumentDiagnosticReportResult::Report( + lsp_types::DocumentDiagnosticReport::Full( + lsp_types::RelatedFullDocumentDiagnosticReport { + related_documents: None, + full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport { + result_id: None, + items: vec![], + }, + }, + ), + )); + } + let supports_related = snap.config.text_document_diagnostic_related_document_support(); + + let mut related_documents = FxHashMap::default(); + let diagnostics = snap + .analysis + .full_diagnostics(&config, AssistResolveStrategy::None, file_id)? + .into_iter() + .filter_map(|d| { + let file = d.range.file_id; + let diagnostic = convert_diagnostic(&line_index, d); + if file == file_id { + return Some(diagnostic); + } + if supports_related { + related_documents.entry(file).or_insert_with(Vec::new).push(diagnostic); + } + None + }); + Ok(lsp_types::DocumentDiagnosticReportResult::Report( + lsp_types::DocumentDiagnosticReport::Full(lsp_types::RelatedFullDocumentDiagnosticReport { + full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport { + result_id: None, + items: diagnostics.collect(), + }, + related_documents: related_documents.is_empty().not().then(|| { + related_documents + .into_iter() + .map(|(id, items)| { + ( + to_proto::url(&snap, id), + lsp_types::DocumentDiagnosticReportKind::Full( + lsp_types::FullDocumentDiagnosticReport { result_id: None, items }, + ), + ) + }) + .collect() + }), + }), + )) +} + pub(crate) fn handle_document_symbol( snap: GlobalStateSnapshot, params: lsp_types::DocumentSymbolParams, diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs index 939593dd45..271a9c0f3d 100644 --- a/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/crates/rust-analyzer/src/lsp/capabilities.rs @@ -155,7 +155,15 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { "ssr": true, "workspaceSymbolScopeKindFiltering": true, })), - diagnostic_provider: None, + diagnostic_provider: Some(lsp_types::DiagnosticServerCapabilities::Options( + lsp_types::DiagnosticOptions { + identifier: None, + inter_file_dependencies: true, + // FIXME + workspace_diagnostics: false, + work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, + }, + )), inline_completion_provider: None, } } @@ -380,6 +388,15 @@ impl ClientCapabilities { .unwrap_or_default() } + pub fn text_document_diagnostic(&self) -> bool { + (|| -> _ { self.0.text_document.as_ref()?.diagnostic.as_ref() })().is_some() + } + + pub fn text_document_diagnostic_related_document_support(&self) -> bool { + (|| -> _ { self.0.text_document.as_ref()?.diagnostic.as_ref()?.related_document_support })() + == Some(true) + } + pub fn code_action_group(&self) -> bool { self.experimental_bool("codeActionGroup") } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 20be38a9e4..c531cd8d11 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -417,6 +417,8 @@ impl GlobalState { } } + let supports_diagnostic_pull_model = self.config.text_document_diagnostic(); + let client_refresh = became_quiescent || state_changed; if client_refresh { // Refresh semantic tokens if the client supports it. @@ -434,11 +436,21 @@ impl GlobalState { if self.config.inlay_hints_refresh() { self.send_request::((), |_, _| ()); } + + if supports_diagnostic_pull_model { + self.send_request::( + (), + |_, _| (), + ); + } } let project_or_mem_docs_changed = became_quiescent || state_changed || memdocs_added_or_removed; - if project_or_mem_docs_changed && self.config.publish_diagnostics(None) { + if project_or_mem_docs_changed + && !supports_diagnostic_pull_model + && self.config.publish_diagnostics(None) + { self.update_diagnostics(); } if project_or_mem_docs_changed && self.config.test_explorer() { @@ -1080,6 +1092,17 @@ impl GlobalState { .on_latency_sensitive::(handlers::handle_semantic_tokens_range) // FIXME: Some of these NO_RETRY could be retries if the file they are interested didn't change. // All other request handlers + .on_or::(handlers::handle_document_diagnostics, || lsp_types::DocumentDiagnosticReportResult::Report( + lsp_types::DocumentDiagnosticReport::Full( + lsp_types::RelatedFullDocumentDiagnosticReport { + related_documents: None, + full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport { + result_id: None, + items: vec![], + }, + }, + ), + )) .on::(handlers::handle_document_symbol) .on::(handlers::handle_folding_range) .on::(handlers::handle_signature_help)