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.
This commit is contained in:
2026-03-15 01:50:23 -05:00
parent 6cb66985aa
commit a4ef662da7
14 changed files with 105 additions and 93 deletions

View File

@@ -2,7 +2,6 @@
import os import os
# Lib imports # Lib imports
from gi.repository import GLib
# Application imports # Application imports
from libs.dto.code.lsp.lsp_messages import get_message_obj 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"]["languageId"] = data["language_id"]
params["textDocument"]["text"] = data["text"] 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): def _lsp_did_save(self, data: dict):
method = "textDocument/didSave" method = "textDocument/didSave"
@@ -54,7 +53,7 @@ class LSPClientEvents:
params["textDocument"]["uri"] = data["uri"] params["textDocument"]["uri"] = data["uri"]
params["text"] = data["text"] params["text"] = data["text"]
GLib.idle_add( self.send_notification, method, params ) self.send_notification( method, params )
def _lsp_did_close(self, data: dict): def _lsp_did_close(self, data: dict):
method = "textDocument/didClose" method = "textDocument/didClose"
@@ -62,7 +61,7 @@ class LSPClientEvents:
params["textDocument"]["uri"] = data["uri"] 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): def _lsp_did_change(self, data: dict):
method = "textDocument/didChange" method = "textDocument/didChange"
@@ -75,7 +74,7 @@ class LSPClientEvents:
contentChanges = params["contentChanges"][0] contentChanges = params["contentChanges"][0]
contentChanges["text"] = data["text"] contentChanges["text"] = data["text"]
GLib.idle_add( self.send_notification, method, params ) self.send_notification( method, params )
# def _lsp_did_change(self, data: dict): # def _lsp_did_change(self, data: dict):
# method = "textDocument/didChange" # method = "textDocument/didChange"
@@ -94,7 +93,7 @@ class LSPClientEvents:
# end["line"] = data["line"] # end["line"] = data["line"]
# end["character"] = data["column"] # end["character"] = data["column"]
# GLib.idle_add( self.send_notification, method, params ) # self.send_notification( method, params )
def _lsp_definition(self, data: dict): def _lsp_definition(self, data: dict):
method = "textDocument/definition" method = "textDocument/definition"
@@ -106,7 +105,7 @@ class LSPClientEvents:
params["position"]["line"] = data["line"] params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"] params["position"]["character"] = data["column"]
GLib.idle_add( self.send_request, method, params ) self.send_request( method, params )
def _lsp_completion(self, data: dict): def _lsp_completion(self, data: dict):
method = "textDocument/completion" method = "textDocument/completion"
@@ -118,7 +117,7 @@ class LSPClientEvents:
params["position"]["line"] = data["line"] params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"] 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): def _lsp_java_class_file_contents(self, uri: str):
method = "java/classFileContents" method = "java/classFileContents"
@@ -126,4 +125,4 @@ class LSPClientEvents:
"uri": uri "uri": uri
} }
GLib.idle_add( self.send_request, method, params ) self.send_request( method, params )

View File

@@ -9,7 +9,7 @@ from .provider import Provider
from .provider_response_cache import ProviderResponseCache from .provider_response_cache import ProviderResponseCache
from .lsp_manager_ui import LSPManagerUI from .lsp_manager_ui import LSPManagerUI
from .lsp_manager_client import LSPManagerClient 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.provider: Provider = Provider()
self.response_cache: ProviderResponseCache = ProviderResponseCache() self.response_cache: ProviderResponseCache = ProviderResponseCache()
self.lsp_manager_client: LSPManagerClient = LSPManagerClient() self.lsp_manager_client: LSPManagerClient = LSPManagerClient()
self.handler_registry: HandlerRegistry = HandlerRegistry() self.response_registry: ResponseRegistry = ResponseRegistry()
def _load_widgets(self): def _load_widgets(self):
self.lsp_manager_ui: LSPManagerUI = LSPManagerUI() self.lsp_manager_ui: LSPManagerUI = LSPManagerUI()
self.lsp_manager_ui.create_client = self.create_client self.lsp_manager_ui.connect('create-client', self._on_create_client)
self.lsp_manager_ui.close_client = self.close_client self.lsp_manager_ui.connect('close-client', self._on_close_client)
def _do_bind_mapping(self): def _do_bind_mapping(self):
self.response_cache.process_file_load = self.lsp_manager_client.process_file_load self.response_cache.set_lsp_client(self.lsp_manager_client)
self.response_cache.process_file_close = self.lsp_manager_client.process_file_close self.provider.response_cache = self.response_cache
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
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( def create_client(
self, self,
@@ -50,7 +59,7 @@ class LSPManager:
client = self.lsp_manager_client.create_client( client = self.lsp_manager_client.create_client(
lang_id, workspace_uri, init_opts 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 self.lsp_manager_client.active_language_id = lang_id
if not client or not handler: if not client or not handler:
@@ -58,7 +67,7 @@ class LSPManager:
self.close_client(lang_id) self.close_client(lang_id)
return False return False
handler.set_context(self.handler_registry) handler.set_context(self.response_registry)
handler.set_response_cache(self.response_cache) handler.set_response_cache(self.response_cache)
client.handle_lsp_response = self.server_response client.handle_lsp_response = self.server_response
@@ -68,7 +77,7 @@ class LSPManager:
def close_client(self, lang_id: str) -> bool: def close_client(self, lang_id: str) -> bool:
self.lsp_manager_client.close_client(lang_id) self.lsp_manager_client.close_client(lang_id)
self.handler_registry.close_handler(lang_id) self.response_registry.close_handler(lang_id)
return True return True
@@ -82,17 +91,17 @@ class LSPManager:
controller = self.lsp_manager_client.get_active_client() controller = self.lsp_manager_client.get_active_client()
event = controller.get_event_by_id(lsp_response.id) 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 self.lsp_manager_client.active_language_id, event
) )
if not handler: return if not handler: return
handler.handle(event, lsp_response.result, controller) handler.handle(event, lsp_response.result, controller)
elif isinstance(lsp_response, LSPResponseNotification): 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 if not handler: return
handler.set_context(self.handler_registry) handler.set_context(self.response_registry)
handler.set_response_cache(self.response_cache) handler.set_response_cache(self.response_cache)
handler.handle(lsp_response.method, lsp_response.params, None) handler.handle(lsp_response.method, lsp_response.params, None)

View File

@@ -7,6 +7,7 @@ import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import GObject
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
@@ -16,6 +17,11 @@ from gi.repository import GtkSource
class LSPManagerUI(Gtk.Dialog): 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): def __init__(self):
super(LSPManagerUI, self).__init__() super(LSPManagerUI, self).__init__()
@@ -57,8 +63,8 @@ class LSPManagerUI(Gtk.Dialog):
self.hide_bttn = Gtk.Button(label = "X") self.hide_bttn = Gtk.Button(label = "X")
bttn_box = Gtk.Box() bttn_box = Gtk.Box()
create_client_bttn = Gtk.Button(label = "Create Language Client") self.create_client_bttn = Gtk.Button(label = "Create Language Client")
close_client_bttn = Gtk.Button(label = "Close Language Client") self.close_client_bttn = Gtk.Button(label = "Close Language Client")
self.path_entry.set_can_focus(False) self.path_entry.set_can_focus(False)
self.path_entry.set_placeholder_text("Workspace Folder...") 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.connect("file-set", self._file_set)
self.path_bttn.set_halign(Gtk.Align.FILL) self.path_bttn.set_halign(Gtk.Align.FILL)
self.hide_bttn.connect("clicked", lambda widget: self.hide()) self.hide_bttn.connect("clicked", lambda widget: self.hide())
create_client_bttn.connect("clicked", self._create_client, close_client_bttn) self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn)
close_client_bttn.connect("clicked", self._close_client, create_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_column_spacing(15)
self.main_box.set_row_spacing(15) self.main_box.set_row_spacing(15)
bttn_box.pack_start(create_client_bttn, False, False, 0) bttn_box.pack_start(self.create_client_bttn, False, False, 0)
bttn_box.pack_start(close_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_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) 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.add(self.main_box)
content_area.show_all() content_area.show_all()
close_client_bttn.hide() self.close_client_bttn.hide()
bttn_box.hide() bttn_box.hide()
def _show(self, widget): def _show(self, widget):
@@ -173,6 +179,20 @@ class LSPManagerUI(Gtk.Dialog):
for lang_id in lang_ids: for lang_id in lang_ids:
self.combo_box.append_text(lang_id) 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): def _create_client(self, widget, sibling):
if not self.source_view: return if not self.source_view: return
@@ -180,35 +200,16 @@ class LSPManagerUI(Gtk.Dialog):
lang_id = self.combo_box.get_active_text() lang_id = self.combo_box.get_active_text()
if not lang_id: return 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() workspace_dir = self.path_entry.get_text()
result = None self.emit('create-client', lang_id, workspace_dir)
result = self.create_client(
lang_id, workspace_dir, init_opts
)
if not result: return
widget.hide()
sibling.show()
def _close_client(self, widget, sibling): def _close_client(self, widget, sibling):
lang_id = self.combo_box.get_active_text() lang_id = self.combo_box.get_active_text()
if not lang_id: return if not lang_id: return
result = self.close_client(lang_id) self.emit('close-client', lang_id)
if not result: return
widget.hide() def toggle_client_buttons(self, show_close: bool):
sibling.show() self.create_client_bttn.set_visible(not show_close)
self.close_client_bttn.set_visible(show_close)

View File

@@ -76,6 +76,18 @@ class LSPServerEventsMixin:
GLib.idle_add( move_cursor, buffer, pointer_pos ) 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): def _handle_java_class_file_contents(self, text: str):
event = Event_Factory.create_event( event = Event_Factory.create_event(
"get_active_view", "get_active_view",

View File

@@ -59,10 +59,7 @@ class Plugin(PluginCode):
lsp_manager.lsp_manager_ui.set_source_view(source_view) lsp_manager.lsp_manager_ui.set_source_view(source_view)
lsp_manager.lsp_manager_ui.load_lsp_servers_config_placeholders() lsp_manager.lsp_manager_ui.load_lsp_servers_config_placeholders()
lsp_manager.handler_registry.emit = self.emit lsp_manager.response_registry.set_event_hub(self.emit, self.emit_to, lsp_manager.provider)
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
def run(self): def run(self):
... ...
@@ -70,38 +67,6 @@ class Plugin(PluginCode):
def generate_plugin_element(self): 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: class Handler:
@staticmethod @staticmethod

View File

@@ -0,0 +1,2 @@
from .provider import Provider
from .provider_response_cache import ProviderResponseCache

View File

@@ -19,7 +19,26 @@ class ProviderResponseCache(ProviderResponseCacheBase):
super(ProviderResponseCache, self).__init__() super(ProviderResponseCache, self).__init__()
self.matchers: dict = {} 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]: def filter(self, word: str) -> list[dict]:
return [] return []

View File

@@ -2,4 +2,4 @@ from .base import BaseHandler
from .default import DefaultHandler from .default import DefaultHandler
from .python import PythonHandler from .python import PythonHandler
from .java import JavaHandler from .java import JavaHandler
from .registry import HandlerRegistry from .response_registry import ResponseRegistry

View File

@@ -12,7 +12,7 @@ from .java import JavaHandler
class HandlerRegistry(LSPServerEventsMixin): class ResponseRegistry(LSPServerEventsMixin):
def __init__(self): def __init__(self):
self._instances: dict = {} self._instances: dict = {}
@@ -22,6 +22,11 @@ class HandlerRegistry(LSPServerEventsMixin):
"java": JavaHandler, "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: def _get_instance(self, handler_cls: type[BaseHandler]) -> BaseHandler:
if handler_cls in self._instances: return self._instances[handler_cls] if handler_cls in self._instances: return self._instances[handler_cls]