Repository: helix-editor/helix
File: helix-term/src/handlers/diagnostics.rs (Rust)

Code before:
        .text_document_diagnostic(doc.identifier(), doc.previous_diagnostic_id.clone())
    else {
        return;
    };

    let Some(uri) = doc.uri() else {
        return;
    };

    let provider = DiagnosticProvider::PullDiagnosticProvider(language_server.id());
    let document_id = doc.id();

    tokio::spawn(async move {
        match future.await {
            Ok(res) => {
                job::dispatch(move |editor, _| {
                    let response = match serde_json::from_value(res) {
                        Ok(result) => result,
                        Err(_) => return,
                    };

                    handle_pull_diagnostics_response(editor, response, provider, uri, document_id)
                })
                .await
            }
            Err(err) => log::error!("Pull diagnostic request failed: {err}"),
        }
    });
}

fn handle_pull_diagnostics_response(
    editor: &mut Editor,
    response: lsp::DocumentDiagnosticReport,
    provider: DiagnosticProvider,
    uri: Uri,
    document_id: DocumentId,
) {
    let Some(doc) = editor.document_mut(document_id) else {
        return;
    };

    match response {
        lsp::DocumentDiagnosticReport::Full(report) => {
            // Diagnostic for requested file
            editor.handle_lsp_diagnostics(
                provider,
                uri,
                None,
                report.full_document_diagnostic_report.items,
                report.full_document_diagnostic_report.result_id,
            );

Review comment (suggestion):
I believe we should also handle the error case here and look for `ServerCancelled` and retry based on the cancellation data: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticServerCancellationData

Lack of cancel+retry support for this seems to have bit the neovim+rust-analyzer combination lately: https://github.com/neovim/neovim/issues/30985 / https://github.com/neovim/neovim/pull/31345. I can reproduce getting some error logs from this line about the server cancelling the request when modifying a document shortly after opening a rust file (starting rust-analyzer)

Code after:

#[derive(Debug, Default)]
pub(super) struct PullAllDocumentsDiagnosticHandler {
    language_servers: HashSet<LanguageServerId>,
}

impl helix_event::AsyncHook for PullAllDocumentsDiagnosticHandler {
    type Event = PullAllDocumentsDiagnosticsEvent;

    fn handle_event(
        &mut self,
        event: Self::Event,
        _timeout: Option<tokio::time::Instant>,
    ) -> Option<tokio::time::Instant> {
        self.language_servers.extend(&event.language_servers);
        Some(Instant::now() + Duration::from_secs(1))
    }

    fn finish_debounce(&mut self) {
        let language_servers = mem::take(&mut self.language_servers);
        job::dispatch_blocking(move |editor, _| {
            let documents: Vec<_> = editor.documents.keys().copied().collect();

            for document in documents {
                request_document_diagnostics_for_language_severs(
                    editor,
                    document,
                    language_servers.clone(),
                );
            }
        })
    }
}

fn request_document_diagnostics_for_language_severs(
    editor: &mut Editor,
    doc_id: DocumentId,
    language_servers: HashSet<LanguageServerId>,
) {
    let Some(doc) = editor.document_mut(doc_id) else {
        return;
    };

    let cancel = doc.pull_diagnostic_controller.restart();

    let mut futures: FuturesUnordered<_> = language_servers
        .iter()
        .filter_map(|x| doc.language_servers().find(|y| &y.id() == x))
        .filter_map(|language_server| {
            let future = language_server
                .text_document_diagnostic(doc.identifier(), doc.previous_diagnostic_id.clone())?;