From 13b126ef6e982ab979e7054b980f651a0b9724fc Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 9 Sep 2024 21:50:52 -0500 Subject: [PATCH] message refactoring; initial event call from client structure --- src/core/controllers/lsp_controller.py | 96 +++++++++++++------ .../enteries/lsp_message_source_view.py | 61 +----------- src/core/widgets/log_list.py | 15 +-- src/core/widgets/lsp_message_box.py | 17 ++-- src/core/widgets/lsp_ui.py | 6 +- src/libs/dto/lsp_message_structs.py | 28 +++--- src/libs/dto/lsp_messages.py | 92 ++++++++++++++++++ src/libs/dto/lsp_structs.py | 5 + 8 files changed, 206 insertions(+), 114 deletions(-) create mode 100644 src/libs/dto/lsp_messages.py diff --git a/src/core/controllers/lsp_controller.py b/src/core/controllers/lsp_controller.py index 3ab6832..bc0d8d8 100644 --- a/src/core/controllers/lsp_controller.py +++ b/src/core/controllers/lsp_controller.py @@ -1,7 +1,6 @@ # Python imports import os import signal -import json import subprocess import threading @@ -9,12 +8,17 @@ import threading from gi.repository import GLib # Application imports -from libs.dto.lsp_message_structs import MessageEncoder, LSPRequest, LSPNotification, LSPResponse +from libs.dto.lsp_messages import LEN_HEADER, TYPE_HEADER, get_message_str, get_message_obj, definition_query, references_query, symbols_query +from libs.dto.lsp_message_structs import ClientRequest, ClientNotification, LSPResponse -LEN_HEADER = "Content-Length: " -TYPE_HEADER = "Content-Type: " +def _log_list(): + def add_log_entry(title: str, message: any): + ... + + def clear(): + ... @@ -22,6 +26,8 @@ class LSPController: def __init__(self): super(LSPController).__init__() + self._language = "python3" + # https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers # initialize-params-slim.json was created off of jedi_language_server one self._init_params = settings_manager.get_lsp_init_data() @@ -32,12 +38,44 @@ class LSPController: self.read_lock = threading.Lock() self.write_lock = threading.Lock() + self.log_list = _log_list + self.request_list = {} + self._subscribe_to_events() + + + def _subscribe_to_events(self): + event_system.subscribe("client-send-request", self._client_send_request) + event_system.subscribe("client-send-notification", self._client_send_notification) + + def _client_send_request(self, method: str, uri: str, line: int, character: int): + if not method: return + + if "textDocument/definition": + params = symbols_query + elif "textDocument/references": + params = symbols_query + elif "textDocument/documentSymbol": + params = symbols_query + + params["textDocument"]["uri"] = uri + params["textDocument"]["languageId"] = self._language + params["position"]["line"] = line + params["position"]["character"] = character + + self.send_request(method, params) + + def _client_send_notification(self, method: str, line: int, char: int): + if not method: return + self.send_notification(method, params) + + def set_language(self, language): + self._language = language def set_log_list(self, log_list): self.log_list = log_list - def set_start_command(self, start_command: []): + def set_start_command(self, start_command: list): self._start_command = start_command def unset_start_command(self): @@ -56,25 +94,26 @@ class LSPController: } ] - self._init_params["initializationOptions"] = json.loads(init_ops) + self._init_params["initializationOptions"] = get_message_obj(init_ops) self.send_request("initialize", self._init_params) def send_initialized_message(self): self.send_notification("initialized") def send_notification(self, method: str, params: {} = {}): - self._send_message( LSPNotification(method, params) ) + self._send_message( ClientNotification(method, params) ) def send_request(self, method: str, params: {} = {}): self._monitor_lsp_response() - self._send_message( LSPRequest(self._message_id, method, params) ) + self._send_message( ClientRequest(self._message_id, method, params) ) + self.request_list[self._message_id] = {"method": method} self._message_id += 1 - def _send_message(self, data: LSPRequest or LSPNotification): + def _send_message(self, data: ClientRequest or ClientNotification): if not data or not hasattr(self, "lsp_process"): return - message_str = json.dumps(data, cls = MessageEncoder) + message_str = get_message_str(data) message_size = len(message_str) message = f"Content-Length: {message_size}\r\n\r\n{message_str}" @@ -170,33 +209,36 @@ class LSPController: data = self.lsp_process.stdout.read(message_size) jsonrpc_res = data.decode("utf-8") - lsp_response = json.loads( jsonrpc_res ) + lsp_response = LSPResponse(**get_message_obj(jsonrpc_res)) response_id = -1 if not lsp_response: return - if "id" in lsp_response.keys(): response_id = lsp_response["id"] - - GLib.idle_add(self.handle_lsp_response, LSPResponse(response_id, lsp_response)) + GLib.idle_add(self.handle_lsp_response, lsp_response) def handle_lsp_response(self, lsp_response: LSPResponse): - self.log_list.add_log_entry("LSP Response", lsp_response) - + method = self.request_list[lsp_response.id] result = lsp_response.result - keys = result.keys() - if "error" in keys: - error = result["error"] - logger.debug(f"LSP Error Code: {error['code']}") - logger.debug(f"LSP Error Message:\n{error['message']}") - return - - if "result" in keys: - result = result["result"] + self.log_list.add_log_entry("LSP Response", lsp_response) + self.process_lsp_response(method, result) + def process_lsp_response(self, method, result): if isinstance(result, dict): keys = result.keys() - if "capabilities" in keys: - ... + return + if "error" in keys: + error = result["error"] + logger.debug(f"LSP Error Code: {error['code']}") + logger.debug(f"LSP Error Message:\n{error['message']}") + return + + if "result" in keys: + result = result["result"] + + if isinstance(result, dict): + keys = result.keys() + if "capabilities" in keys: + ... if isinstance(result, list): ... diff --git a/src/core/widgets/enteries/lsp_message_source_view.py b/src/core/widgets/enteries/lsp_message_source_view.py index b530692..bd2bf24 100644 --- a/src/core/widgets/enteries/lsp_message_source_view.py +++ b/src/core/widgets/enteries/lsp_message_source_view.py @@ -6,64 +6,7 @@ gi.require_version('GtkSource', '4') from gi.repository import GtkSource # Application imports - - - -# Request type formatting -# https://github.com/microsoft/multilspy/blob/main/src/multilspy/language_server.py#L417 - -content_part = """{ - "method": "textDocument/definition", - "params": { - "textDocument": { - "uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/000_Usable/gtk/LSP-Manager/src/core/widgets/lsp_message_box.py", - "languageId": "python3", - "version": 1, - "text": "" - }, - "position": { - "line": 5, - "character": 12, - "offset": 0 - } - } -} -""" - -references_query = """{ - - "method": "textDocument/references", - "params": { - "context": { - "includeDeclaration": false - }, - "textDocument": { - "uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/000_Usable/gtk/LSP-Manager/src/core/widgets/lsp_message_box.py", - "languageId": "python3", - "version": 1, - "text": "" - }, - "position": { - "line": 30, - "character": 13, - "offset": 0 - } - } -} -""" - -symbols_query = """{ - "method": "textDocument/documentSymbol", - "params": { - "textDocument": { - "uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/000_Usable/gtk/LSP-Manager/src/core/widgets/lsp_message_box.py", - "languageId": "python3", - "version": 1, - "text": "" - } - } -} -""" +from libs.dto.lsp_messages import get_message_str, content_part @@ -97,7 +40,7 @@ class LspMessageSourceView(GtkSource.View): buffer.set_language( language_manager.get_language("json") ) buffer.set_style_scheme(style_scheme) - buffer.set_text(content_part) + buffer.set_text( get_message_str(content_part) ) def _setup_signals(self): diff --git a/src/core/widgets/log_list.py b/src/core/widgets/log_list.py index 48b5cb8..60c9ba7 100644 --- a/src/core/widgets/log_list.py +++ b/src/core/widgets/log_list.py @@ -1,5 +1,4 @@ # Python imports -import json # Lib imports import gi @@ -8,7 +7,9 @@ from gi.repository import Gtk from gi.repository import GLib # Application imports -from libs.dto.lsp_message_structs import MessageEncoder, LSPRequest, LSPNotification, LSPResponse +from libs.dto.lsp_messages import get_message_str +from libs.dto.lsp_message_structs import MessageTypes + class LogList(Gtk.ListBox): @@ -28,13 +29,13 @@ class LogList(Gtk.ListBox): ctx.add_class("log-list-listbox") self.set_placeholder( Gtk.Label(label="Empty Log List...") ) - self.set_selection_mode(Gtk.SelectionMode.NONE) + self.set_selection_mode( Gtk.SelectionMode.NONE ) def _setup_signals(self): def tggl_row_view(lb, row): frame = row.get_child() revealer = frame.get_children()[0] - revealer.set_reveal_child(not revealer.get_reveal_child()) + revealer.set_reveal_child( not revealer.get_reveal_child() ) del frame del revealer @@ -51,12 +52,12 @@ class LogList(Gtk.ListBox): def _load_widgets(self): ... - def add_log_entry(self, m_type: str, data: LSPRequest or LSPNotification or LSPResponse): - message = json.dumps(data, separators = (',', ':'), indent = 4, cls = MessageEncoder) + def add_log_entry(self, m_type: str, data: MessageTypes): + message = get_message_str(data) frame = Gtk.Frame() revealer = Gtk.Revealer() - label = Gtk.Label(label=message) + label = Gtk.Label(label = message) lbl_str = f"> {m_type} Message: {data.id}" if hasattr(data, "id") else f"> {m_type} Notification" label.set_xalign(0.010) diff --git a/src/core/widgets/lsp_message_box.py b/src/core/widgets/lsp_message_box.py index d667c0f..6422394 100644 --- a/src/core/widgets/lsp_message_box.py +++ b/src/core/widgets/lsp_message_box.py @@ -1,5 +1,4 @@ # Python imports -import json # Lib imports import gi @@ -7,6 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports +from libs.dto.lsp_messages import get_message_obj from core.controllers.lsp_controller import LSPController from .buttons.top_button_box import TopButtonBox from .enteries.lsp_message_source_view import LspMessageSourceView @@ -15,12 +15,9 @@ from .buttons.bottom_button_box import BottomButtonBox class LSPMessageBox(Gtk.Box): - def __init__(self, log_list): + def __init__(self): super(LSPMessageBox, self).__init__() - self.lsp_controller = LSPController() - self.lsp_controller.set_log_list(log_list) - self._setup_styling() self._setup_signals() self._subscribe_to_events() @@ -56,6 +53,12 @@ class LSPMessageBox(Gtk.Box): self.add(scrolled_win) self.add(bottom_buttons) + def create_lsp_controller(self, language, log_list): + self.lsp_controller = LSPController() + self.lsp_controller.set_language(language) + self.lsp_controller.set_log_list(log_list) + + def update_message_id_label(self): self.top_buttons.update_message_id_lbl( self.lsp_controller.get_message_id() @@ -74,11 +77,11 @@ class LSPMessageBox(Gtk.Box): self.lsp_controller.send_initialized_message() def button_send_notification(self): - message = json.loads( self.lsp_msg_src_vw.get_text_str() ) + message = get_message_obj( self.lsp_msg_src_vw.get_text_str() ) self.lsp_controller.send_notification(message["method"], message["params"]) def button_send_request(self): - message = json.loads( self.lsp_msg_src_vw.get_text_str() ) + message = get_message_obj( self.lsp_msg_src_vw.get_text_str() ) self.lsp_controller.send_request(message["method"], message["params"]) self.update_message_id_label() diff --git a/src/core/widgets/lsp_ui.py b/src/core/widgets/lsp_ui.py index 552f959..6e89d57 100644 --- a/src/core/widgets/lsp_ui.py +++ b/src/core/widgets/lsp_ui.py @@ -65,7 +65,7 @@ class LSPUI(Gtk.Grid): self.command_entry = CommandEntry( self._data["command"] ) self.socket_entry = SocketEntry( self._data["socket"] ) self.init_ops_src_vw = InitOptionsSourceView( self._data["initialization-options"] ) - self.message_box = LSPMessageBox(self.log_list) + message_box = LSPMessageBox() init_options_lbl.set_margin_top(10) message_box_lbl.set_margin_top(10) @@ -76,6 +76,8 @@ class LSPUI(Gtk.Grid): scrolled_win1.add(self.init_ops_src_vw) scrolled_win2.add(self.log_list) + message_box.create_lsp_controller(self._language, self.log_list) + # child, left, top, width, height self.attach(self.link_btn, 0, 0, 3, 1) @@ -91,7 +93,7 @@ class LSPUI(Gtk.Grid): self.attach(scrolled_win1, 0, 5, 3, 1) self.attach(message_box_lbl, 0, 6, 3, 1) - self.attach(self.message_box, 0, 7, 3, 1) + self.attach(message_box, 0, 7, 3, 1) self.attach(log_list_lbl, 3, 0, 3, 1) self.attach(scrolled_win2, 3, 1, 3, 11) diff --git a/src/libs/dto/lsp_message_structs.py b/src/libs/dto/lsp_message_structs.py index 8118ab2..46ce412 100644 --- a/src/libs/dto/lsp_message_structs.py +++ b/src/libs/dto/lsp_message_structs.py @@ -1,6 +1,7 @@ # Python imports import json import enum +from dataclasses import dataclass # Lib imports @@ -18,11 +19,11 @@ class MessageEncoder(json.JSONEncoder): return o.__dict__ - -class LSPRequest(object): +@dataclass +class ClientRequest(object): def __init__(self, id: int, method: str, params: dict): """ - Constructs a new LSP Request instance. + Constructs a new Client Request instance. :param int id: Message id to track instance. :param str method: The type of lsp request being made. @@ -33,11 +34,11 @@ class LSPRequest(object): self.method = method self.params = params - -class LSPNotification(object): +@dataclass +class ClientNotification(object): def __init__(self, method: str, params: dict): """ - Constructs a new LSP Notification instance. + Constructs a new Client Notification instance. :param str method: The type of lsp notification being made. :param dict params: The arguments of the given method. @@ -46,15 +47,18 @@ class LSPNotification(object): self.method = method self.params = params - +@dataclass class LSPResponse(object): - def __init__(self, id: int or None, result: dict): """ Constructs a new LSP Response instance. :param str method: The type of lsp notification being made. - :param dict params: The arguments of the given method. + :param dict result: The arguments of the given method. """ - self.jsonrpc = "2.0" - self.id = id - self.result = result + jsonrpc: str + id: int or None + result: {} + + +class MessageTypes(ClientRequest, ClientNotification, LSPResponse): + ... \ No newline at end of file diff --git a/src/libs/dto/lsp_messages.py b/src/libs/dto/lsp_messages.py new file mode 100644 index 0000000..fa3a305 --- /dev/null +++ b/src/libs/dto/lsp_messages.py @@ -0,0 +1,92 @@ +# Python imports +import json + +# Lib imports + +# Application imports +from .lsp_message_structs import MessageEncoder + + + +LEN_HEADER = "Content-Length: " +TYPE_HEADER = "Content-Type: " + + + +def get_message_str(data: dict) -> str: + return json.dumps(data, separators = (',', ':'), indent = 4, cls = MessageEncoder) + +def get_message_obj(data: str): + return json.loads(data) + + + +# Request type formatting +# https://github.com/microsoft/multilspy/blob/main/src/multilspy/language_server.py#L417 +content_part = { + "method": "textDocument/definition", + "params": { + "textDocument": { + "uri": "file:///", + "languageId": "python3", + "version": 1, + "text": "" + }, + "position": { + "line": 5, + "character": 12, + "offset": 0 + } + } +} + + +definition_query = { + "method": "textDocument/definition", + "params": { + "textDocument": { + "uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/000_Usable/gtk/LSP-Manager/src/core/widgets/lsp_message_box.py", + "languageId": "python3", + "version": 1, + "text": "" + }, + "position": { + "line": 5, + "character": 12, + "offset": 0 + } + } +} + +references_query = { + "method": "textDocument/references", + "params": { + "context": { + "includeDeclaration": False + }, + "textDocument": { + "uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/000_Usable/gtk/LSP-Manager/src/core/widgets/lsp_message_box.py", + "languageId": "python3", + "version": 1, + "text": "" + }, + "position": { + "line": 30, + "character": 13, + "offset": 0 + } + } +} + + +symbols_query = { + "method": "textDocument/documentSymbol", + "params": { + "textDocument": { + "uri": "file:///home/abaddon/Coding/Projects/Active/Python_Projects/000_Usable/gtk/LSP-Manager/src/core/widgets/lsp_message_box.py", + "languageId": "python3", + "version": 1, + "text": "" + } + } +} \ No newline at end of file diff --git a/src/libs/dto/lsp_structs.py b/src/libs/dto/lsp_structs.py index 9236dd4..7ec4449 100644 --- a/src/libs/dto/lsp_structs.py +++ b/src/libs/dto/lsp_structs.py @@ -1,5 +1,10 @@ +# Python imports import enum +# Lib imports + +# Application imports + def to_type(o, new_type):