Moved plugins and add loaded file filtering

- Moved prettify_json and lsp_completer plugins to sub categories
- Add filter_out_loaded_files to prevent opening already-loaded files
- Refactor code events into single events module
- Fix signal blocking during file load operations
- Include READONLY state in source view lifecycle handling
This commit is contained in:
2026-03-08 00:46:21 -06:00
parent 22abc02d25
commit 220a8c2743
102 changed files with 8471 additions and 153 deletions

View File

@@ -0,0 +1,3 @@
"""
Plugin Controller Module
"""

View File

@@ -0,0 +1,67 @@
# Python imports
import threading
# Lib imports
import gi
from gi.repository import GLib
# Application imports
from libs.dto.code.lsp.lsp_messages import get_message_str
from libs.dto.code.lsp.lsp_message_structs import LSPResponseTypes, ClientRequest, ClientNotification
from .lsp_controller_websocket import LSPControllerWebsocket
class LSPController(LSPControllerWebsocket):
def __init__(self):
super(LSPController, self).__init__()
# 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()
self._language: str = ""
self._init_params: dict = {}
self._event_history: dict[str] = {}
try:
from os import path
import json
_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().replace("{user.home}", _USER_HOME)
self._init_params = json.loads(data)
except Exception as e:
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
self._message_id: int = -1
self._socket = None
self.read_lock = threading.Lock()
self.write_lock = threading.Lock()
def set_language(self, language):
self._language = language
def set_socket(self, socket: str):
self._socket = socket
def unset_socket(self):
self._socket = None
def send_notification(self, method: str, params: {} = {}):
self._send_message( ClientNotification(method, params) )
def send_request(self, method: str, params: {} = {}):
self._message_id += 1
self._event_history[self._message_id] = method
self._send_message( ClientRequest(self._message_id, method, params) )
def get_event_by_id(self, message_id: int):
if not message_id in self._event_history: return
return self._event_history[message_id]
def handle_lsp_response(self, lsp_response: LSPResponseTypes):
raise NotImplementedError

View File

@@ -0,0 +1,19 @@
# Python imports
# Lib imports
# Application imports
from .lsp_controller_events import LSPControllerEvents
from libs.dto.code.lsp.lsp_message_structs import ClientRequest, ClientNotification
class LSPControllerBase(LSPControllerEvents):
def _send_message(self, data: ClientRequest or ClientNotification):
raise NotImplementedError
def start_client(self):
raise NotImplementedError
def stop_client(self):
raise NotImplementedError

View File

@@ -0,0 +1,121 @@
# Python imports
import os
# Lib imports
from gi.repository import GLib
# Application imports
from libs.dto.code.lsp.lsp_messages import get_message_obj
from libs.dto.code.lsp.lsp_messages import didopen_notification
from libs.dto.code.lsp.lsp_messages import didsave_notification
from libs.dto.code.lsp.lsp_messages import didclose_notification
from libs.dto.code.lsp.lsp_messages import didchange_notification
from libs.dto.code.lsp.lsp_messages import completion_request
from libs.dto.code.lsp.lsp_messages import definition_request
from libs.dto.code.lsp.lsp_messages import references_request
from libs.dto.code.lsp.lsp_messages import symbols_request
class LSPControllerEvents:
def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str):
folder_name = os.path.basename(workspace_file)
self._init_params["processId"] = None
self._init_params["rootPath"] = workspace_file
self._init_params["rootUri"] = workspace_uri
self._init_params["workspaceFolders"] = [
{
"name": folder_name,
"uri": workspace_uri
}
]
self._init_params["initializationOptions"] = init_ops
self.send_request("initialize", self._init_params)
def send_initialized_message(self):
self.send_notification("initialized")
def _lsp_did_open(self, data: dict):
method = "textDocument/didOpen"
params = didopen_notification["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["text"] = data["text"]
GLib.idle_add( self.send_notification, method, params )
def _lsp_did_save(self, data: dict):
method = "textDocument/didSave"
params = didsave_notification["params"]
params["textDocument"]["uri"] = data["uri"]
params["text"] = data["text"]
GLib.idle_add( self.send_notification, method, params )
def _lsp_did_close(self, data: dict):
method = "textDocument/didClose"
params = didclose_notification["params"]
params["textDocument"]["uri"] = data["uri"]
GLib.idle_add( self.send_notification, method, params )
def _lsp_did_change(self, data: dict):
method = "textDocument/didChange"
params = didchange_notification["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
contentChanges = params["contentChanges"][0]
contentChanges["text"] = data["text"]
GLib.idle_add( self.send_notification, method, params )
# def _lsp_did_change(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"]
# 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"]
# GLib.idle_add( self.send_notification, method, params )
def _lsp_definition(self, data: dict):
method = "textDocument/definition"
params = definition_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"]
GLib.idle_add( 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"]
GLib.idle_add( self.send_request, method, params )

View File

@@ -0,0 +1,57 @@
# Python imports
import traceback
import subprocess
# Lib imports
from gi.repository import GLib
# Application imports
# from libs import websockets
from libs.dto.code.lsp.lsp_messages import LEN_HEADER, TYPE_HEADER, get_message_str, get_message_obj
from libs.dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification, LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
from .lsp_controller_base import LSPControllerBase
from .websocket_client import WebsocketClient
class LSPControllerWebsocket(LSPControllerBase):
def _send_message(self, data: ClientRequest or ClientNotification):
if not data: return
message_str = get_message_str(data)
message_size = len(message_str)
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
logger.debug(f"Client: {message_str}")
self.ws_client.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()
return self.ws_client
def stop_client(self):
if not hasattr(self, "ws_client"): return
self.ws_client.close_client()
def _monitor_lsp_response(self, data: None or {}):
if not data: return
message = get_message_obj(data)
keys = message.keys()
lsp_response = None
if "result" in keys:
lsp_response = LSPResponseRequest(**get_message_obj(data))
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
GLib.idle_add(self.handle_lsp_response, lsp_response)

View File

@@ -0,0 +1,62 @@
# Python imports
import json
import threading
# Lib imports
# Application imports
from ..libs import websocket
class WebsocketClient:
def __init__(self):
self.ws = None
self._socket = None
self._connected = threading.Event()
def set_socket(self, socket: str):
self._socket = socket
def unset_socket(self):
self._socket = None
def send(self, message: str):
self.ws.send(message)
def on_message(self, ws, message: dict):
self.respond(message)
def on_error(self, ws, error: str):
logger.debug(f"WS Error: {error}")
def on_close(self, ws, close_status_code: int, close_msg: str):
logger.debug("WS Closed...")
def on_open(self, ws):
self._connected.set()
logger.debug("WS opened connection...")
def wait_for_connection(self, timeout: float = 5.0) -> bool:
return self._connected.wait(timeout)
def set_callback(self, callback: object):
self.respond = callback
def close_client(self):
self.ws.close()
@daemon_threaded
def start_client(self):
if not self._socket:
raise Exception("Socket address isn't set so cannot start WebsocketClient listener...")
# websocket.enableTrace(True)
self.ws = websocket.WebSocketApp(self._socket,
on_open = self.on_open,
on_message = self.on_message,
on_error = self.on_error,
on_close = self.on_close)
self.ws.run_forever(reconnect = 0.5)