From a4ef662da7a40a8ec36876701f7cde126398513e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 15 Mar 2026 01:50:23 -0500 Subject: [PATCH] refactor(lsp-manager): replace handler architecture with response registry and modular providers * Remove legacy handlers system (BaseHandler, DefaultHandler, JavaHandler, PythonHandler, HandlerRegistry) * Introduce response_handlers module with ResponseRegistry for LSP response routing * Replace HandlerRegistry usage in LSPManager with ResponseRegistry * Convert LSPManagerUI client lifecycle to GObject signals * Remove GLib.idle_add usage in LSP client event dispatch * Move completion request logic into LSPServerEventsMixin * Replace provider.py and provider_response_cache.py with modular provider/ package * Simplify plugin wiring via response_registry.set_event_hub() This refactor decouples response handling, simplifies event flow, and prepares the LSP manager for easier language-specific extensions. --- .../lsp_manager/client/lsp_client_events.py | 17 +++-- plugins/code/ui/lsp_manager/lsp_manager.py | 39 +++++++----- plugins/code/ui/lsp_manager/lsp_manager_ui.py | 63 ++++++++++--------- .../mixins/lsp_server_events_mixin.py | 12 ++++ plugins/code/ui/lsp_manager/plugin.py | 37 +---------- .../code/ui/lsp_manager/provider/__init__.py | 2 + .../ui/lsp_manager/{ => provider}/provider.py | 0 .../{ => provider}/provider_response_cache.py | 19 ++++++ .../__init__.py | 2 +- .../{handlers => response_handlers}/base.py | 0 .../default.py | 0 .../{handlers => response_handlers}/java.py | 0 .../{handlers => response_handlers}/python.py | 0 .../response_registry.py} | 7 ++- 14 files changed, 105 insertions(+), 93 deletions(-) create mode 100644 plugins/code/ui/lsp_manager/provider/__init__.py rename plugins/code/ui/lsp_manager/{ => provider}/provider.py (100%) rename plugins/code/ui/lsp_manager/{ => provider}/provider_response_cache.py (55%) rename plugins/code/ui/lsp_manager/{handlers => response_handlers}/__init__.py (73%) rename plugins/code/ui/lsp_manager/{handlers => response_handlers}/base.py (100%) rename plugins/code/ui/lsp_manager/{handlers => response_handlers}/default.py (100%) rename plugins/code/ui/lsp_manager/{handlers => response_handlers}/java.py (100%) rename plugins/code/ui/lsp_manager/{handlers => response_handlers}/python.py (100%) rename plugins/code/ui/lsp_manager/{handlers/registry.py => response_handlers/response_registry.py} (87%) diff --git a/plugins/code/ui/lsp_manager/client/lsp_client_events.py b/plugins/code/ui/lsp_manager/client/lsp_client_events.py index 10b91d2..69fadf6 100644 --- a/plugins/code/ui/lsp_manager/client/lsp_client_events.py +++ b/plugins/code/ui/lsp_manager/client/lsp_client_events.py @@ -2,7 +2,6 @@ import os # Lib imports -from gi.repository import GLib # Application imports from libs.dto.code.lsp.lsp_messages import get_message_obj @@ -45,7 +44,7 @@ class LSPClientEvents: params["textDocument"]["languageId"] = data["language_id"] params["textDocument"]["text"] = data["text"] - GLib.idle_add( self.send_notification, method, params ) + self.send_notification( method, params ) def _lsp_did_save(self, data: dict): method = "textDocument/didSave" @@ -54,7 +53,7 @@ class LSPClientEvents: params["textDocument"]["uri"] = data["uri"] params["text"] = data["text"] - GLib.idle_add( self.send_notification, method, params ) + self.send_notification( method, params ) def _lsp_did_close(self, data: dict): method = "textDocument/didClose" @@ -62,7 +61,7 @@ class LSPClientEvents: params["textDocument"]["uri"] = data["uri"] - GLib.idle_add( self.send_notification, method, params ) + self.send_notification( method, params ) def _lsp_did_change(self, data: dict): method = "textDocument/didChange" @@ -75,7 +74,7 @@ class LSPClientEvents: contentChanges = params["contentChanges"][0] contentChanges["text"] = data["text"] - GLib.idle_add( self.send_notification, method, params ) + self.send_notification( method, params ) # def _lsp_did_change(self, data: dict): # method = "textDocument/didChange" @@ -94,7 +93,7 @@ class LSPClientEvents: # end["line"] = data["line"] # end["character"] = data["column"] - # GLib.idle_add( self.send_notification, method, params ) + # self.send_notification( method, params ) def _lsp_definition(self, data: dict): method = "textDocument/definition" @@ -106,7 +105,7 @@ class LSPClientEvents: params["position"]["line"] = data["line"] params["position"]["character"] = data["column"] - GLib.idle_add( self.send_request, method, params ) + self.send_request( method, params ) def _lsp_completion(self, data: dict): method = "textDocument/completion" @@ -118,7 +117,7 @@ class LSPClientEvents: params["position"]["line"] = data["line"] params["position"]["character"] = data["column"] - GLib.idle_add( self.send_request, method, params ) + self.send_request( method, params ) def _lsp_java_class_file_contents(self, uri: str): method = "java/classFileContents" @@ -126,4 +125,4 @@ class LSPClientEvents: "uri": uri } - GLib.idle_add( self.send_request, method, params ) + self.send_request( method, params ) diff --git a/plugins/code/ui/lsp_manager/lsp_manager.py b/plugins/code/ui/lsp_manager/lsp_manager.py index cd1160e..d18d20c 100644 --- a/plugins/code/ui/lsp_manager/lsp_manager.py +++ b/plugins/code/ui/lsp_manager/lsp_manager.py @@ -9,7 +9,7 @@ from .provider import Provider from .provider_response_cache import ProviderResponseCache from .lsp_manager_ui import LSPManagerUI from .lsp_manager_client import LSPManagerClient -from .handlers.registry import HandlerRegistry +from .response_handlers.response_registry import ResponseRegistry @@ -26,20 +26,29 @@ class LSPManager: self.provider: Provider = Provider() self.response_cache: ProviderResponseCache = ProviderResponseCache() self.lsp_manager_client: LSPManagerClient = LSPManagerClient() - self.handler_registry: HandlerRegistry = HandlerRegistry() + self.response_registry: ResponseRegistry = ResponseRegistry() def _load_widgets(self): self.lsp_manager_ui: LSPManagerUI = LSPManagerUI() - self.lsp_manager_ui.create_client = self.create_client - self.lsp_manager_ui.close_client = self.close_client + self.lsp_manager_ui.connect('create-client', self._on_create_client) + self.lsp_manager_ui.connect('close-client', self._on_close_client) def _do_bind_mapping(self): - self.response_cache.process_file_load = self.lsp_manager_client.process_file_load - self.response_cache.process_file_close = self.lsp_manager_client.process_file_close - self.response_cache.process_file_save = self.lsp_manager_client.process_file_save - self.response_cache.process_file_change = self.lsp_manager_client.process_file_change - self.provider.response_cache = self.response_cache + self.response_cache.set_lsp_client(self.lsp_manager_client) + self.provider.response_cache = self.response_cache + def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool: + init_opts = ui.get_init_opts(lang_id) + result = self.create_client(lang_id, workspace_uri, init_opts) + if result: + ui.toggle_client_buttons(show_close=True) + return result + + def _on_close_client(self, ui, lang_id: str) -> bool: + result = self.close_client(lang_id) + if result: + ui.toggle_client_buttons(show_close=False) + return result def create_client( self, @@ -50,7 +59,7 @@ class LSPManager: client = self.lsp_manager_client.create_client( lang_id, workspace_uri, init_opts ) - handler = self.handler_registry.get_handler(lang_id) + handler = self.response_registry.get_handler(lang_id) self.lsp_manager_client.active_language_id = lang_id if not client or not handler: @@ -58,7 +67,7 @@ class LSPManager: self.close_client(lang_id) return False - handler.set_context(self.handler_registry) + handler.set_context(self.response_registry) handler.set_response_cache(self.response_cache) client.handle_lsp_response = self.server_response @@ -68,7 +77,7 @@ class LSPManager: def close_client(self, lang_id: str) -> bool: self.lsp_manager_client.close_client(lang_id) - self.handler_registry.close_handler(lang_id) + self.response_registry.close_handler(lang_id) return True @@ -82,17 +91,17 @@ class LSPManager: controller = self.lsp_manager_client.get_active_client() event = controller.get_event_by_id(lsp_response.id) - handler = self.handler_registry.get_handler( + handler = self.response_registry.get_handler( self.lsp_manager_client.active_language_id, event ) if not handler: return handler.handle(event, lsp_response.result, controller) elif isinstance(lsp_response, LSPResponseNotification): - handler = self.handler_registry.get_handler("default", lsp_response.method) + handler = self.response_registry.get_handler("default", lsp_response.method) if not handler: return - handler.set_context(self.handler_registry) + handler.set_context(self.response_registry) handler.set_response_cache(self.response_cache) handler.handle(lsp_response.method, lsp_response.params, None) diff --git a/plugins/code/ui/lsp_manager/lsp_manager_ui.py b/plugins/code/ui/lsp_manager/lsp_manager_ui.py index ac23002..6530ab1 100644 --- a/plugins/code/ui/lsp_manager/lsp_manager_ui.py +++ b/plugins/code/ui/lsp_manager/lsp_manager_ui.py @@ -7,6 +7,7 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('GtkSource', '4') +from gi.repository import GObject from gi.repository import Gtk from gi.repository import GLib from gi.repository import GtkSource @@ -16,6 +17,11 @@ from gi.repository import GtkSource class LSPManagerUI(Gtk.Dialog): + __gsignals__ = { + 'create-client': (GObject.SignalFlags.RUN_LAST, None, (str, str)), + 'close-client': (GObject.SignalFlags.RUN_LAST, None, (str,)), + } + def __init__(self): super(LSPManagerUI, self).__init__() @@ -57,8 +63,8 @@ class LSPManagerUI(Gtk.Dialog): self.hide_bttn = Gtk.Button(label = "X") bttn_box = Gtk.Box() - create_client_bttn = Gtk.Button(label = "Create Language Client") - close_client_bttn = Gtk.Button(label = "Close Language Client") + self.create_client_bttn = Gtk.Button(label = "Create Language Client") + self.close_client_bttn = Gtk.Button(label = "Close Language Client") self.path_entry.set_can_focus(False) self.path_entry.set_placeholder_text("Workspace Folder...") @@ -67,14 +73,14 @@ class LSPManagerUI(Gtk.Dialog): self.path_bttn.connect("file-set", self._file_set) self.path_bttn.set_halign(Gtk.Align.FILL) self.hide_bttn.connect("clicked", lambda widget: self.hide()) - create_client_bttn.connect("clicked", self._create_client, close_client_bttn) - close_client_bttn.connect("clicked", self._close_client, create_client_bttn) + self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn) + self.close_client_bttn.connect("clicked", self._close_client, self.create_client_bttn) self.main_box.set_column_spacing(15) self.main_box.set_row_spacing(15) - bttn_box.pack_start(create_client_bttn, False, False, 0) - bttn_box.pack_start(close_client_bttn, False, False, 0) + bttn_box.pack_start(self.create_client_bttn, False, False, 0) + bttn_box.pack_start(self.close_client_bttn, False, False, 0) self.main_box.attach(child = self.path_entry, left = 0, top = 0, width = 4, height = 1) self.main_box.attach(child = self.path_bttn, left = 4, top = 0, width = 1, height = 1) @@ -87,7 +93,7 @@ class LSPManagerUI(Gtk.Dialog): content_area.add(self.main_box) content_area.show_all() - close_client_bttn.hide() + self.close_client_bttn.hide() bttn_box.hide() def _show(self, widget): @@ -173,6 +179,20 @@ class LSPManagerUI(Gtk.Dialog): for lang_id in lang_ids: self.combo_box.append_text(lang_id) + def get_init_opts(self, lang_id: str) -> dict: + buffer = self.source_view.get_buffer() + try: + self.servers_config = json.loads( + buffer.get_text( *buffer.get_bounds() ) + ) + except json.JSONDecodeError as e: + logger.error(f"Invalid JSON: {e}") + return {} + + if not lang_id or not lang_id in self.servers_config: return {} + + return self.servers_config[lang_id].get("initialization-options", {}) + def _create_client(self, widget, sibling): if not self.source_view: return @@ -180,35 +200,16 @@ class LSPManagerUI(Gtk.Dialog): lang_id = self.combo_box.get_active_text() if not lang_id: return - if not lang_id in self.servers_config: return - try: - self.servers_config = json.loads( - buffer.get_text( *buffer.get_bounds() ) - ) - except json.JSONDecodeError as e: - logger.error(f"Invalid JSON: {e}") - return - - init_opts = self.servers_config[lang_id]["initialization-options"] workspace_dir = self.path_entry.get_text() - result = None - - result = self.create_client( - lang_id, workspace_dir, init_opts - ) - - if not result: return - - widget.hide() - sibling.show() + self.emit('create-client', lang_id, workspace_dir) def _close_client(self, widget, sibling): lang_id = self.combo_box.get_active_text() if not lang_id: return - result = self.close_client(lang_id) - if not result: return + self.emit('close-client', lang_id) - widget.hide() - sibling.show() + def toggle_client_buttons(self, show_close: bool): + self.create_client_bttn.set_visible(not show_close) + self.close_client_bttn.set_visible(show_close) 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 6de5be9..251fb84 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 @@ -76,6 +76,18 @@ class LSPServerEventsMixin: GLib.idle_add( move_cursor, buffer, pointer_pos ) + def _prompt_completion_request(self): + event = Event_Factory.create_event("get_active_view") + self.emit_to("source_views", event) + view = event.response + + event = Event_Factory.create_event( + "request_completion", + view = view, + provider = self._provider + ) + self.emit_to("completion", event) + def _handle_java_class_file_contents(self, text: str): event = Event_Factory.create_event( "get_active_view", diff --git a/plugins/code/ui/lsp_manager/plugin.py b/plugins/code/ui/lsp_manager/plugin.py index 5bbcfee..1db147b 100644 --- a/plugins/code/ui/lsp_manager/plugin.py +++ b/plugins/code/ui/lsp_manager/plugin.py @@ -59,10 +59,7 @@ class Plugin(PluginCode): lsp_manager.lsp_manager_ui.set_source_view(source_view) lsp_manager.lsp_manager_ui.load_lsp_servers_config_placeholders() - lsp_manager.handler_registry.emit = self.emit - lsp_manager.handler_registry.emit_to = self.emit_to - lsp_manager.handler_registry._prompt_goto_request = self._prompt_goto_request - lsp_manager.handler_registry._prompt_completion_request = self._prompt_completion_request + lsp_manager.response_registry.set_event_hub(self.emit, self.emit_to, lsp_manager.provider) def run(self): ... @@ -70,38 +67,6 @@ class Plugin(PluginCode): def generate_plugin_element(self): ... - 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 _prompt_completion_request(self): - event = Event_Factory.create_event( - "get_active_view", - ) - self.emit_to("source_views", event) - view = event.response - - event = Event_Factory.create_event( - "request_completion", - view = view, - provider = lsp_manager.provider - ) - self.emit_to("completion", event) - class Handler: @staticmethod diff --git a/plugins/code/ui/lsp_manager/provider/__init__.py b/plugins/code/ui/lsp_manager/provider/__init__.py new file mode 100644 index 0000000..fd976ce --- /dev/null +++ b/plugins/code/ui/lsp_manager/provider/__init__.py @@ -0,0 +1,2 @@ +from .provider import Provider +from .provider_response_cache import ProviderResponseCache diff --git a/plugins/code/ui/lsp_manager/provider.py b/plugins/code/ui/lsp_manager/provider/provider.py similarity index 100% rename from plugins/code/ui/lsp_manager/provider.py rename to plugins/code/ui/lsp_manager/provider/provider.py diff --git a/plugins/code/ui/lsp_manager/provider_response_cache.py b/plugins/code/ui/lsp_manager/provider/provider_response_cache.py similarity index 55% rename from plugins/code/ui/lsp_manager/provider_response_cache.py rename to plugins/code/ui/lsp_manager/provider/provider_response_cache.py index 7758f80..2de91d0 100644 --- a/plugins/code/ui/lsp_manager/provider_response_cache.py +++ b/plugins/code/ui/lsp_manager/provider/provider_response_cache.py @@ -19,7 +19,26 @@ class ProviderResponseCache(ProviderResponseCacheBase): super(ProviderResponseCache, self).__init__() self.matchers: dict = {} + self._lsp_client = None + def set_lsp_client(self, lsp_client): + self._lsp_client = lsp_client + + def process_file_load(self, event): + if self._lsp_client: + self._lsp_client.process_file_load(event) + + def process_file_close(self, event): + if self._lsp_client: + self._lsp_client.process_file_close(event) + + def process_file_save(self, event): + if self._lsp_client: + self._lsp_client.process_file_save(event) + + def process_file_change(self, event): + if self._lsp_client: + self._lsp_client.process_file_change(event) def filter(self, word: str) -> list[dict]: return [] diff --git a/plugins/code/ui/lsp_manager/handlers/__init__.py b/plugins/code/ui/lsp_manager/response_handlers/__init__.py similarity index 73% rename from plugins/code/ui/lsp_manager/handlers/__init__.py rename to plugins/code/ui/lsp_manager/response_handlers/__init__.py index 30dffdc..c2946fb 100644 --- a/plugins/code/ui/lsp_manager/handlers/__init__.py +++ b/plugins/code/ui/lsp_manager/response_handlers/__init__.py @@ -2,4 +2,4 @@ from .base import BaseHandler from .default import DefaultHandler from .python import PythonHandler from .java import JavaHandler -from .registry import HandlerRegistry +from .response_registry import ResponseRegistry diff --git a/plugins/code/ui/lsp_manager/handlers/base.py b/plugins/code/ui/lsp_manager/response_handlers/base.py similarity index 100% rename from plugins/code/ui/lsp_manager/handlers/base.py rename to plugins/code/ui/lsp_manager/response_handlers/base.py diff --git a/plugins/code/ui/lsp_manager/handlers/default.py b/plugins/code/ui/lsp_manager/response_handlers/default.py similarity index 100% rename from plugins/code/ui/lsp_manager/handlers/default.py rename to plugins/code/ui/lsp_manager/response_handlers/default.py diff --git a/plugins/code/ui/lsp_manager/handlers/java.py b/plugins/code/ui/lsp_manager/response_handlers/java.py similarity index 100% rename from plugins/code/ui/lsp_manager/handlers/java.py rename to plugins/code/ui/lsp_manager/response_handlers/java.py diff --git a/plugins/code/ui/lsp_manager/handlers/python.py b/plugins/code/ui/lsp_manager/response_handlers/python.py similarity index 100% rename from plugins/code/ui/lsp_manager/handlers/python.py rename to plugins/code/ui/lsp_manager/response_handlers/python.py diff --git a/plugins/code/ui/lsp_manager/handlers/registry.py b/plugins/code/ui/lsp_manager/response_handlers/response_registry.py similarity index 87% rename from plugins/code/ui/lsp_manager/handlers/registry.py rename to plugins/code/ui/lsp_manager/response_handlers/response_registry.py index 938a275..7ed4fa8 100644 --- a/plugins/code/ui/lsp_manager/handlers/registry.py +++ b/plugins/code/ui/lsp_manager/response_handlers/response_registry.py @@ -12,7 +12,7 @@ from .java import JavaHandler -class HandlerRegistry(LSPServerEventsMixin): +class ResponseRegistry(LSPServerEventsMixin): def __init__(self): self._instances: dict = {} @@ -22,6 +22,11 @@ class HandlerRegistry(LSPServerEventsMixin): "java": JavaHandler, } + def set_event_hub(self, emit, emit_to, provider=None): + self.emit = emit + self.emit_to = emit_to + self._provider = provider + def _get_instance(self, handler_cls: type[BaseHandler]) -> BaseHandler: if handler_cls in self._instances: return self._instances[handler_cls]