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.
This commit is contained in:
@@ -24,6 +24,7 @@ class Plugin(PluginCode):
|
|||||||
self.emit_to("files", event)
|
self.emit_to("files", event)
|
||||||
|
|
||||||
file = event.response
|
file = event.response
|
||||||
|
if not file: return
|
||||||
if file.ftype == "buffer": return
|
if file.ftype == "buffer": return
|
||||||
|
|
||||||
file.check_file_on_disk()
|
file.check_file_on_disk()
|
||||||
|
|||||||
@@ -19,13 +19,10 @@ class LSPClient(LSPClientWebsocket):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(LSPClient, self).__init__()
|
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._language: str = ""
|
||||||
|
self._workspace_path: str = ""
|
||||||
self._init_params: dict = {}
|
self._init_params: dict = {}
|
||||||
self._event_history: dict[int, str] = {}
|
self._init_opts: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_USER_HOME = path.expanduser('~')
|
_USER_HOME = path.expanduser('~')
|
||||||
@@ -33,19 +30,29 @@ class LSPClient(LSPClientWebsocket):
|
|||||||
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
|
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
|
||||||
|
|
||||||
with open(_LSP_INIT_CONFIG) as file:
|
with open(_LSP_INIT_CONFIG) as file:
|
||||||
data = file.read().replace("{user.home}", _USER_HOME)
|
data = file.read()
|
||||||
self._init_params = json.loads(data)
|
self._init_params = json.loads(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
|
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
|
||||||
|
|
||||||
self._message_id: int = -1
|
|
||||||
self._socket = None
|
self._socket = None
|
||||||
|
self._message_id: int = -1
|
||||||
|
self._event_history: dict[int, str] = {}
|
||||||
|
|
||||||
self.read_lock = threading.Lock()
|
self.read_lock = threading.Lock()
|
||||||
self.write_lock = threading.Lock()
|
self.write_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def set_language(self, language: str):
|
def set_language(self, language: str):
|
||||||
self._language = language
|
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):
|
def set_socket(self, socket: str):
|
||||||
self._socket = socket
|
self._socket = socket
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ from ..dto.code.lsp.lsp_messages import symbols_request
|
|||||||
|
|
||||||
|
|
||||||
class LSPClientEvents:
|
class LSPClientEvents:
|
||||||
def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str):
|
def send_initialize_message(self):
|
||||||
folder_name = os.path.basename(workspace_file)
|
folder_name = os.path.basename(self._workspace_path)
|
||||||
|
workspace_uri = f"file://{self._workspace_path}"
|
||||||
|
|
||||||
self._init_params["processId"] = None
|
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["rootUri"] = workspace_uri
|
||||||
self._init_params["workspaceFolders"] = [
|
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)
|
self.send_request("initialize", self._init_params)
|
||||||
|
|
||||||
def send_initialized_message(self):
|
def send_initialized_message(self):
|
||||||
|
|||||||
@@ -54,17 +54,21 @@ class LSPManager(ControllerBase):
|
|||||||
self.response_registry.unregister_handler(event.lang_id)
|
self.response_registry.unregister_handler(event.lang_id)
|
||||||
self.lsp_manager_ui.remove_client_listing(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)
|
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:
|
if result:
|
||||||
ui.toggle_client_buttons(show_close = True)
|
ui.toggle_client_buttons(show_close = True)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _on_close_client(self, ui, lang_id: str) -> bool:
|
def _on_close_client(self, ui, lang_id: str) -> bool:
|
||||||
result = self.close_client(lang_id)
|
result = self.close_client(lang_id)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
ui.toggle_client_buttons(show_close = False)
|
ui.toggle_client_buttons(show_close = False)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def handle_destroy(self):
|
def handle_destroy(self):
|
||||||
@@ -73,12 +77,12 @@ class LSPManager(ControllerBase):
|
|||||||
|
|
||||||
def create_client(
|
def create_client(
|
||||||
self,
|
self,
|
||||||
lang_id: str = "python",
|
lang_id: str,
|
||||||
workspace_uri: str = "",
|
workspace_path: str,
|
||||||
init_opts: dict = {}
|
init_opts: dict[str, str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
client = self.lsp_manager_client.create_client(
|
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)
|
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
|
||||||
@@ -92,7 +96,7 @@ class LSPManager(ControllerBase):
|
|||||||
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
|
||||||
client.send_initialize_message(init_opts, "", f"file://{workspace_uri}")
|
client.send_initialize_message()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class LSPManagerClient(LSPClientEventsMixin):
|
|||||||
|
|
||||||
def create_client(
|
def create_client(
|
||||||
self,
|
self,
|
||||||
lang_id: str = "python",
|
lang_id: str,
|
||||||
workspace_uri: str = "",
|
workspace_path: str,
|
||||||
init_opts: dict = {}
|
init_opts: dict[str, str]
|
||||||
) -> LSPClient:
|
) -> LSPClient:
|
||||||
if lang_id in self.clients: return None
|
if lang_id in self.clients: return None
|
||||||
|
|
||||||
@@ -33,8 +33,10 @@ class LSPManagerClient(LSPClientEventsMixin):
|
|||||||
uri = f"ws://{address}:{port}/{lang_id}"
|
uri = f"ws://{address}:{port}/{lang_id}"
|
||||||
client = LSPClient()
|
client = LSPClient()
|
||||||
|
|
||||||
client.set_language(lang_id)
|
|
||||||
client.set_socket(uri)
|
client.set_socket(uri)
|
||||||
|
client.set_language(lang_id)
|
||||||
|
client.set_workspace_path(workspace_path)
|
||||||
|
client.set_init_opts(init_opts)
|
||||||
client.start_client()
|
client.start_client()
|
||||||
|
|
||||||
if not client.ws_client.wait_for_connection(timeout = 5.0):
|
if not client.ws_client.wait_for_connection(timeout = 5.0):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
from os import path
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
@@ -24,6 +25,8 @@ class LSPManagerUI(Gtk.Dialog):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(LSPManagerUI, self).__init__()
|
super(LSPManagerUI, self).__init__()
|
||||||
|
|
||||||
|
self._USER_HOME = path.expanduser('~')
|
||||||
|
|
||||||
self.client_configs: dict[str, str] = {}
|
self.client_configs: dict[str, str] = {}
|
||||||
|
|
||||||
self.source_view = None
|
self.source_view = None
|
||||||
@@ -167,9 +170,11 @@ 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
|
||||||
|
|
||||||
json_str = self.client_configs[lang_id].replace("{workspace.folder}", workspace_dir)
|
json_str = self.client_configs[lang_id] \
|
||||||
buffer = self.source_view.get_buffer()
|
.replace("{workspace.folder}", workspace_dir) \
|
||||||
|
.replace("{user.home}", self._USER_HOME)
|
||||||
|
|
||||||
|
buffer = self.source_view.get_buffer()
|
||||||
buffer.set_text(json_str, -1)
|
buffer.set_text(json_str, -1)
|
||||||
|
|
||||||
def map_parent_resize_event(self, parent):
|
def map_parent_resize_event(self, parent):
|
||||||
@@ -204,7 +209,7 @@ class LSPManagerUI(Gtk.Dialog):
|
|||||||
model = self.combo_box.get_model()
|
model = self.combo_box.get_model()
|
||||||
|
|
||||||
for i, row in enumerate(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)
|
self.combo_box.remove(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -215,7 +220,9 @@ class LSPManagerUI(Gtk.Dialog):
|
|||||||
if not lang_id or lang_id not in self.client_configs: return {}
|
if not lang_id or lang_id not in self.client_configs: return {}
|
||||||
|
|
||||||
try:
|
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:
|
except json.JSONDecodeError as e:
|
||||||
logger.error(f"Invalid JSON for {lang_id}: {e}")
|
logger.error(f"Invalid JSON for {lang_id}: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class CenterContainer(Gtk.Box):
|
|||||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
self.set_hexpand(True)
|
self.set_hexpand(True)
|
||||||
self.set_vexpand(True)
|
self.set_vexpand(True)
|
||||||
|
self.set_size_request(320, -1)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
self.connect("show", self._handle_show)
|
self.connect("show", self._handle_show)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class EditorsContainer(Gtk.Paned):
|
|||||||
self.set_hexpand(True)
|
self.set_hexpand(True)
|
||||||
self.set_vexpand(True)
|
self.set_vexpand(True)
|
||||||
self.set_wide_handle(True)
|
self.set_wide_handle(True)
|
||||||
|
self.set_size_request(320, -1)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
self.connect("map", self._init_map)
|
self.connect("map", self._init_map)
|
||||||
|
|||||||
@@ -18,16 +18,12 @@ class BaseControllerMixin:
|
|||||||
files = []
|
files = []
|
||||||
|
|
||||||
for arg in unknownargs + [args.new_tab,]:
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
if os.path.isdir( arg.replace("file://", "") ):
|
if os.path.isfile(arg):
|
||||||
files.append( f"DIR|{arg.replace('file://', '')}" )
|
files.append(f"{arg}")
|
||||||
continue
|
|
||||||
|
|
||||||
# NOTE: If passing line number with file split against :
|
if os.path.isdir(arg):
|
||||||
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
message = f"DIR|{arg}"
|
||||||
files.append( f"FILE|{arg.replace('file://', '')}" )
|
ipc_server.send_ipc_message(message)
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(f"Not a File: {arg}")
|
|
||||||
|
|
||||||
if not files: return
|
if not files: return
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ class SourceViewsController(ControllerBase, list):
|
|||||||
self.signal_mapper.connect_signals(source_view)
|
self.signal_mapper.connect_signals(source_view)
|
||||||
|
|
||||||
self.append(source_view)
|
self.append(source_view)
|
||||||
|
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"created_source_view", view = source_view
|
||||||
|
)
|
||||||
|
self.emit(event)
|
||||||
|
|
||||||
return source_view
|
return source_view
|
||||||
|
|
||||||
def first_map_load(self):
|
def first_map_load(self):
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
|
#gi.require_version('Gdk', '3.0')
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
#from gi.repository import Gdk
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
|
||||||
@@ -12,25 +15,41 @@ from gi.repository import Gtk
|
|||||||
class SourceViewDnDMixin:
|
class SourceViewDnDMixin:
|
||||||
|
|
||||||
def _set_up_dnd(self):
|
def _set_up_dnd(self):
|
||||||
PLAIN_TEXT_TARGET_TYPE = 70
|
URI_TARGET_TYPE = 10
|
||||||
URI_TARGET_TYPE = 80
|
PLAIN_TEXT_TARGET_TYPE = 50
|
||||||
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)
|
uri_target = Gtk.TargetEntry.new(
|
||||||
targets = [ text_target, uri_target ]
|
'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)
|
self.drag_dest_set_target_list(targets)
|
||||||
|
|
||||||
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
def _on_drag_data_received(
|
||||||
if info == 70: return
|
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()
|
uris = data.get_uris()
|
||||||
|
|
||||||
if not uris:
|
if not uris:
|
||||||
uris = data.get_text().split("\n")
|
uris = data.get_text().split("\n")
|
||||||
|
|
||||||
self._on_uri_data_received(uris)
|
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]):
|
def _on_uri_data_received(self, uris: list[str]):
|
||||||
uris = self.command.filter_out_loaded_files(uris)
|
uris = self.command.filter_out_loaded_files(uris)
|
||||||
if not uris: return
|
if not uris: return
|
||||||
|
|||||||
@@ -155,9 +155,10 @@ class SourceFile(GtkSource.File):
|
|||||||
self.buffer.unblock_modified_changed_signal()
|
self.buffer.unblock_modified_changed_signal()
|
||||||
|
|
||||||
def is_externally_modified(self) -> bool:
|
def is_externally_modified(self) -> bool:
|
||||||
|
if self.fname == "buffer": return
|
||||||
|
|
||||||
stat = os.stat(self.fpath)
|
stat = os.stat(self.fpath)
|
||||||
current = (stat.st_mtime_ns, stat.st_size)
|
current = (stat.st_mtime_ns, stat.st_size)
|
||||||
|
|
||||||
is_modified = \
|
is_modified = \
|
||||||
hasattr(self, "last_state") and not current == self.last_state
|
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()
|
loaded, contents, etag_out = gfile.load_contents()
|
||||||
if not loaded: raise Exception("File couldn't be loaded...'")
|
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 <20>
|
||||||
|
# "backslashreplace" -> uses escape sequences like \xFF
|
||||||
|
text = contents.decode("UTF-8", errors = "replace")
|
||||||
info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None)
|
info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None)
|
||||||
content_type = info.get_content_type()
|
content_type = info.get_content_type()
|
||||||
self.ftype = Gio.content_type_get_mime_type(content_type) \
|
self.ftype = Gio.content_type_get_mime_type(content_type) \
|
||||||
@@ -177,6 +183,7 @@ class SourceFile(GtkSource.File):
|
|||||||
.replace("text/", "") \
|
.replace("text/", "") \
|
||||||
.replace("x-", "")
|
.replace("x-", "")
|
||||||
|
|
||||||
|
del contents
|
||||||
self.set_path(gfile)
|
self.set_path(gfile)
|
||||||
logger.debug(f"File content type: {self.ftype}")
|
logger.debug(f"File content type: {self.ftype}")
|
||||||
self._load_data(text)
|
self._load_data(text)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
from .code_event import CodeEvent
|
from .code_event import CodeEvent
|
||||||
from .toggle_plugins_ui_event import TogglePluginsUiEvent
|
from .toggle_plugins_ui_event import TogglePluginsUiEvent
|
||||||
from .create_source_view_event import CreateSourceViewEvent
|
from .create_source_view_event import CreateSourceViewEvent
|
||||||
|
from .created_source_view_event import CreatedSourceViewEvent
|
||||||
from .register_completer_event import RegisterCompleterEvent
|
from .register_completer_event import RegisterCompleterEvent
|
||||||
from .unregister_completer_event import UnregisterCompleterEvent
|
from .unregister_completer_event import UnregisterCompleterEvent
|
||||||
from .register_provider_event import RegisterProviderEvent
|
from .register_provider_event import RegisterProviderEvent
|
||||||
|
|||||||
14
src/libs/dto/code/events/created_source_view_event.py
Normal file
14
src/libs/dto/code/events/created_source_view_event.py
Normal file
@@ -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):
|
||||||
|
...
|
||||||
@@ -13,9 +13,11 @@ from .requests import Requests
|
|||||||
class Manifest:
|
class Manifest:
|
||||||
name: str = ""
|
name: str = ""
|
||||||
author: str = ""
|
author: str = ""
|
||||||
credit: str = ""
|
description: str = ""
|
||||||
version: str = "0.0.1"
|
version: str = "0.0.1"
|
||||||
support: str = "support@mail.com"
|
support: str = "support@mail.com"
|
||||||
|
credit: str = ""
|
||||||
|
copyright: str = "GPLv2"
|
||||||
pre_launch: bool = False
|
pre_launch: bool = False
|
||||||
autoload: bool = True
|
autoload: bool = True
|
||||||
requests: Requests = field(default_factory = lambda: Requests())
|
requests: Requests = field(default_factory = lambda: Requests())
|
||||||
|
|||||||
Reference in New Issue
Block a user