From 3dfb198aa5f1709af216c242f567d218dca9e8bf Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 8 Mar 2026 17:54:21 -0500 Subject: [PATCH] feat(lsp): support Java class file contents and improve definition navigation handling - Add `_lsp_java_class_file_contents` request to fetch contents of compiled Java classes via LSP (`java/classFileContents`). - Handle `java/classFileContents` responses by opening a new buffer with Java syntax highlighting and inserting the returned source. - Update definition handling to pass URI and range, enabling precise cursor placement after navigation. - Detect `jdt://` URIs in `textDocument/definition` responses and request class file contents instead of direct navigation. - Move goto navigation logic into `LSPServerEventsMixin`, using event system to access the active view and position the cursor. - Expose `emit` and `emit_to` to the response cache for event dispatching. - Restrict completion activation to `USER_REQUESTED`. - Add TODO note about mapping language IDs to dedicated response handlers. --- .../controllers/lsp_controller_events.py | 8 ++++ .../mixins/lsp_server_events_mixin.py | 48 +++++++++++++++---- plugins/code/ui/lsp_manager/plugin.py | 17 ++----- plugins/code/ui/lsp_manager/provider.py | 3 +- .../ui/lsp_manager/provider_response_cache.py | 14 +++++- 5 files changed, 65 insertions(+), 25 deletions(-) diff --git a/plugins/code/ui/lsp_manager/controllers/lsp_controller_events.py b/plugins/code/ui/lsp_manager/controllers/lsp_controller_events.py index 5d83eff..5bc8a0f 100644 --- a/plugins/code/ui/lsp_manager/controllers/lsp_controller_events.py +++ b/plugins/code/ui/lsp_manager/controllers/lsp_controller_events.py @@ -119,3 +119,11 @@ class LSPControllerEvents: params["position"]["character"] = data["column"] GLib.idle_add( self.send_request, method, params ) + + def _lsp_java_class_file_contents(self, uri: str): + method = "java/classFileContents" + params = { + "uri": uri + } + + GLib.idle_add( self.send_request, method, params ) diff --git a/plugins/code/ui/lsp_manager/mixins/lsp_server_events_mixin.py b/plugins/code/ui/lsp_manager/mixins/lsp_server_events_mixin.py index 7b86b48..a779893 100644 --- a/plugins/code/ui/lsp_manager/mixins/lsp_server_events_mixin.py +++ b/plugins/code/ui/lsp_manager/mixins/lsp_server_events_mixin.py @@ -3,18 +3,20 @@ # Lib imports import gi +gi.require_version('GtkSource', '4') + from gi.repository import GLib +from gi.repository import GtkSource # Application imports -from libs.event_factory import Code_Event_Types +from libs.event_factory import Event_Factory, Code_Event_Types class LSPServerEventsMixin: - def _handle_definition_response(self, result: dict or list): - if not result: return - self._prompt_goto_request(result[0]["uri"]) + def _handle_definition_response(self, uri: str, pointer_pos: dict): + self._prompt_goto_request(uri, pointer_pos) def _handle_completion_response(self, result: dict or list): if not result: return @@ -52,8 +54,38 @@ class LSPServerEventsMixin: self._prompt_completion_request() - def _prompt_completion_request(self): - raise NotImplementedError - def _prompt_goto_request(self, uri: str): - raise NotImplementedError + def _prompt_goto_request(self, uri: str, pointer_pos: dict): + event = Event_Factory.create_event( + "get_active_view", + ) + self.emit_to("source_views", event) + view = event.response + view._on_uri_data_received( [uri] ) + + buffer = view.get_buffer() + + def move_cursor(buffer, pointer_pos): + itr = buffer.get_iter_at_line( pointer_pos["end"]["line"] ) + itr.forward_chars( pointer_pos["end"]["character"] ) + buffer.place_cursor(itr) + view.scroll_to_iter(itr, 0.2, False, 0, 0) + + GLib.idle_add( move_cursor, buffer, pointer_pos ) + + def _handle_java_class_file_contents(self, text: str): + event = Event_Factory.create_event( + "get_active_view", + ) + self.emit_to("source_views", event) + + view = event.response + file = view.command.exec("new_file") + buffer = view.get_buffer() + itr = buffer.get_iter_at_mark( buffer.get_insert() ) + lm = GtkSource.LanguageManager.get_default() + language = lm.get_language("java") + file.ftype = "java" + + buffer.set_language(language) + buffer.insert(itr, text, -1) diff --git a/plugins/code/ui/lsp_manager/plugin.py b/plugins/code/ui/lsp_manager/plugin.py index b5d7407..00d1735 100644 --- a/plugins/code/ui/lsp_manager/plugin.py +++ b/plugins/code/ui/lsp_manager/plugin.py @@ -55,11 +55,9 @@ class Plugin(PluginCode): lsp_manager.load_lsp_servers_config() lsp_manager.set_source_view(source_view) lsp_manager.load_lsp_servers_config_placeholders() - lsp_manager.provider.response_cache._prompt_completion_request = \ - self._prompt_completion_request - - lsp_manager.provider.response_cache._prompt_goto_request = \ - self._prompt_goto_request + lsp_manager.provider.response_cache.emit = self.emit + lsp_manager.provider.response_cache.emit_to = self.emit_to + lsp_manager.provider.response_cache._prompt_completion_request = self._prompt_completion_request def run(self): ... @@ -82,15 +80,6 @@ class Plugin(PluginCode): self.emit_to("completion", event) - def _prompt_goto_request(self, uri: str): - event = Event_Factory.create_event( - "get_active_view", - ) - self.emit_to("source_views", event) - view = event.response - view._on_uri_data_received( [uri] ) - - class Handler: @staticmethod def execute( diff --git a/plugins/code/ui/lsp_manager/provider.py b/plugins/code/ui/lsp_manager/provider.py index e1a8176..0efa2d0 100644 --- a/plugins/code/ui/lsp_manager/provider.py +++ b/plugins/code/ui/lsp_manager/provider.py @@ -60,9 +60,8 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider): def do_get_activation(self): """ The context for when a provider will show results """ # return GtkSource.CompletionActivation.NONE -# return GtkSource.CompletionActivation.USER_REQUESTED + return GtkSource.CompletionActivation.USER_REQUESTED # return GtkSource.CompletionActivation.INTERACTIVE - return GtkSource.CompletionActivation.INTERACTIVE | GtkSource.CompletionActivation.USER_REQUESTED def do_populate(self, context): results = self.response_cache.filter_with_context(context) diff --git a/plugins/code/ui/lsp_manager/provider_response_cache.py b/plugins/code/ui/lsp_manager/provider_response_cache.py index 23eee95..bf51b22 100644 --- a/plugins/code/ui/lsp_manager/provider_response_cache.py +++ b/plugins/code/ui/lsp_manager/provider_response_cache.py @@ -66,6 +66,9 @@ class ProviderResponseCache(LSPClientEventsMixin, LSPServerEventsMixin, Provider return True + # TODO: Need to map 'lang_id' to a given language response class and + # pass the controller to a 'server_response' method there. + # It would allow clean separation of each language's idiosyncracies def server_response(self, lsp_response: LSPResponseTypes): logger.debug(f"LSP Response: { lsp_response }") @@ -81,7 +84,16 @@ class ProviderResponseCache(LSPClientEventsMixin, LSPServerEventsMixin, Provider case "textDocument/completion": self._handle_completion_response(lsp_response.result) case "textDocument/definition": - self._handle_definition_response(lsp_response.result) + result = lsp_response.result + if not result: return + uri = result[0]["uri"] + if "jdt://" in uri: + controller._lsp_java_class_file_contents(uri) + return + + self._handle_definition_response(uri, result[0]["range"]) + case "java/classFileContents": + self._handle_java_class_file_contents(lsp_response.result) case _: ... elif isinstance(lsp_response, LSPResponseNotification):