Support textDocument/diagnostic specification (Pull diagnostics)#11315
Support textDocument/diagnostic specification (Pull diagnostics)#11315the-mikedavis merged 7 commits intohelix-editor:masterfrom SofusA:pull-diagnostics
Conversation
|
Thanks again for the reviews! The request is currently only being send on document changes, which makes a terrible user experience 😄 Is there a way to send this request when a document is opened, or should a workspace diagnostic event be send when the language server (which supports this feature) is started? Edit: By the way, i am using this branch daily for C# development, since it allows me to use the same language server as the C# vscode extension. So i am keeping the branch up to date with |
|
I did some experiments with this on roslyn language server. It might be that this language server just behaves weird (which it does on other stuff), but sending I also noticed that responses can either be a full or an related unchanged document report. At the moment, since we only listening on changes, we only get the full reports. This means that when i eg. rename something, i will not see diagnostics on related documents until i make a change. I am not sure if the specification suggest to pull diagnostics every time a document is viewed, or if we should pull diagnostics for all open documents on changes to a document. Edit: Pulling diagnostics for all open documents seems to work fine actually. |
|
My plan is to send pull diagnostics request:
|
|
Edit: This is no outdated. I also pull blocking when a document is opened or changed to buffer. I chose this model because i thought pulling for all open documents on edit was too aggressive. Pull diagnostics for language servers Document ids are being passed around, and i do some cloning of ids.
|
|
I've been trying this PR to see if it'll solve some issues using the ESLint LS from Of note, you do have to be using |
|
Thanks for testing!
I see the same behaviour with roslyn language server. I think this is because we are requesting diagnostics before the language server is ready. I was hoping that this would be the language servers responsibility. I am not sure what to do about this.
I fixed this by skipping the diagnostics request if the document id is not in the editor. Edit:
I am actually (without knowing) been using |
|
It looks like the crash is fixed now, thank you. I'd be curious on why the diagnostic request is trying to happen on a missing document ID (is there a memory leak somewhere?), but a little defensive programming didn't hurt anyone. I've been testing this PR against v4.10.0 and so far it's been working (unlike the master branch, since eslint needs this now I think), although I am still getting the issue where it doesn't pick up the first open file. With my testing just now, it seems that it also doesn't pick up if you open helix blank and then open a file via the picker, you then have to re-open the file (or make an edit) before it starts showing anything. IMO I think functionally it's fine for this PR, but it may be worth opening an issue afterwards as a follow-up to at least document that this is known dodgey behaviour. |
|
I looked into it, and i am not sending the pull diagnostics request, because of a race between the event, and the launching the language server (and registering capabilities). The event wins 😄
I am not completely sure why. I think part of it was because i was doing this asynchronous, where i collected all document_ids in a hashset, and then send all notifications after a debounce. I changed this to a synchronous hook instead. |
|
I did some experiments with |
|
I started to implement workspace diagnostics, but the language servers that i use don't responds to this request. Eslint returns an error and roslyn returns an empty array. So not much to work with here. I also think that this will be difficult to get to work in helix, because we would need the diagnostics picker to await the async request before displaying, or we would need a way to refresh diagnostics in the picker when the request has finished. I got refresh diagnostics working though |
See helix-editor/helix#11506, until helix-editor/helix#11315 lands I have to stay on vscode-langservers-extracted 4.8.0 or older
Have you tried building a newer version of Microsoft/vscode-eslint? Since you referred to version |
No i have only tried But this would require some refactoring of the diagnostics picker, and i think it should be in a separate pull request |
|
See the symbol picker for an example of a picker that waits on an LSP request. The workspace symbol picker is maybe also a good example but it's a sort of "dynamic picker" that re-requests symbols as you type with follow-up requests to the language server. |
Okay it does not look that difficult. I have however not been able to get response from Edit: Okay i gave it a very low effort shot using this fork which has a nix flake i could use. I looked at the capability responses from the 3 language servers i know of that supports pull diagnostics: A workaround could be to request diagnostics for all open buffers, for all language servers which support pull diagnostics, before opening workspace diagnostics. I suggest to wait for language servers to support this capability, and maybe handle pulling diagnostics for all documents for workspace diagnostics picker in a separate pull request. |
| editor.document(document_id), | ||
| editor.language_server_by_id(language_server_id), | ||
| ) { | ||
| pull_diagnostics_for_document(doc, language_server); |
There was a problem hiding this comment.
this can lead to runaway requests. We should not be making a new request right away since the cancellation is usually caused by a new edit for which we also make a new request anyways.
We should instead send an even to the handler here (which needs to track for which revision we already made a request and only rerequest if it makes sense)
| job::dispatch_blocking(move |editor, _| { | ||
| let documents = editor.documents.values(); | ||
|
|
||
| for document in documents { |
There was a problem hiding this comment.
if the server supports it we should use a workspace diagnostic request when pulling all documents.
|
@pascalkuthe Thanks for the review. It sounds like this needs some more features:
While i do agree that this would be great features to have, i am personally only invested in basic pull diagnostic support. I absolutely understand why this should be implemented, i just do not have the time (or skills 😄 ). I have been updating this branch for quite some time and will continue to do so, but i don't think i will be able to implement these features in the near future. |
|
Pascal and I can take the branch forward as we find time to work on it. I have some changes locally I was working on to track the ongoing requests anyways. We wouldn't mind getting something relatively simple in as a first step but there are some changes we'll want to make to ensure we don't spam the language server or allow duplicate in-flight requests |
Thanks. I appreciate your work 😃 I did play around with the cancel request. The result is great. I was able to reduce the debounce to 125 ms without issues, but I am not sure if the code holding ongoing requests is ideal. edit: Fat fingered the close with comment button. |
|
I will have some time to work on this again. But i could need some guidance for what need to be done before this is ready for review (or merge 😄 ). My current solution for cancelling on-going requests has been working very well, at least in C#. But it requires the consumer to mark the requests as done, which i don't find ideal. I do understand if you are busy or other pull requests has higher priority. |
|
There's some prior art in the LSP document colors code that should be a good reference. We want the pull request to be cancellable in case a document changes before the response arrives: helix/helix-view/src/document.rs Lines 209 to 211 in 02fe437 helix/helix-term/src/handlers/document_colors.rs Lines 53 to 91 in 02fe437 helix/helix-term/src/handlers/document_colors.rs Lines 178 to 179 in 02fe437 |
|
Nice! I have tried to have similar function signature close to |
|
The request cancellation has been working great. It leaves some additional tasks which i think should be in followup in separate pull requests:
This change is a very relevant for all developers who writes ruby or csharp, or uses |
|
I've been running this PR for a couple weeks, and it's been really nice to be able to use https://github.com/SofusA/csharp-language-server/ for C#. But I've been sometimes running into a bug when I try to open multiple files at once from the commandline. DescriptionWhen opening two or more files from the commandline ( Screencast.from.09-18-2025.06.19.57.PM.mp4Environment
Steps to Reproduce
file1.py (empty file)file2.pyclass Foo:
def bar(self):
pass
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
# _______________________________________________________________________________________________________________
Expected Resulthx should open both buffers, setting Observed Resulthx hangs, never showing the editor, only exiting after sending SIGINT (ctrl-c). MiscellaneousI wasn't able to root cause this, but here's a couple things I noticed while making a minimal-ish repro + using this PR:
|
Thanks for the detailed report. It sounds frustrating. Does the hang also occur on Can you add information about your OS, and try again on the rebased branch? |
|
This sounds possibly like something I had seen for LSP documentColors: ba54b6a. I didn't spend much time debugging that so I'm not sure it's related, but maybe it's a useful pointer. |
the-mikedavis
left a comment
There was a problem hiding this comment.
I think this is looking good, I only have some minor comments. At this point I'd be inclined to just merge this as-is and we can iterate on the hooks in follow-ups if needed
The job::dispatch_blocking attempted to run a job on the main thread, but the request_document_diagnostics_for_language_severs is already run on the main thread. We can restart the task controller for the document directly without the blocking job.
The HashSet doesn't have a defined order so we can equivalently use FuturesUnordered.
Otherwise changing a document once will cause it to be requested when the debounce finishes for another document's change for example. The HashSets would only grow. `std::mem::take` swaps the HashSets with new empty ones, effectively resetting them, and gives us the owned ones to pass into job::dispatch_blocking.
We can eagerly handle all successful responses, then wait 500ms and retry requests to all failed language servers which responded with the `retrigger_request` option.
|
I could reproduce the deadlock, fixed in 1184bc0 - looks like the main thread was blocking trying to acquire a lock already held by the main thread |
the-mikedavis
left a comment
There was a problem hiding this comment.
Thanks for working on this!
I think the hooks could be a bit fancier in the long run:
- We could empty out the HashSets in the AsyncHooks for any language servers that request
workspace/diagnostic/refresh. Looks like rust-analyzer requests that when it seestextDocument/didChangein my testing. When it does that we end up with multiple requests for the same document - Similarly in the PullAllDocumentsDiagnosticHandler we could avoid requesting diagnostics for any documents that were passed to the PullDiagnosticHandler - just tracking them in another HashSet
These can be done as follow-ups if necessary though. This is already working well in my testing so I will merge it down now 🚀
…ix-editor#11315) Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
…ix-editor#11315) Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
…ix-editor#11315) Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
…ix-editor#11315) Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
…ix-editor#11315) Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Closes #7757, if workspace diagnostics is not a requirement. I have not yet found a language server which supports this capability.
Handling of response was originally done by @woojiq in #7900. I was not able to include their git history since their branch has been deleted.
Tested with language servers:
eslintversion >4.10.0csharp-language-severversion >4.12.0ruby-lsprust-analyzerversion >2025-03-03Diagnostics are pulled when a document is changed for each language server with pull diagnostics feature with an async hook after a debounce period.
Diagnostics are also pulled when a document is opened, when changed to buffer and when a language server is successfully initiated. This is done without debounce.