-
Notifications
You must be signed in to change notification settings - Fork 185
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
Semantic highlighting (v2) #1839
Conversation
Interestingly, I see no request for semantic tokens for clangd with this pull request. It does a
|
That is strange. clangd doesn't send any |
Ah right, I don’t have the color scheme customization on the Linux box I’m testing this with. It’s late and I’ll get back to this some other time :) |
Please fix the merge conflicts :) |
Not sure how to proceed with this PR. Have you tried it already? It seems to works relatively well with clangd and rust-analyzer, but I haven't thoroughly tested because I neither use C++ nor Rust. Of course there is a little bit of lag from the communication with the servers, which can make the syntax highlighting feel a bit sluggish. E.g. if you comment out a line of code, then it will take a fraction of a second until the tokens on this line are cleared by the server too, which I find quite noticeable and I don't like it. But for rust-analyzer I saw for example that it will mark argument names in a function declaration just as "variable" token type (instead of I guess something with possible improvements would be the token-to-scope mapping, if we can figure out how exactly the tokens are used by each server. It is already possible in this PR to override this mapping in the client config for the servers, or add scopes for custom token types. If I remember correctly, rust-analyzer uses a lot of custom types for example. |
I have doubts about treating the built-in color schemes special. It shouldn't be that hard to run |
Also please merge with upstream :) |
Sorry if that was already discussed/reported but do squiggly error highlights are supposed to work correctly with those changes or it's still an unsolved issue? Because what I can see now is that if a given word has both semantic region and error region applied then the squiggly underline is missing completely. |
They are supposed to work correctly even with underlines from diagnostics and also with documentHighlights. This is what the |
Sorry, I think that was the case. My custom rule for highlighting unused variables was actually the reason why the error highlight didn't show up. |
This is good to know :) But yes, it can still be considered to be a bug that it might not work for when there are only color scheme rules for custom token types. But I don't really know how this could be solved. |
To fix the bug I described in #1839 (comment), I think we need better support for request cancellation. From looking at the code base, the only thing we can do right now, is Session.send_notification(Notification("$/cancelRequest", {"id": request_id})) But according to the LSP specs, the server still must send a response for the request. If I understand it correctly, it is advised to return an error response with code Therefore I would suggest to add a new method Lines 312 to 314 in 8807689
|
…fault color schemes"" This reverts commit ea80e28.
I've added what I meant with the request cancellation & ignored response (rust-analyzer indeed still sends a response and just ignores the cancel notification) in 5014576. I'm not sure if this implementation is a good way to do it, and perhaps it would fit better for a separate PR, ideally including a test case (I looked into the tests, but couldn't add one for it without help). Also I didn't see a good way to return the request_id from |
I think the cancellation stuff is better suited as a separate PR because this one is getting quite large. Do you want to put it on “ready for review” or is there something that requires work for this to be on draft status? |
Compared to the commit I linked above, it would be nice for consistency to also return the request id at least from Click to expanddiff --git a/plugin/core/sessions.py b/plugin/core/sessions.py
index dd61e6d..c417f9a 100644
--- a/plugin/core/sessions.py
+++ b/plugin/core/sessions.py
@@ -1646,15 +1646,13 @@ class Session(TransportCallbacks):
# --- RPC message handling ----------------------------------------------------------------------------------------
- def send_request_async(
+ def _send_request_async(
self,
+ request_id: int,
request: Request,
on_result: Callable[[Any], None],
on_error: Optional[Callable[[Any], None]] = None
) -> None:
- """You must call this method from Sublime's worker thread. Callbacks will run in Sublime's worker thread."""
- self.request_id += 1
- request_id = self.request_id
if request.progress and isinstance(request.params, dict):
request.params["workDoneToken"] = _WORK_DONE_PROGRESS_PREFIX + str(request_id)
self._response_handlers[request_id] = (request, on_result, on_error)
@@ -1664,14 +1662,29 @@ class Session(TransportCallbacks):
self._logger.outgoing_request(request_id, request.method, request.params)
self.send_payload(request.to_payload(request_id))
+ def send_request_async(
+ self,
+ request: Request,
+ on_result: Callable[[Any], None],
+ on_error: Optional[Callable[[Any], None]] = None
+ ) -> int:
+ """You must call this method from Sublime's worker thread. Callbacks will run in Sublime's worker thread."""
+ self.request_id += 1
+ request_id = self.request_id
+ self._send_request_async(request_id, request, on_result, on_error)
+ return request_id
+
def send_request(
self,
request: Request,
on_result: Callable[[Any], None],
on_error: Optional[Callable[[Any], None]] = None,
- ) -> None:
+ ) -> int:
"""You can call this method from any thread. Callbacks will run in Sublime's worker thread."""
- sublime.set_timeout_async(functools.partial(self.send_request_async, request, on_result, on_error))
+ self.request_id += 1
+ request_id = self.request_id
+ sublime.set_timeout_async(functools.partial(self._send_request_async, request_id, request, on_result, on_error))
+ return request_id
def send_request_task(self, request: Request) -> Promise:
task = Promise.packaged_task() # type: PackagedTask[Any]
@@ -1679,6 +1692,12 @@ class Session(TransportCallbacks):
self.send_request_async(request, resolver, lambda x: resolver(Error.from_lsp(x)))
return promise
+ def cancel_request(self, request_id: int, ignore_response: bool = True) -> None:
+ self.send_notification(Notification("$/cancelRequest", {"id": request_id}))
+ if ignore_response and request_id in self._response_handlers:
+ request, _, _ = self._response_handlers[request_id]
+ self._response_handlers[request_id] = (request, lambda *args: None, lambda *args: None)
+
def send_notification(self, notification: Notification) -> None:
if self._plugin:
self._plugin.on_pre_send_notification_async(notification)
And a test with something like the following would be useful, but idk really how to start:
If you know how to fix it and how to add a test, feel free to use the diff or and/or improve it for a separate PR :)
OK. Some remaining limitations of the PR:
Btw, I updated my clangd version to 13.0.0 and it seems to work noticeably better than the previous version (now uses token modifiers & syncronization bug fixed). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work. Thanks for spending time on this :) Let's move on with the cancellation stuff.
This is an alternative to #1751, but with support for more request types and additional features.
There is still a lot to optimize, for example there is a short flickering because all regions are redrawn after each request. Also I'm not happy yet with how token modifiers are taken into account - there probably should be a way for the helper plugins to consider them too, and for color schemes to target them. I left some "TODO" comments in the code where I got stuck a bit; suggestions or help appreciated.
I haven't yet considered the region draw order (so that it works together with diagnostics etc.) and there might be some other bugs around - just wanted to open it here already, so that those who are interested could take a look or provide suggestions. And in general, I'm not sure if this add_regions approach for semantic highlighting is something we want, or if we shouldn't better wait for a dedicated API (might possibly never be implemented though).
In general it seems to work with clangd, but I have only tested with very simple examples because I'm not very familiar with C++. Maybe some tweaks for the default scopes are needed too, probably requires some experience with more servers about how token types are used.
I couldn't get it really to work with the Lua server, because that server immediately unregisters the semantic token capability after it initializes.