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

Code before:
        }
    }
}

impl helix_event::AsyncHook for PullDiagnosticsHandler {
    type Event = PullDiagnosticsEvent;

    fn handle_event(
        &mut self,
        event: Self::Event,
        existing_debounce: Option<tokio::time::Instant>,
    ) -> Option<tokio::time::Instant> {
        if existing_debounce.is_none() {
            dispatch_pull_diagnostic_for_document(event.document_id);
        }

        self.document_ids.insert(event.document_id);
        Some(Instant::now() + Duration::from_millis(500))
    }

    fn finish_debounce(&mut self) {
        for document_id in self.document_ids.clone() {
            dispatch_pull_diagnostic_for_document(document_id);
        }
    }
}

fn dispatch_pull_diagnostic_for_document(document_id: DocumentId) {
    job::dispatch_blocking(move |editor, _| {
        let Some(doc) = editor.document(document_id) else {
            return;
        };

        let language_servers =
            doc.language_servers_with_feature(LanguageServerFeature::PullDiagnostics);

        for language_server in language_servers {
            pull_diagnostics_for_document(doc, language_server);
        }
    })
}

pub fn pull_diagnostics_for_document(
    doc: &helix_view::Document,
    language_server: &helix_lsp::Client,
) {
    let Some(future) = language_server
        .text_document_diagnostic(doc.identifier(), doc.previous_diagnostic_id.clone())
    else {
        return;
    };

Review comment (suggestion):
For the handler I think we will want some more nuanced logic. There are some tips at the bottom of the "Diagnostics Refresh" section in the spec that I think are meant to apply to pull diagnostics generally and are just out of place in the spec at the moment:

> Implementation Considerations
> 
> Generally the language server specification doesn’t enforce any specific client implementation since those usually depend on how the client UI behaves. However since diagnostics can be provided on a document and workspace level here are some tips:
> 
> * a client should pull actively for the document the users types in.
> * if the server signals inter file dependencies a client should also pull for visible documents to ensure accurate diagnostics. However the pull should happen less frequently.
> * if the server signals workspace pull support a client should also pull for workspace diagnostics. It is recommended for clients to implement partial result progress for the workspace pull to allow servers to keep the request open for a long time. If a server closes a workspace diagnostic pull request the client should re-trigger the request.

So the behavior should depend on whether the server declares that the language has inter-file dependencies. For language servers with inter-file dependencies we want to query for all "visible" documents attached to that language server. And for languages without inter-file dependencies we can request diagnostics only when that document changes, and we can pull on a lower debounce.

As for what is a "visible" document I think it's meant to be left up to the editor. In our case I think it's fine to query any open document (so we could look at `Editor`'s documents instead of holding a hashset here).

Code after:
        }
        Ok(())
    });

    register_hook!(move |event: &mut DocumentDidOpen<'_>| {
        request_document_diagnostics(event.editor, event.doc);

        Ok(())
    });

    register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
        let doc_ids: Vec<_> = event.editor.documents.keys().copied().collect();

        for doc_id in doc_ids {
            request_document_diagnostics(event.editor, doc_id);
        }

        Ok(())
    });
}

#[derive(Debug, Default)]
pub(super) struct PullDiagnosticsHandler {
    document_ids: HashSet<DocumentId>,
}

impl helix_event::AsyncHook for PullDiagnosticsHandler {
    type Event = PullDiagnosticsEvent;

    fn handle_event(
        &mut self,
        event: Self::Event,
        _timeout: Option<tokio::time::Instant>,
    ) -> Option<tokio::time::Instant> {
        self.document_ids.insert(event.document_id);
        Some(Instant::now() + Duration::from_millis(250))
    }

    fn finish_debounce(&mut self) {
        let document_ids = mem::take(&mut self.document_ids);
        job::dispatch_blocking(move |editor, _| {
            for document_id in document_ids {
                request_document_diagnostics(editor, document_id);
            }
        })
    }
}

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