From bd277c0214b3e7ba66ae6b755a86da9c4aaa6966 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 29 Mar 2026 14:04:56 -0500 Subject: [PATCH] Refactor LSP manager and file handling - Refactored LSPClient and LSPClientEvents to use workspace path and initialization options. - Updated LSPManager to pass workspace path to client creation. - Modified LSPManagerUI to handle user home paths and workspace configurations. - Enhanced file handling to manage buffer and directory types. - Introduced CreatedSourceViewEvent for source view creation event tracking. - Added better error handling and logging for file operations. Other minor UI and widget adjustments for improved layout and drag-and-drop handling. --- .../file_state_watcher/plugin.py | 1 + .../lsp_manager/client/lsp_client.py | 27 +++++++++----- .../lsp_manager/client/lsp_client_events.py | 9 +++-- .../lsp_manager/lsp_manager.py | 22 ++++++----- .../lsp_manager/lsp_manager_client.py | 10 +++-- .../lsp_manager/lsp_manager_ui.py | 17 ++++++--- src/core/containers/center_container.py | 1 + src/core/containers/code/editors_container.py | 1 + src/core/controllers/base_controller_mixin.py | 14 +++---- .../views/source_views_controller.py | 6 +++ .../code/mixins/source_view_dnd_mixin.py | 37 ++++++++++++++----- src/core/widgets/code/source_file.py | 13 +++++-- src/core/widgets/code/source_view.py | 6 +-- src/libs/dto/code/events/__init__.py | 1 + .../code/events/created_source_view_event.py | 14 +++++++ src/libs/dto/plugins/manifest.py | 4 +- 16 files changed, 126 insertions(+), 57 deletions(-) create mode 100644 src/libs/dto/code/events/created_source_view_event.py diff --git a/plugins/code/event-watchers/file_state_watcher/plugin.py b/plugins/code/event-watchers/file_state_watcher/plugin.py index ae17dd6..49adb0b 100644 --- a/plugins/code/event-watchers/file_state_watcher/plugin.py +++ b/plugins/code/event-watchers/file_state_watcher/plugin.py @@ -24,6 +24,7 @@ class Plugin(PluginCode): self.emit_to("files", event) file = event.response + if not file: return if file.ftype == "buffer": return file.check_file_on_disk() diff --git a/plugins/code/language_server_clients/lsp_manager/client/lsp_client.py b/plugins/code/language_server_clients/lsp_manager/client/lsp_client.py index 816e711..a36f069 100644 --- a/plugins/code/language_server_clients/lsp_manager/client/lsp_client.py +++ b/plugins/code/language_server_clients/lsp_manager/client/lsp_client.py @@ -19,13 +19,10 @@ class LSPClient(LSPClientWebsocket): def __init__(self): super(LSPClient, 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._workspace_path: str = "" self._init_params: dict = {} - self._event_history: dict[int, str] = {} + self._init_opts: dict = {} try: _USER_HOME = path.expanduser('~') @@ -33,19 +30,29 @@ class LSPClient(LSPClientWebsocket): _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) + 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._message_id: int = -1 - self._socket = None - self.read_lock = threading.Lock() - self.write_lock = threading.Lock() + + self._socket = None + self._message_id: int = -1 + self._event_history: dict[int, str] = {} + + self.read_lock = threading.Lock() + self.write_lock = threading.Lock() + def set_language(self, language: str): self._language = language + def set_workspace_path(self, workspace_path: str): + self._workspace_path = workspace_path + + def set_init_opts(self, init_opts: dict[str, str]): + self._init_opts = init_opts + def set_socket(self, socket: str): self._socket = socket diff --git a/plugins/code/language_server_clients/lsp_manager/client/lsp_client_events.py b/plugins/code/language_server_clients/lsp_manager/client/lsp_client_events.py index a4f2bac..df52d8e 100644 --- a/plugins/code/language_server_clients/lsp_manager/client/lsp_client_events.py +++ b/plugins/code/language_server_clients/lsp_manager/client/lsp_client_events.py @@ -17,11 +17,12 @@ from ..dto.code.lsp.lsp_messages import symbols_request class LSPClientEvents: - def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str): - folder_name = os.path.basename(workspace_file) + def send_initialize_message(self): + folder_name = os.path.basename(self._workspace_path) + workspace_uri = f"file://{self._workspace_path}" self._init_params["processId"] = None - self._init_params["rootPath"] = workspace_file + self._init_params["rootPath"] = self._workspace_path self._init_params["rootUri"] = workspace_uri self._init_params["workspaceFolders"] = [ { @@ -30,7 +31,7 @@ class LSPClientEvents: } ] - self._init_params["initializationOptions"] = init_ops + self._init_params["initializationOptions"] = self._init_opts self.send_request("initialize", self._init_params) def send_initialized_message(self): diff --git a/plugins/code/language_server_clients/lsp_manager/lsp_manager.py b/plugins/code/language_server_clients/lsp_manager/lsp_manager.py index cbfce28..a806b89 100644 --- a/plugins/code/language_server_clients/lsp_manager/lsp_manager.py +++ b/plugins/code/language_server_clients/lsp_manager/lsp_manager.py @@ -54,17 +54,21 @@ class LSPManager(ControllerBase): self.response_registry.unregister_handler(event.lang_id) self.lsp_manager_ui.remove_client_listing(event.lang_id) - def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool: + def _on_create_client(self, ui, lang_id: str, workspace_path: str) -> bool: init_opts = ui.get_init_opts(lang_id) - result = self.create_client(lang_id, workspace_uri, init_opts) + result = self.create_client(lang_id, workspace_path, init_opts) + if result: - ui.toggle_client_buttons(show_close=True) + 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) + ui.toggle_client_buttons(show_close = False) + return result def handle_destroy(self): @@ -73,12 +77,12 @@ class LSPManager(ControllerBase): def create_client( self, - lang_id: str = "python", - workspace_uri: str = "", - init_opts: dict = {} + lang_id: str, + workspace_path: str, + init_opts: dict[str, str] ) -> bool: client = self.lsp_manager_client.create_client( - lang_id, workspace_uri, init_opts + lang_id, workspace_path, init_opts ) handler = self.response_registry.get_handler(lang_id) self.lsp_manager_client.active_language_id = lang_id @@ -92,7 +96,7 @@ class LSPManager(ControllerBase): handler.set_response_cache(self.response_cache) client.handle_lsp_response = self.server_response - client.send_initialize_message(init_opts, "", f"file://{workspace_uri}") + client.send_initialize_message() return True diff --git a/plugins/code/language_server_clients/lsp_manager/lsp_manager_client.py b/plugins/code/language_server_clients/lsp_manager/lsp_manager_client.py index 8e649dd..9befbe5 100644 --- a/plugins/code/language_server_clients/lsp_manager/lsp_manager_client.py +++ b/plugins/code/language_server_clients/lsp_manager/lsp_manager_client.py @@ -22,9 +22,9 @@ class LSPManagerClient(LSPClientEventsMixin): def create_client( self, - lang_id: str = "python", - workspace_uri: str = "", - init_opts: dict = {} + lang_id: str, + workspace_path: str, + init_opts: dict[str, str] ) -> LSPClient: if lang_id in self.clients: return None @@ -33,8 +33,10 @@ class LSPManagerClient(LSPClientEventsMixin): uri = f"ws://{address}:{port}/{lang_id}" client = LSPClient() - client.set_language(lang_id) client.set_socket(uri) + client.set_language(lang_id) + client.set_workspace_path(workspace_path) + client.set_init_opts(init_opts) client.start_client() if not client.ws_client.wait_for_connection(timeout = 5.0): diff --git a/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py b/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py index 740dd0d..47c84c0 100644 --- a/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py +++ b/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py @@ -1,4 +1,5 @@ # Python imports +from os import path import json # Lib imports @@ -24,9 +25,11 @@ class LSPManagerUI(Gtk.Dialog): def __init__(self): super(LSPManagerUI, self).__init__() + self._USER_HOME = path.expanduser('~') + self.client_configs: dict[str, str] = {} - self.source_view = None + self.source_view = None self._setup_styling() self._setup_signals() @@ -167,9 +170,11 @@ class LSPManagerUI(Gtk.Dialog): lang_id = self.combo_box.get_active_text() if not lang_id: return - json_str = self.client_configs[lang_id].replace("{workspace.folder}", workspace_dir) - buffer = self.source_view.get_buffer() + json_str = self.client_configs[lang_id] \ + .replace("{workspace.folder}", workspace_dir) \ + .replace("{user.home}", self._USER_HOME) + buffer = self.source_view.get_buffer() buffer.set_text(json_str, -1) def map_parent_resize_event(self, parent): @@ -204,7 +209,7 @@ class LSPManagerUI(Gtk.Dialog): model = self.combo_box.get_model() for i, row in enumerate(model): - if row[0] == lang_id: # assuming text is in column 0 + if row[0] == lang_id: self.combo_box.remove(i) break @@ -215,7 +220,9 @@ class LSPManagerUI(Gtk.Dialog): if not lang_id or lang_id not in self.client_configs: return {} try: - lang_config = json.loads(self.client_configs[lang_id]) + buffer = self.source_view.get_buffer() + json_str = buffer.get_text(*buffer.get_bounds(), -1) + lang_config = json.loads(json_str) except json.JSONDecodeError as e: logger.error(f"Invalid JSON for {lang_id}: {e}") return {} diff --git a/src/core/containers/center_container.py b/src/core/containers/center_container.py index 5fcd3bf..32eb2c7 100644 --- a/src/core/containers/center_container.py +++ b/src/core/containers/center_container.py @@ -28,6 +28,7 @@ class CenterContainer(Gtk.Box): self.set_orientation(Gtk.Orientation.VERTICAL) self.set_hexpand(True) self.set_vexpand(True) + self.set_size_request(320, -1) def _setup_signals(self): self.connect("show", self._handle_show) diff --git a/src/core/containers/code/editors_container.py b/src/core/containers/code/editors_container.py index 675e9ad..200e7be 100644 --- a/src/core/containers/code/editors_container.py +++ b/src/core/containers/code/editors_container.py @@ -29,6 +29,7 @@ class EditorsContainer(Gtk.Paned): self.set_hexpand(True) self.set_vexpand(True) self.set_wide_handle(True) + self.set_size_request(320, -1) def _setup_signals(self): self.connect("map", self._init_map) diff --git a/src/core/controllers/base_controller_mixin.py b/src/core/controllers/base_controller_mixin.py index c0c9eee..6862b46 100644 --- a/src/core/controllers/base_controller_mixin.py +++ b/src/core/controllers/base_controller_mixin.py @@ -18,16 +18,12 @@ class BaseControllerMixin: files = [] for arg in unknownargs + [args.new_tab,]: - if os.path.isdir( arg.replace("file://", "") ): - files.append( f"DIR|{arg.replace('file://', '')}" ) - continue + if os.path.isfile(arg): + files.append(f"{arg}") - # NOTE: If passing line number with file split against : - if os.path.isfile( arg.replace("file://", "").split(":")[0] ): - files.append( f"FILE|{arg.replace('file://', '')}" ) - continue - - logger.info(f"Not a File: {arg}") + if os.path.isdir(arg): + message = f"DIR|{arg}" + ipc_server.send_ipc_message(message) if not files: return diff --git a/src/core/widgets/code/controllers/views/source_views_controller.py b/src/core/widgets/code/controllers/views/source_views_controller.py index cb66763..6503cdf 100644 --- a/src/core/widgets/code/controllers/views/source_views_controller.py +++ b/src/core/widgets/code/controllers/views/source_views_controller.py @@ -111,6 +111,12 @@ class SourceViewsController(ControllerBase, list): self.signal_mapper.connect_signals(source_view) self.append(source_view) + + event = Event_Factory.create_event( + "created_source_view", view = source_view + ) + self.emit(event) + return source_view def first_map_load(self): diff --git a/src/core/widgets/code/mixins/source_view_dnd_mixin.py b/src/core/widgets/code/mixins/source_view_dnd_mixin.py index bee3467..80af4f9 100644 --- a/src/core/widgets/code/mixins/source_view_dnd_mixin.py +++ b/src/core/widgets/code/mixins/source_view_dnd_mixin.py @@ -3,7 +3,10 @@ # Lib imports import gi gi.require_version('Gtk', '3.0') +#gi.require_version('Gdk', '3.0') from gi.repository import Gtk +#from gi.repository import Gdk + # Application imports @@ -12,25 +15,41 @@ from gi.repository import Gtk class SourceViewDnDMixin: def _set_up_dnd(self): - PLAIN_TEXT_TARGET_TYPE = 70 - URI_TARGET_TYPE = 80 - text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE) - uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) - targets = [ text_target, uri_target ] + URI_TARGET_TYPE = 10 + PLAIN_TEXT_TARGET_TYPE = 50 + + uri_target = Gtk.TargetEntry.new( + 'text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE + ) + text_target = Gtk.TargetEntry.new( + 'text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE + ) + targets = Gtk.TargetList.new([ uri_target, text_target ]) self.drag_dest_set_target_list(targets) - def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time): - if info == 70: return + def _on_drag_data_received( + self, widget, drag_context, x, y, data, info, time + ): + target = data.get_target().name() - if info == 80: + if (info == 10) or (target == "text/uri-list"): uris = data.get_uris() - if not uris: uris = data.get_text().split("\n") self._on_uri_data_received(uris) + drag_context.finish(True, False, time) + + return + elif (info == 50) or (target == "text/plain"): + ... + else: + logger.info(f"DnD Dropped File Type: {target}") + + drag_context.finish(False, False, time) + def _on_uri_data_received(self, uris: list[str]): uris = self.command.filter_out_loaded_files(uris) if not uris: return diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py index 1e3efc2..038cecc 100644 --- a/src/core/widgets/code/source_file.py +++ b/src/core/widgets/code/source_file.py @@ -155,9 +155,10 @@ class SourceFile(GtkSource.File): self.buffer.unblock_modified_changed_signal() def is_externally_modified(self) -> bool: - stat = os.stat(self.fpath) - current = (stat.st_mtime_ns, stat.st_size) + if self.fname == "buffer": return + stat = os.stat(self.fpath) + current = (stat.st_mtime_ns, stat.st_size) is_modified = \ hasattr(self, "last_state") and not current == self.last_state @@ -169,7 +170,12 @@ class SourceFile(GtkSource.File): loaded, contents, etag_out = gfile.load_contents() if not loaded: raise Exception("File couldn't be loaded...'") - text = contents.decode("UTF-8") + # Note: + # "strict" (default) -> raises an error on invalid bytes + # "ignore" -> skips invalid bytes entirely + # "replace" -> replaces invalid bytes with � + # "backslashreplace" -> uses escape sequences like \xFF + text = contents.decode("UTF-8", errors = "replace") info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None) content_type = info.get_content_type() self.ftype = Gio.content_type_get_mime_type(content_type) \ @@ -177,6 +183,7 @@ class SourceFile(GtkSource.File): .replace("text/", "") \ .replace("x-", "") + del contents self.set_path(gfile) logger.debug(f"File content type: {self.ftype}") self._load_data(text) diff --git a/src/core/widgets/code/source_view.py b/src/core/widgets/code/source_view.py index 4f348cd..d5ad4ca 100644 --- a/src/core/widgets/code/source_view.py +++ b/src/core/widgets/code/source_view.py @@ -19,10 +19,10 @@ class SourceView(GtkSource.View, SourceViewDnDMixin): def __init__(self, state: SourceViewStates = SourceViewStates.INSERT): super(SourceView, self).__init__() - self.state = state + self.state = state - self.sibling_right = None - self.sibling_left = None + self.sibling_right = None + self.sibling_left = None self._setup_styles() self._setup_signals() diff --git a/src/libs/dto/code/events/__init__.py b/src/libs/dto/code/events/__init__.py index cfe8223..0079fd5 100644 --- a/src/libs/dto/code/events/__init__.py +++ b/src/libs/dto/code/events/__init__.py @@ -6,6 +6,7 @@ from .code_event import CodeEvent from .toggle_plugins_ui_event import TogglePluginsUiEvent from .create_source_view_event import CreateSourceViewEvent +from .created_source_view_event import CreatedSourceViewEvent from .register_completer_event import RegisterCompleterEvent from .unregister_completer_event import UnregisterCompleterEvent from .register_provider_event import RegisterProviderEvent diff --git a/src/libs/dto/code/events/created_source_view_event.py b/src/libs/dto/code/events/created_source_view_event.py new file mode 100644 index 0000000..19ed377 --- /dev/null +++ b/src/libs/dto/code/events/created_source_view_event.py @@ -0,0 +1,14 @@ +# Python imports +from dataclasses import dataclass + +# Lib imports + +# Application imports +from .code_event import CodeEvent +from libs.dto.states.source_view_states import SourceViewStates + + + +@dataclass +class CreatedSourceViewEvent(CodeEvent): + ... diff --git a/src/libs/dto/plugins/manifest.py b/src/libs/dto/plugins/manifest.py index 03c1a9c..4840aa8 100644 --- a/src/libs/dto/plugins/manifest.py +++ b/src/libs/dto/plugins/manifest.py @@ -13,9 +13,11 @@ from .requests import Requests class Manifest: name: str = "" author: str = "" - credit: str = "" + description: str = "" version: str = "0.0.1" support: str = "support@mail.com" + credit: str = "" + copyright: str = "GPLv2" pre_launch: bool = False autoload: bool = True requests: Requests = field(default_factory = lambda: Requests())