Refactor LSP manager architecture and event + completion pipeline
- Replace legacy LSPManagerClient + LSPManagerUI with ClientManager and UIManager - Remove WebsocketClient in favor of unified Websocket implementation - Move LSP initialization config loading into centralized config module - Update LSPClient to support dynamic socket, improved init params, and doc version tracking - Introduce range-based didChange notifications and add implementation + references requests - Expand LSPClientEvents to support implementation/references and structured range edits - Simplify websocket response handling and normalize LSP response parsing flow - Decouple UI from LSP manager core; UI now emits address/port for client creation - Refactor completion provider pipeline: - Split TextChangedEvent into TextInsertedEvent and DeleteRangeEvent - Update ProviderResponseCacheBase and controller dispatch paths accordingly - Improve SourceBuffer and SourceFile event tracking with delete-range support - Update plugin system: - Centralize command/provider registration via helper methods - Add LSP commands: definition, references, implementation, toggle UI - Enhance response handlers to support references and implementation hooks - Improve Python LSP config (jedi completion, signatures, references enabled) - Fix minor GTK lifecycle and buffer signal handling issues - Clean up unused imports, dead code, and outdated JSON server configs
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
# Python imports
|
||||
import threading
|
||||
from os import path
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from ..config import get_lsp_init_config
|
||||
from ..dto.code.lsp.lsp_messages import get_message_str
|
||||
from ..dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseTypes, ClientRequest, ClientNotification
|
||||
@@ -19,29 +15,14 @@ class LSPClient(LSPClientWebsocket):
|
||||
def __init__(self):
|
||||
super(LSPClient, self).__init__()
|
||||
|
||||
self._socket: str = ""
|
||||
self._language: str = ""
|
||||
self._workspace_path: str = ""
|
||||
self._init_params: dict = {}
|
||||
self._init_opts: dict = {}
|
||||
|
||||
try:
|
||||
_USER_HOME = path.expanduser('~')
|
||||
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
|
||||
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
|
||||
|
||||
with open(_LSP_INIT_CONFIG) as file:
|
||||
data = file.read()
|
||||
self._init_params = json.loads(data)
|
||||
except Exception as e:
|
||||
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
|
||||
|
||||
|
||||
self._socket = None
|
||||
self._message_id: int = -1
|
||||
self._event_history: dict[int, str] = {}
|
||||
|
||||
self.read_lock = threading.Lock()
|
||||
self.write_lock = threading.Lock()
|
||||
self._init_params: dict = get_lsp_init_config()
|
||||
self._init_opts: dict[str, str] = {}
|
||||
self.doc_vers: dict[str, int] = {}
|
||||
|
||||
|
||||
def set_language(self, language: str):
|
||||
@@ -57,7 +38,7 @@ class LSPClient(LSPClientWebsocket):
|
||||
self._socket = socket
|
||||
|
||||
def unset_socket(self):
|
||||
self._socket = None
|
||||
self._socket = ""
|
||||
|
||||
def send_notification(self, method: str, params: dict = {}):
|
||||
self._send_message( ClientNotification(method, params) )
|
||||
@@ -71,5 +52,5 @@ class LSPClient(LSPClientWebsocket):
|
||||
if not message_id in self._event_history: return
|
||||
return self._event_history[message_id]
|
||||
|
||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes):
|
||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes | dict):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -9,8 +9,10 @@ from ..dto.code.lsp.lsp_messages import didopen_notification
|
||||
from ..dto.code.lsp.lsp_messages import didsave_notification
|
||||
from ..dto.code.lsp.lsp_messages import didclose_notification
|
||||
from ..dto.code.lsp.lsp_messages import didchange_notification
|
||||
from ..dto.code.lsp.lsp_messages import didchange_notification_range
|
||||
from ..dto.code.lsp.lsp_messages import completion_request
|
||||
from ..dto.code.lsp.lsp_messages import definition_request
|
||||
from ..dto.code.lsp.lsp_messages import implementation_request
|
||||
from ..dto.code.lsp.lsp_messages import references_request
|
||||
from ..dto.code.lsp.lsp_messages import symbols_request
|
||||
|
||||
@@ -40,6 +42,7 @@ class LSPClientEvents:
|
||||
def _lsp_did_open(self, data: dict):
|
||||
method = "textDocument/didOpen"
|
||||
params = didopen_notification["params"]
|
||||
self.doc_vers[ data["uri"] ] = -1
|
||||
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["textDocument"]["languageId"] = data["language_id"]
|
||||
@@ -77,24 +80,24 @@ class LSPClientEvents:
|
||||
|
||||
self.send_notification( method, params )
|
||||
|
||||
# def _lsp_did_change(self, data: dict):
|
||||
# method = "textDocument/didChange"
|
||||
# params = didchange_notification_range["params"]
|
||||
def _lsp_did_change_range(self, data: dict):
|
||||
method = "textDocument/didChange"
|
||||
params = didchange_notification_range["params"]
|
||||
|
||||
# params["textDocument"]["uri"] = data["uri"]
|
||||
# params["textDocument"]["languageId"] = data["language_id"]
|
||||
# params["textDocument"]["version"] = data["version"]
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["textDocument"]["languageId"] = data["language_id"]
|
||||
params["textDocument"]["version"] = data["version"]
|
||||
|
||||
# contentChanges = params["contentChanges"][0]
|
||||
# start = contentChanges["range"]["start"]
|
||||
# end = contentChanges["range"]["end"]
|
||||
# contentChanges["text"] = data["text"]
|
||||
# start["line"] = data["line"]
|
||||
# start["character"] = 0
|
||||
# end["line"] = data["line"]
|
||||
# end["character"] = data["column"]
|
||||
contentChanges = params["contentChanges"][0]
|
||||
start = contentChanges["range"]["start"]
|
||||
end = contentChanges["range"]["end"]
|
||||
contentChanges["text"] = data["text"]
|
||||
start["line"] = data["line"]
|
||||
start["character"] = data["column"]
|
||||
end["line"] = data["end_line"]
|
||||
end["character"] = data["end_column"]
|
||||
|
||||
# self.send_notification( method, params )
|
||||
self.send_notification( method, params )
|
||||
|
||||
def _lsp_definition(self, data: dict):
|
||||
method = "textDocument/definition"
|
||||
@@ -108,13 +111,33 @@ class LSPClientEvents:
|
||||
|
||||
self.send_request( method, params )
|
||||
|
||||
def _lsp_implementation(self, data: dict):
|
||||
method = "textDocument/implementation"
|
||||
params = implementation_request["params"]
|
||||
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["position"]["line"] = data["line"]
|
||||
params["position"]["character"] = data["column"]
|
||||
|
||||
self.send_request( method, params )
|
||||
|
||||
def _lsp_references(self, data: dict):
|
||||
method = "textDocument/references"
|
||||
params = references_request["params"]
|
||||
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["textDocument"]["languageId"] = data["language_id"]
|
||||
params["textDocument"]["version"] = data["version"]
|
||||
params["position"]["line"] = data["line"]
|
||||
params["position"]["character"] = data["column"]
|
||||
|
||||
self.send_request( method, params )
|
||||
|
||||
def _lsp_completion(self, data: dict):
|
||||
method = "textDocument/completion"
|
||||
params = completion_request["params"]
|
||||
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["textDocument"]["languageId"] = data["language_id"]
|
||||
params["textDocument"]["version"] = data["version"]
|
||||
params["position"]["line"] = data["line"]
|
||||
params["position"]["character"] = data["column"]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from ..dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
|
||||
|
||||
from .lsp_client_base import LSPClientBase
|
||||
from .websocket_client import WebsocketClient
|
||||
from .websocket import Websocket
|
||||
|
||||
|
||||
|
||||
@@ -24,26 +24,26 @@ class LSPClientWebsocket(LSPClientBase):
|
||||
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
|
||||
|
||||
logger.debug(f"Client: {message_str}")
|
||||
self.ws_client.send(message_str)
|
||||
self.websocket.send(message_str)
|
||||
|
||||
def start_client(self):
|
||||
self.ws_client = WebsocketClient()
|
||||
self.ws_client.set_socket(self._socket)
|
||||
self.ws_client.set_callback(self._monitor_lsp_response)
|
||||
self.ws_client.start_client()
|
||||
self.websocket = Websocket()
|
||||
self.websocket.set_socket(self._socket)
|
||||
self.websocket.set_callback(self._monitor_lsp_response)
|
||||
self.websocket.start_client()
|
||||
|
||||
return self.ws_client
|
||||
return self.websocket
|
||||
|
||||
def stop_client(self):
|
||||
if not hasattr(self, "ws_client"): return
|
||||
self.ws_client.close_client()
|
||||
if not hasattr(self, "websocket"): return
|
||||
self.websocket.close_client()
|
||||
|
||||
def _monitor_lsp_response(self, data: dict | None):
|
||||
if not data: return
|
||||
if not data: return {}
|
||||
|
||||
message = get_message_obj(data)
|
||||
keys = message.keys()
|
||||
lsp_response = None
|
||||
lsp_response = data
|
||||
|
||||
if "result" in keys:
|
||||
lsp_response = LSPResponseRequest(**get_message_obj(data))
|
||||
@@ -51,6 +51,7 @@ class LSPClientWebsocket(LSPClientBase):
|
||||
if "method" in keys:
|
||||
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
|
||||
|
||||
if not lsp_response: return
|
||||
if isinstance(lsp_response, str):
|
||||
lsp_response = get_message_obj(lsp_response)
|
||||
|
||||
GLib.idle_add(self.handle_lsp_response, lsp_response)
|
||||
GLib.idle_add(self.handle_lsp_response, lsp_response)
|
||||
|
||||
@@ -9,7 +9,7 @@ from ..libs import websocket
|
||||
|
||||
|
||||
|
||||
class WebsocketClient:
|
||||
class Websocket:
|
||||
def __init__(self):
|
||||
self.ws = None
|
||||
self._socket = None
|
||||
@@ -59,4 +59,4 @@ class WebsocketClient:
|
||||
on_error = self.on_error,
|
||||
on_close = self.on_close)
|
||||
|
||||
self.ws.run_forever(reconnect = 0.5)
|
||||
self.ws.run_forever(reconnect = 0.5)
|
||||
Reference in New Issue
Block a user