-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LSP doesn't handle diagnostic dynamic registration correctly #2561
Comments
relevant client/registerCapability from the logs formatted as JSON{
"registrations": [
{
"id": "ee01d1d1-9547-4581-ae65-c564e7fc48c7",
"method": "textDocument/diagnostic",
"registerOptions": {
"workDoneProgress": true,
"identifier": "HotReloadDiagnostics",
"interFileDependencies": true,
"workspaceDiagnostics": true
}
},
{
"id": "5a7194a3-66ed-4bd4-b781-0db332716c34",
"method": "textDocument/diagnostic",
"registerOptions": {
"workDoneProgress": true,
"identifier": "enc",
"interFileDependencies": true,
"workspaceDiagnostics": true
}
},
{
"id": "543a1e23-2e55-4aae-b548-8058cc83c1d3",
"method": "textDocument/diagnostic",
"registerOptions": {
"identifier": "DocumentAnalyzerSemantic",
"interFileDependencies": true,
"workspaceDiagnostics": false
}
},
{
"id": "8c08d1a7-e45b-48e2-a371-59820d24ee65",
"method": "textDocument/diagnostic",
"registerOptions": {
"identifier": "XamlDiagnostics",
"interFileDependencies": true,
"workspaceDiagnostics": false
}
},
{
"id": "4da15f15-03b2-4d07-a5f1-638b4596717f",
"method": "textDocument/diagnostic",
"registerOptions": {
"identifier": "DocumentAnalyzerSyntax",
"interFileDependencies": true,
"workspaceDiagnostics": false
}
},
{
"id": "bf607efd-91c1-4999-bfb1-23aaf15b8a3d",
"method": "textDocument/diagnostic",
"registerOptions": {
"identifier": "NonLocal",
"interFileDependencies": true,
"workspaceDiagnostics": false
}
},
{
"id": "82a65d1e-40f0-4086-9bad-3b36abd1d9fd",
"method": "textDocument/diagnostic",
"registerOptions": {
"identifier": "DocumentCompilerSemantic",
"interFileDependencies": true,
"workspaceDiagnostics": false
}
},
{
"id": "629fb783-9d3d-492b-8f32-e37e3778a5ca",
"method": "textDocument/diagnostic",
"registerOptions": {
"identifier": "syntax",
"interFileDependencies": true,
"workspaceDiagnostics": false
}
},
{
"id": "73744f2e-435b-490b-8f51-029bb1173248",
"method": "textDocument/diagnostic",
"registerOptions": {
"workDoneProgress": true,
"identifier": "WorkspaceDocumentsAndProject",
"interFileDependencies": true,
"workspaceDiagnostics": true
}
}
]
} This client indeed doesn't support registering the same method (capability) multiple times, instead they just get overridden: Lines 538 to 541 in 9be040d
But even if this gets fixed, currently only one pull diagnostics request would be sent. We would need to iterate over all provided Here it would need to iterate over all Lines 506 to 516 in 9be040d
|
I think we should split this into two separate issues.
It looks like both of these issues will require significant rewrites. |
Yeah, it appears that supporting this server in a proper way will take significant time and effort. So, ig for now I will just fork this client and will try to hack together something (this way I can avoid the need for a wrapper altogether). I try contributing upstream if I can get something working. |
I don't use discord. But feel free to open a PR or discuss possible solutions here if you have some ideas. For point 2) an approach could be to pass the But I think it would probably be better to start with implementing point 1), which would be required anyway when we want to test the implementation for 2). |
Here is a dirty way how point 1) could work. I haven't tested, though. diff --git a/plugin/core/types.py b/plugin/core/types.py
index b5d5b932..105b3c58 100644
--- a/plugin/core/types.py
+++ b/plugin/core/types.py
@@ -528,6 +528,13 @@ class Capabilities(DottedDict):
(from Server -> Client).
"""
+ __slots__ = ('_d', '_registrations', '_registration_options')
+
+ def __init__(self, d: dict[str, Any] | None = None) -> None:
+ super().__init__(d)
+ self._registrations: dict[str, set[str]] = {}
+ self._registration_options: dict[str, Any] = {}
+
def register(
self,
registration_id: str,
@@ -535,10 +542,8 @@ class Capabilities(DottedDict):
registration_path: str,
options: dict[str, Any]
) -> None:
- stored_registration_id = self.get(registration_path)
- if isinstance(stored_registration_id, str):
- msg = "{} is already registered at {} with ID {}, overwriting"
- debug(msg.format(capability_path, registration_path, stored_registration_id))
+ self._registrations.setdefault(capability_path, set()).add(registration_id)
+ self._registration_options[registration_id] = options
self.set(capability_path, options)
self.set(registration_path, registration_id)
@@ -548,13 +553,15 @@ class Capabilities(DottedDict):
capability_path: str,
registration_path: str
) -> dict[str, Any] | None:
+ self._registrations.get(capability_path, set()).discard(registration_id)
+ self._registration_options.pop(registration_id, None)
stored_registration_id = self.get(registration_path)
if not isinstance(stored_registration_id, str):
debug("stored registration ID at", registration_path, "is not a string")
return None
elif stored_registration_id != registration_id:
- msg = "stored registration ID ({}) is not the same as the provided registration ID ({})"
- debug(msg.format(stored_registration_id, registration_id))
+ # msg = "stored registration ID ({}) is not the same as the provided registration ID ({})"
+ # debug(msg.format(stored_registration_id, registration_id))
return None
else:
discarded = self.get(capability_path)
@@ -562,6 +569,9 @@ class Capabilities(DottedDict):
self.remove(registration_path)
return discarded
+ def get_all(self, path: str) -> list[Any]:
+ return [self._registration_options[registration_id] for registration_id in self._registrations.get(path, [])]
+
def assign(self, d: ServerCapabilities) -> None:
textsync = normalize_text_sync(d.pop("textDocumentSync", None))
super().assign(cast(dict, d))
diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py
index e0d4cb49..003ad962 100644
--- a/plugin/session_buffer.py
+++ b/plugin/session_buffer.py
@@ -271,6 +271,11 @@ class SessionBuffer:
value = self.capabilities.get(capability_path)
return value if value is not None else self.session.capabilities.get(capability_path)
+ def get_capability_2(self, capability_path: str) -> list[Any]:
+ if self.session.config.is_disabled_capability(capability_path):
+ return []
+ return self.capabilities.get_all(capability_path) + self.session.capabilities.get_all(capability_path)
+
def has_capability(self, capability_path: str) -> bool:
value = self.get_capability(capability_path)
return value is not False and value is not None This keeps a list of the registration IDs for each registered method. It's ugly because it still keeps the old DottedDict for the "single registration" capabilities, to maintain some form of backwards compatibility with the current _params: DocumentDiagnosticParams = {'textDocument': text_document_identifier(view)}
identifiers = set()
for registration in self.get_capability_2('diagnosticProvider'):
identifiers.add(registration.get('identifier', ''))
for identifier in identifiers:
params = _params.copy()
if identifier:
params['identifier'] = identifier
# ... send request This should also work if the diagnostics capability was registered without an identifier. After that the request id and pending requests need to be stored per identifier. And then the responses need to be distributed and stored into the respective diagnostic streams as mentioned in the previous comment. |
I started to work on this but then got busy irl due to exams. Thank you for the patch. I will take a look once my exams are over. |
I have something in the works / in my mind how the diagnostic streams could be implemented. So if you have a bit of patience I can probably push it on a testing branch when it's ready, and then it would be helpful if you could test it (I don't have any C# project at hand and iirc I have only used C# once for a few days and that was almost 10 years ago). The implementation for the diagnostic streams with dynamic registrations might be a bit tricky and I'm unsure whether my ideas would be the best approach, so if you or rchl or predragnikolic have some ideas too, feel free to leave some notes. Good luck for your exams! |
Tried your patches and the good news is that all the diagnostic requests are sent correctly. |
Oh, it seems that the diagnostics is appearing but vanishing almost instantly for some reason. |
It becomes more clear when I make another mistake in the same line. And I think I have an idea why this is happening but don't have enough understanding of the code base to solve it. Screencast.From.2024-11-30.04-36-48.mp4 |
That is expected. Currently the diagnostic responses would just override each other. You could try wether #2564 works. To be honest, the capability registration and the diagnostic handling should be rewritten from scratch in a clean way. DottedDict was nice to have, but it just doesn't work. But first someone needs to find a better design which can handle multiple registrations for the same capability method. |
I am using https://github.com/SofusA/roslyn-language-server (which basically wraps the microsoft roslyn lsp server since sublime lsp doesn't support named pipes yet) for C# but there are no diagnostics. The reason seems to be that sublime doesn't request correct textDocument/diagnostic even after they are registered. I am attaching both the VSCode traces and sublime lsp logs below for comparison. Comparing the logs it can be seen that even though the DocumentCompilerSemantic was registered along with some other options, sublime doesn't request for them whereas VSCode does and it is this request that provides the diagnostics.
sublime-logs.txt
trace.txt
The text was updated successfully, but these errors were encountered: