refactor(lsp): restructure lsp plugin controller architecture and simplify provider cache
- Replace LSPManager usage with LSPController integration - Move UI access through lsp_controller.lsp_manager_ui - Remove legacy ProviderResponseCache client management - Simplify completion filtering and matcher handling - Improve typing annotations and modernize union syntax - Clean up unused imports and dead code - Fix completion item parsing for insertText/textEdit fallbacks - Add async-safe scrolling via GLib.idle_add
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import threading
|
import threading
|
||||||
|
from os import path
|
||||||
|
import json
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
@@ -22,15 +24,13 @@ class LSPController(LSPControllerWebsocket):
|
|||||||
|
|
||||||
self._language: str = ""
|
self._language: str = ""
|
||||||
self._init_params: dict = {}
|
self._init_params: dict = {}
|
||||||
self._event_history: dict[str] = {}
|
self._event_history: dict[int, str] = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from os import path
|
|
||||||
import json
|
|
||||||
|
|
||||||
_USER_HOME = path.expanduser('~')
|
_USER_HOME = path.expanduser('~')
|
||||||
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
|
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
|
||||||
_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().replace("{user.home}", _USER_HOME)
|
||||||
self._init_params = json.loads(data)
|
self._init_params = json.loads(data)
|
||||||
@@ -42,7 +42,7 @@ class LSPController(LSPControllerWebsocket):
|
|||||||
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):
|
def set_language(self, language: str):
|
||||||
self._language = language
|
self._language = language
|
||||||
|
|
||||||
def set_socket(self, socket: str):
|
def set_socket(self, socket: str):
|
||||||
@@ -51,15 +51,15 @@ class LSPController(LSPControllerWebsocket):
|
|||||||
def unset_socket(self):
|
def unset_socket(self):
|
||||||
self._socket = None
|
self._socket = None
|
||||||
|
|
||||||
def send_notification(self, method: str, params: {} = {}):
|
def send_notification(self, method: str, params: dict = {}):
|
||||||
self._send_message( ClientNotification(method, params) )
|
self._send_message( ClientNotification(method, params) )
|
||||||
|
|
||||||
def send_request(self, method: str, params: {} = {}):
|
def send_request(self, method: str, params: dict = {}):
|
||||||
self._message_id += 1
|
self._message_id += 1
|
||||||
self._event_history[self._message_id] = method
|
self._event_history[self._message_id] = method
|
||||||
self._send_message( ClientRequest(self._message_id, method, params) )
|
self._send_message( ClientRequest(self._message_id, method, params) )
|
||||||
|
|
||||||
def get_event_by_id(self, message_id: int):
|
def get_event_by_id(self, message_id: int) -> str:
|
||||||
if not message_id in self._event_history: return
|
if not message_id in self._event_history: return
|
||||||
return self._event_history[message_id]
|
return self._event_history[message_id]
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import traceback
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
# from libs import websockets
|
# 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_messages import get_message_str, get_message_obj
|
||||||
from libs.dto.code.lsp.lsp_message_structs import \
|
from libs.dto.code.lsp.lsp_message_structs import \
|
||||||
LSPResponseTypes, ClientRequest, ClientNotification, LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
|
LSPResponseTypes, ClientRequest, ClientNotification, \
|
||||||
|
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
|
||||||
|
|
||||||
from .lsp_controller_base import LSPControllerBase
|
from .lsp_controller_base import LSPControllerBase
|
||||||
from .websocket_client import WebsocketClient
|
from .websocket_client import WebsocketClient
|
||||||
@@ -17,7 +16,7 @@ from .websocket_client import WebsocketClient
|
|||||||
|
|
||||||
|
|
||||||
class LSPControllerWebsocket(LSPControllerBase):
|
class LSPControllerWebsocket(LSPControllerBase):
|
||||||
def _send_message(self, data: ClientRequest or ClientNotification):
|
def _send_message(self, data: ClientRequest | ClientNotification):
|
||||||
if not data: return
|
if not data: return
|
||||||
|
|
||||||
message_str = get_message_str(data)
|
message_str = get_message_str(data)
|
||||||
@@ -39,7 +38,7 @@ class LSPControllerWebsocket(LSPControllerBase):
|
|||||||
if not hasattr(self, "ws_client"): return
|
if not hasattr(self, "ws_client"): return
|
||||||
self.ws_client.close_client()
|
self.ws_client.close_client()
|
||||||
|
|
||||||
def _monitor_lsp_response(self, data: None or {}):
|
def _monitor_lsp_response(self, data: dict | None):
|
||||||
if not data: return
|
if not data: return
|
||||||
|
|
||||||
message = get_message_obj(data)
|
message = get_message_obj(data)
|
||||||
|
|||||||
5
plugins/code/ui/lsp_manager/handlers/__init__.py
Normal file
5
plugins/code/ui/lsp_manager/handlers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .base import BaseHandler
|
||||||
|
from .default import DefaultHandler
|
||||||
|
from .python import PythonHandler
|
||||||
|
from .java import JavaHandler
|
||||||
|
from .registry import HandlerRegistry
|
||||||
31
plugins/code/ui/lsp_manager/handlers/base.py
Normal file
31
plugins/code/ui/lsp_manager/handlers/base.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Python imports
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BaseHandler:
|
||||||
|
def __init__(self):
|
||||||
|
self.context = None
|
||||||
|
self.response_cache = None
|
||||||
|
|
||||||
|
|
||||||
|
def set_context(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def set_response_cache(self, response_cache):
|
||||||
|
self.response_cache = response_cache
|
||||||
|
|
||||||
|
@property
|
||||||
|
def emit(self):
|
||||||
|
return self.context.emit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def emit_to(self):
|
||||||
|
return self.context.emit_to
|
||||||
|
|
||||||
|
def handle(self, method: str, response, controller):
|
||||||
|
pass
|
||||||
100
plugins/code/ui/lsp_manager/handlers/default.py
Normal file
100
plugins/code/ui/lsp_manager/handlers/default.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
|
from .base import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultHandler(BaseHandler):
|
||||||
|
"""Fallback handler for unknown languages - uses generic LSP handling."""
|
||||||
|
|
||||||
|
def handle(self, method: str, response, controller):
|
||||||
|
match method:
|
||||||
|
case "textDocument/completion":
|
||||||
|
self._handle_completion(response)
|
||||||
|
case "textDocument/definition":
|
||||||
|
self._handle_definition(response, controller)
|
||||||
|
case "textDocument/publishDiagnostics":
|
||||||
|
self._handle_diagnostics(response)
|
||||||
|
|
||||||
|
def _handle_completion(self, result):
|
||||||
|
if not result: return
|
||||||
|
|
||||||
|
items = result.get("items", []) if isinstance(result, dict) else result
|
||||||
|
|
||||||
|
self.response_cache.matchers.clear()
|
||||||
|
for item in items:
|
||||||
|
label = item.get("label")
|
||||||
|
if not label:
|
||||||
|
continue
|
||||||
|
|
||||||
|
text = (
|
||||||
|
item.get("insertText")
|
||||||
|
or item.get("textEdit", {}).get("newText")
|
||||||
|
or item.get("textEditText", "")
|
||||||
|
or label
|
||||||
|
)
|
||||||
|
|
||||||
|
detail = item.get("detail")
|
||||||
|
doc = item.get("documentation")
|
||||||
|
|
||||||
|
if detail:
|
||||||
|
info = detail
|
||||||
|
elif isinstance(doc, dict):
|
||||||
|
info = doc.get("value", "")
|
||||||
|
else:
|
||||||
|
info = str(doc) if doc else ""
|
||||||
|
|
||||||
|
self.response_cache.matchers[label] = {
|
||||||
|
"label": label,
|
||||||
|
"text": text,
|
||||||
|
"info": info,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context._prompt_completion_request()
|
||||||
|
|
||||||
|
def _handle_definition(self, response, controller):
|
||||||
|
if not response: return
|
||||||
|
|
||||||
|
uri = response[0]["uri"]
|
||||||
|
self.context._prompt_goto_request(uri, response[0]["range"])
|
||||||
|
|
||||||
|
def _handle_diagnostics(self, params):
|
||||||
|
if not params: return
|
||||||
|
|
||||||
|
uri = params.get("uri", "")
|
||||||
|
diagnostics = params.get("diagnostics", [])
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
warnings = []
|
||||||
|
hints = []
|
||||||
|
|
||||||
|
for diag in diagnostics:
|
||||||
|
severity = diag.get("severity", 1)
|
||||||
|
message = diag.get("message", "")
|
||||||
|
range = diag.get("range", {})
|
||||||
|
|
||||||
|
diag_info = {
|
||||||
|
"message": message,
|
||||||
|
"range": range
|
||||||
|
}
|
||||||
|
|
||||||
|
if severity == 1:
|
||||||
|
errors.append(diag_info)
|
||||||
|
elif severity == 2:
|
||||||
|
warnings.append(diag_info)
|
||||||
|
elif severity == 3:
|
||||||
|
hints.append(diag_info)
|
||||||
|
|
||||||
|
self.response_cache.lsp_diagnostics = {
|
||||||
|
"uri": uri,
|
||||||
|
"errors": errors,
|
||||||
|
"warnings": warnings,
|
||||||
|
"hints": hints
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"LSP Diagnostics for {uri}: {len(errors)} errors, {len(warnings)} warnings, {len(hints)} hints")
|
||||||
51
plugins/code/ui/lsp_manager/handlers/java.py
Normal file
51
plugins/code/ui/lsp_manager/handlers/java.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
|
from .default import DefaultHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class JavaHandler(DefaultHandler):
|
||||||
|
"""Java-specific: overrides definition, handles classFileContents."""
|
||||||
|
|
||||||
|
def handle(self, method: str, response, controller):
|
||||||
|
match method:
|
||||||
|
case "textDocument/definition":
|
||||||
|
self._handle_definition(response, controller)
|
||||||
|
case "java/classFileContents":
|
||||||
|
self._handle_class_file_contents(response)
|
||||||
|
case _:
|
||||||
|
super().handle(method, response, controller)
|
||||||
|
|
||||||
|
def _handle_definition(self, response, controller):
|
||||||
|
if not response: return
|
||||||
|
|
||||||
|
uri = response[0]["uri"]
|
||||||
|
if "jdt://" in uri:
|
||||||
|
controller._lsp_java_class_file_contents(uri)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.context._prompt_goto_request(uri, response[0]["range"])
|
||||||
|
|
||||||
|
def _handle_class_file_contents(self, text: str):
|
||||||
|
event = Event_Factory.create_event("get_active_view")
|
||||||
|
self.context.emit_to("source_views", event)
|
||||||
|
|
||||||
|
view = event.response
|
||||||
|
file = view.command.exec("new_file")
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
itr = buffer.get_iter_at_mark(buffer.get_insert())
|
||||||
|
lm = GtkSource.LanguageManager.get_default()
|
||||||
|
language = lm.get_language("java")
|
||||||
|
file.ftype = "java"
|
||||||
|
|
||||||
|
buffer.set_language(language)
|
||||||
|
buffer.insert(itr, text, -1)
|
||||||
12
plugins/code/ui/lsp_manager/handlers/python.py
Normal file
12
plugins/code/ui/lsp_manager/handlers/python.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .default import DefaultHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PythonHandler(DefaultHandler):
|
||||||
|
"""Uses default handling, can override if Python needs special logic."""
|
||||||
|
...
|
||||||
49
plugins/code/ui/lsp_manager/handlers/registry.py
Normal file
49
plugins/code/ui/lsp_manager/handlers/registry.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ..mixins.lsp_server_events_mixin import LSPServerEventsMixin
|
||||||
|
|
||||||
|
from .base import BaseHandler
|
||||||
|
from .default import DefaultHandler
|
||||||
|
from .python import PythonHandler
|
||||||
|
from .java import JavaHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerRegistry(LSPServerEventsMixin):
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self._instances: dict = {}
|
||||||
|
self._lang_handlers: dict = {
|
||||||
|
"default": DefaultHandler,
|
||||||
|
"python": PythonHandler,
|
||||||
|
"java": JavaHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_instance(self, handler_cls: type[BaseHandler]) -> BaseHandler:
|
||||||
|
if handler_cls in self._instances: return self._instances[handler_cls]
|
||||||
|
|
||||||
|
self._instances[handler_cls] = handler_cls()
|
||||||
|
|
||||||
|
return self._instances[handler_cls]
|
||||||
|
|
||||||
|
def register_handler(self, lang_id: str, handler_cls: type[BaseHandler]):
|
||||||
|
self._lang_handlers[lang_id] = handler_cls
|
||||||
|
|
||||||
|
def get_handler(self, lang_id: str = "", method: str = ""):
|
||||||
|
handler_cls = self._lang_handlers.get(
|
||||||
|
lang_id, self._lang_handlers.get("default", DefaultHandler)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not handler_cls: return None
|
||||||
|
|
||||||
|
return self._get_instance(handler_cls)
|
||||||
|
|
||||||
|
def close_handler(self, lang_id: str):
|
||||||
|
if not lang_id in self._lang_handlers: return
|
||||||
|
|
||||||
|
handler_cls = self._lang_handlers[lang_id]
|
||||||
|
self._instances.pop(handler_cls, None)
|
||||||
57
plugins/code/ui/lsp_manager/lsp_client_controller.py
Normal file
57
plugins/code/ui/lsp_manager/lsp_client_controller.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Python imports
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .mixins.lsp_client_events_mixin import LSPClientEventsMixin
|
||||||
|
from .controllers.lsp_controller import LSPController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LSPClientController(LSPClientEventsMixin):
|
||||||
|
def __init__(self):
|
||||||
|
super(LSPClientController, self).__init__()
|
||||||
|
|
||||||
|
self._cache_refresh_timeout_id: int = None
|
||||||
|
|
||||||
|
self.executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers = 1)
|
||||||
|
self.active_language_id: str = ""
|
||||||
|
self.clients: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def create_client(
|
||||||
|
self,
|
||||||
|
lang_id: str = "python",
|
||||||
|
workspace_uri: str = "",
|
||||||
|
init_opts: dict = {}
|
||||||
|
) -> LSPController:
|
||||||
|
if lang_id in self.clients: return None
|
||||||
|
|
||||||
|
address = "127.0.0.1"
|
||||||
|
port = 9999
|
||||||
|
uri = f"ws://{address}:{port}/{lang_id}"
|
||||||
|
client = LSPController()
|
||||||
|
|
||||||
|
client.set_language(lang_id)
|
||||||
|
client.set_socket(uri)
|
||||||
|
client.start_client()
|
||||||
|
|
||||||
|
if not client.ws_client.wait_for_connection(timeout = 5.0):
|
||||||
|
logger.error(f"Failed to connect to LSP server for {lang_id}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.clients[lang_id] = client
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
def close_client(self, lang_id: str) -> bool:
|
||||||
|
if lang_id not in self.clients: return False
|
||||||
|
|
||||||
|
controller = self.clients.pop(lang_id)
|
||||||
|
controller.stop_client()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_active_client(self) -> LSPController:
|
||||||
|
return self.clients[self.active_language_id]
|
||||||
100
plugins/code/ui/lsp_manager/lsp_controller.py
Normal file
100
plugins/code/ui/lsp_manager/lsp_controller.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code.lsp.lsp_message_structs import LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
|
||||||
|
|
||||||
|
from .provider import Provider
|
||||||
|
from .provider_response_cache import ProviderResponseCache
|
||||||
|
from .lsp_manager_ui import LSPManagerUI
|
||||||
|
from .lsp_client_controller import LSPClientController
|
||||||
|
from .handlers.registry import HandlerRegistry
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LSPController:
|
||||||
|
def __init__(self):
|
||||||
|
super(LSPController, self).__init__()
|
||||||
|
|
||||||
|
self._init()
|
||||||
|
self._load_widgets()
|
||||||
|
self._do_bind_mapping()
|
||||||
|
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
self.provider: Provider = Provider()
|
||||||
|
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
||||||
|
self.lsp_client_controller: LSPClientController = LSPClientController()
|
||||||
|
self.handler_registry: HandlerRegistry = HandlerRegistry()
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
self.lsp_manager_ui: LSPManagerUI = LSPManagerUI()
|
||||||
|
self.lsp_manager_ui.create_client = self.create_client
|
||||||
|
self.lsp_manager_ui.close_client = self.close_client
|
||||||
|
|
||||||
|
def _do_bind_mapping(self):
|
||||||
|
self.response_cache.process_file_load = self.lsp_client_controller.process_file_load
|
||||||
|
self.response_cache.process_file_close = self.lsp_client_controller.process_file_close
|
||||||
|
self.response_cache.process_file_save = self.lsp_client_controller.process_file_save
|
||||||
|
self.response_cache.process_file_change = self.lsp_client_controller.process_file_change
|
||||||
|
self.provider.response_cache = self.response_cache
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_client(
|
||||||
|
self,
|
||||||
|
lang_id: str = "python",
|
||||||
|
workspace_uri: str = "",
|
||||||
|
init_opts: dict = {}
|
||||||
|
) -> bool:
|
||||||
|
client = self.lsp_client_controller.create_client(
|
||||||
|
lang_id, workspace_uri, init_opts
|
||||||
|
)
|
||||||
|
handler = self.handler_registry.get_handler(lang_id)
|
||||||
|
self.lsp_client_controller.active_language_id = lang_id
|
||||||
|
|
||||||
|
if not client or not handler:
|
||||||
|
logger.error(f"LSP Controller: Either 'client' or 'handler' didn't get created...'")
|
||||||
|
self.close_client(lang_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
handler.set_context(self.handler_registry)
|
||||||
|
handler.set_response_cache(self.response_cache)
|
||||||
|
|
||||||
|
client.handle_lsp_response = self.server_response
|
||||||
|
client.send_initialize_message(init_opts, "", f"file://{workspace_uri}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def close_client(self, lang_id: str) -> bool:
|
||||||
|
self.lsp_client_controller.close_client(lang_id)
|
||||||
|
self.handler_registry.close_handler(lang_id)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def server_response(self, lsp_response: LSPResponseTypes):
|
||||||
|
logger.debug(f"LSP Response: { lsp_response }")
|
||||||
|
|
||||||
|
if isinstance(lsp_response, LSPResponseRequest):
|
||||||
|
if not self.lsp_client_controller.active_language_id in self.lsp_client_controller.clients:
|
||||||
|
logger.debug(f"No LSP client for '{self.lsp_client_controller.active_language_id}', skipping 'server_response'")
|
||||||
|
return
|
||||||
|
|
||||||
|
controller = self.lsp_client_controller.get_active_client()
|
||||||
|
event = controller.get_event_by_id(lsp_response.id)
|
||||||
|
handler = self.handler_registry.get_handler(
|
||||||
|
self.lsp_client_controller.active_language_id, event
|
||||||
|
)
|
||||||
|
|
||||||
|
if not handler: return
|
||||||
|
handler.handle(event, lsp_response.result, controller)
|
||||||
|
elif isinstance(lsp_response, LSPResponseNotification):
|
||||||
|
handler = self.handler_registry.get_handler("default", lsp_response.method)
|
||||||
|
|
||||||
|
if not handler: return
|
||||||
|
|
||||||
|
# TODO: Need to make default singleton so as to not need to set these here
|
||||||
|
handler.set_context(self.handler_registry)
|
||||||
|
handler.set_response_cache(self.response_cache)
|
||||||
|
handler.handle(lsp_response.method, lsp_response.params, None)
|
||||||
@@ -12,20 +12,18 @@ from gi.repository import GLib
|
|||||||
from gi.repository import GtkSource
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .provider import Provider
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LSPManager(Gtk.Dialog):
|
class LSPManagerUI(Gtk.Dialog):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(LSPManager, self).__init__()
|
super(LSPManagerUI, self).__init__()
|
||||||
|
|
||||||
self._SCRIPT_PTH: str = path.dirname( path.realpath(__file__) )
|
self._SCRIPT_PTH: str = path.dirname( path.realpath(__file__) )
|
||||||
self._USER_HOME: str = path.expanduser('~')
|
self._USER_HOME: str = path.expanduser('~')
|
||||||
self._LSP_SERVERS_CONFIG: str = ""
|
self._LSP_SERVERS_CONFIG: str = ""
|
||||||
self.servers_config: dict = {}
|
self.servers_config: dict = {}
|
||||||
|
|
||||||
self.provider: Provider = Provider()
|
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.source_view = None
|
self.source_view = None
|
||||||
|
|
||||||
@@ -69,8 +67,8 @@ class LSPManager(Gtk.Dialog):
|
|||||||
self.path_bttn.connect("file-set", self._file_set)
|
self.path_bttn.connect("file-set", self._file_set)
|
||||||
self.path_bttn.set_halign(Gtk.Align.FILL)
|
self.path_bttn.set_halign(Gtk.Align.FILL)
|
||||||
self.hide_bttn.connect("clicked", lambda widget: self.hide())
|
self.hide_bttn.connect("clicked", lambda widget: self.hide())
|
||||||
create_client_bttn.connect("clicked", self.create_client, close_client_bttn)
|
create_client_bttn.connect("clicked", self._create_client, close_client_bttn)
|
||||||
close_client_bttn.connect("clicked", self.close_client, create_client_bttn)
|
close_client_bttn.connect("clicked", self._close_client, create_client_bttn)
|
||||||
|
|
||||||
self.main_box.set_column_spacing(15)
|
self.main_box.set_column_spacing(15)
|
||||||
self.main_box.set_row_spacing(15)
|
self.main_box.set_row_spacing(15)
|
||||||
@@ -110,8 +108,7 @@ class LSPManager(Gtk.Dialog):
|
|||||||
widget.move(x, y)
|
widget.move(x, y)
|
||||||
|
|
||||||
def _path_changed(self, widget, buttons_widget):
|
def _path_changed(self, widget, buttons_widget):
|
||||||
fpath = widget.get_text()
|
if not widget.get_text():
|
||||||
if not fpath:
|
|
||||||
buttons_widget.hide()
|
buttons_widget.hide()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -145,10 +142,16 @@ class LSPManager(Gtk.Dialog):
|
|||||||
scrolled_win.show_all()
|
scrolled_win.show_all()
|
||||||
|
|
||||||
def load_lsp_servers_config(self):
|
def load_lsp_servers_config(self):
|
||||||
|
try:
|
||||||
with open(f"{self._SCRIPT_PTH}/configs/lsp-servers-config.json") as file:
|
with open(f"{self._SCRIPT_PTH}/configs/lsp-servers-config.json") as file:
|
||||||
self._LSP_SERVERS_CONFIG = file.read()
|
self._LSP_SERVERS_CONFIG = file.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"Config file not found: {self._SCRIPT_PTH}/configs/lsp-servers-config.json")
|
||||||
|
|
||||||
def load_lsp_servers_config_placeholders(self):
|
def load_lsp_servers_config_placeholders(self):
|
||||||
|
if not self._LSP_SERVERS_CONFIG: return
|
||||||
|
if not self.source_view: return
|
||||||
|
|
||||||
data = self._LSP_SERVERS_CONFIG \
|
data = self._LSP_SERVERS_CONFIG \
|
||||||
.replace("{user.home}", self._USER_HOME) \
|
.replace("{user.home}", self._USER_HOME) \
|
||||||
.replace("{workspace.folder}", self.path_entry.get_text())
|
.replace("{workspace.folder}", self.path_entry.get_text())
|
||||||
@@ -162,24 +165,36 @@ class LSPManager(Gtk.Dialog):
|
|||||||
buffer.delete(start_itr, end_itr)
|
buffer.delete(start_itr, end_itr)
|
||||||
buffer.insert(start_itr, data, -1)
|
buffer.insert(start_itr, data, -1)
|
||||||
|
|
||||||
self.set_language_combo_box( self.servers_config.keys() )
|
self.set_language_combo_box( list(self.servers_config.keys()) )
|
||||||
|
|
||||||
def set_language_combo_box(self, lang_ids: list[str]):
|
def set_language_combo_box(self, lang_ids: list[str]):
|
||||||
|
self.combo_box.remove_all()
|
||||||
|
|
||||||
for lang_id in lang_ids:
|
for lang_id in lang_ids:
|
||||||
self.combo_box.append_text(lang_id)
|
self.combo_box.append_text(lang_id)
|
||||||
|
|
||||||
def create_client(self, widget, sibling):
|
def _create_client(self, widget, sibling):
|
||||||
|
if not self.source_view: return
|
||||||
|
|
||||||
buffer = self.source_view.get_buffer()
|
buffer = self.source_view.get_buffer()
|
||||||
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
|
||||||
if not lang_id in self.servers_config: return
|
if not lang_id in self.servers_config: return
|
||||||
|
|
||||||
self.servers_config = json.loads( buffer.get_text( *buffer.get_bounds() ) )
|
try:
|
||||||
|
self.servers_config = json.loads(
|
||||||
|
buffer.get_text( *buffer.get_bounds() )
|
||||||
|
)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error(f"Invalid JSON: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
init_opts = self.servers_config[lang_id]["initialization-options"]
|
init_opts = self.servers_config[lang_id]["initialization-options"]
|
||||||
workspace_dir = self.path_entry.get_text()
|
workspace_dir = self.path_entry.get_text()
|
||||||
|
result = None
|
||||||
|
|
||||||
result = self.provider.response_cache.create_client(
|
result = self.create_client(
|
||||||
lang_id, workspace_dir, init_opts
|
lang_id, workspace_dir, init_opts
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -188,11 +203,11 @@ class LSPManager(Gtk.Dialog):
|
|||||||
widget.hide()
|
widget.hide()
|
||||||
sibling.show()
|
sibling.show()
|
||||||
|
|
||||||
def close_client(self, widget, sibling):
|
def _close_client(self, widget, sibling):
|
||||||
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
|
||||||
result = self.provider.response_cache.close_client(lang_id)
|
result = self.close_client(lang_id)
|
||||||
if not result: return
|
if not result: return
|
||||||
|
|
||||||
widget.hide()
|
widget.hide()
|
||||||
@@ -23,7 +23,7 @@ class LSPClientEventsMixin:
|
|||||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||||
buffer = event.file.buffer
|
buffer = event.file.buffer
|
||||||
text = buffer.get_text(*buffer.get_bounds())
|
text = buffer.get_text(*buffer.get_bounds())
|
||||||
self._last_active_language_id = lang_id
|
self.active_language_id = lang_id
|
||||||
|
|
||||||
controller._lsp_did_open({
|
controller._lsp_did_open({
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
@@ -54,7 +54,7 @@ class LSPClientEventsMixin:
|
|||||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||||
buffer = event.file.buffer
|
buffer = event.file.buffer
|
||||||
text = buffer.get_text(*buffer.get_bounds())
|
text = buffer.get_text(*buffer.get_bounds())
|
||||||
self._last_active_language_id = lang_id
|
self.active_language_id = lang_id
|
||||||
|
|
||||||
controller._lsp_did_save({"uri": uri, "text": text})
|
controller._lsp_did_save({"uri": uri, "text": text})
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ class LSPClientEventsMixin:
|
|||||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||||
buffer = event.file.buffer
|
buffer = event.file.buffer
|
||||||
text = buffer.get_text(*buffer.get_bounds())
|
text = buffer.get_text(*buffer.get_bounds())
|
||||||
self._last_active_language_id = lang_id
|
self.active_language_id = lang_id
|
||||||
|
|
||||||
controller._lsp_did_change({
|
controller._lsp_did_change({
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
@@ -97,7 +97,7 @@ class LSPClientEventsMixin:
|
|||||||
|
|
||||||
controller = self.clients[lang_id]
|
controller = self.clients[lang_id]
|
||||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||||
self._last_active_language_id = lang_id
|
self.active_language_id = lang_id
|
||||||
|
|
||||||
controller._lsp_definition({
|
controller._lsp_definition({
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
@@ -116,7 +116,7 @@ class LSPClientEventsMixin:
|
|||||||
|
|
||||||
controller = self.clients[lang_id]
|
controller = self.clients[lang_id]
|
||||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||||
self._last_active_language_id = lang_id
|
self.active_language_id = lang_id
|
||||||
|
|
||||||
controller._lsp_completion({
|
controller._lsp_completion({
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
|
|||||||
@@ -29,22 +29,25 @@ class LSPServerEventsMixin:
|
|||||||
|
|
||||||
self.matchers.clear()
|
self.matchers.clear()
|
||||||
for item in items:
|
for item in items:
|
||||||
label = item.get("label", "")
|
label = item.get("label")
|
||||||
if not label: continue
|
if not label: return None
|
||||||
|
|
||||||
text = item.get("insertText")
|
text = (
|
||||||
if not text and "textEdit" in item:
|
item.get("insertText")
|
||||||
text = item["textEdit"].get("newText", "")
|
or item.get("textEdit", {}).get("newText")
|
||||||
|
or item.get("textEditText", "")
|
||||||
|
or label
|
||||||
|
)
|
||||||
|
|
||||||
info = ""
|
detail = item.get("detail")
|
||||||
if "detail" in item:
|
doc = item.get("documentation")
|
||||||
info = item["detail"]
|
|
||||||
elif "documentation" in item:
|
if detail:
|
||||||
doc = item["documentation"]
|
info = detail
|
||||||
if isinstance(doc, dict):
|
elif isinstance(doc, dict):
|
||||||
info = doc.get("value", "")
|
info = doc.get("value", "")
|
||||||
else:
|
else:
|
||||||
info = str(doc)
|
info = str(doc) if doc else ""
|
||||||
|
|
||||||
self.matchers[label] = {
|
self.matchers[label] = {
|
||||||
"label": label,
|
"label": label,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
@@ -8,11 +11,11 @@ from libs.dto.states import SourceViewStates
|
|||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
from plugins.plugin_types import PluginCode
|
||||||
|
|
||||||
from .lsp_manager import LSPManager
|
from .lsp_controller import LSPController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
lsp_manager = LSPManager()
|
lsp_controller = LSPController()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ class Plugin(PluginCode):
|
|||||||
def load(self):
|
def load(self):
|
||||||
window = self.request_ui_element("main-window")
|
window = self.request_ui_element("main-window")
|
||||||
|
|
||||||
lsp_manager.map_parent_resize_event(window)
|
lsp_controller.lsp_manager_ui.map_parent_resize_event(window)
|
||||||
|
|
||||||
event = Event_Factory.create_event("register_command",
|
event = Event_Factory.create_event("register_command",
|
||||||
command_name = "LSP Manager",
|
command_name = "LSP Manager",
|
||||||
@@ -40,7 +43,7 @@ class Plugin(PluginCode):
|
|||||||
event = Event_Factory.create_event(
|
event = Event_Factory.create_event(
|
||||||
"register_provider",
|
"register_provider",
|
||||||
provider_name = "LSP Completer",
|
provider_name = "LSP Completer",
|
||||||
provider = lsp_manager.provider,
|
provider = lsp_controller.provider,
|
||||||
language_ids = []
|
language_ids = []
|
||||||
)
|
)
|
||||||
self.emit_to("completion", event)
|
self.emit_to("completion", event)
|
||||||
@@ -52,12 +55,14 @@ class Plugin(PluginCode):
|
|||||||
self.emit_to("source_views", event)
|
self.emit_to("source_views", event)
|
||||||
|
|
||||||
source_view = event.response
|
source_view = event.response
|
||||||
lsp_manager.load_lsp_servers_config()
|
lsp_controller.lsp_manager_ui.load_lsp_servers_config()
|
||||||
lsp_manager.set_source_view(source_view)
|
lsp_controller.lsp_manager_ui.set_source_view(source_view)
|
||||||
lsp_manager.load_lsp_servers_config_placeholders()
|
lsp_controller.lsp_manager_ui.load_lsp_servers_config_placeholders()
|
||||||
lsp_manager.provider.response_cache.emit = self.emit
|
|
||||||
lsp_manager.provider.response_cache.emit_to = self.emit_to
|
lsp_controller.handler_registry.emit = self.emit
|
||||||
lsp_manager.provider.response_cache._prompt_completion_request = self._prompt_completion_request
|
lsp_controller.handler_registry.emit_to = self.emit_to
|
||||||
|
lsp_controller.handler_registry._prompt_goto_request = self._prompt_goto_request
|
||||||
|
lsp_controller.handler_registry._prompt_completion_request = self._prompt_completion_request
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
...
|
...
|
||||||
@@ -65,6 +70,24 @@ class Plugin(PluginCode):
|
|||||||
def generate_plugin_element(self):
|
def generate_plugin_element(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def _prompt_goto_request(self, uri: str, pointer_pos: dict):
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"get_active_view",
|
||||||
|
)
|
||||||
|
self.emit_to("source_views", event)
|
||||||
|
view = event.response
|
||||||
|
view._on_uri_data_received( [uri] )
|
||||||
|
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
|
||||||
|
def move_cursor(buffer, pointer_pos):
|
||||||
|
itr = buffer.get_iter_at_line( pointer_pos["end"]["line"] )
|
||||||
|
itr.forward_chars( pointer_pos["end"]["character"] )
|
||||||
|
buffer.place_cursor(itr)
|
||||||
|
view.scroll_to_iter(itr, 0.2, False, 0, 0)
|
||||||
|
|
||||||
|
GLib.idle_add( move_cursor, buffer, pointer_pos )
|
||||||
|
|
||||||
def _prompt_completion_request(self):
|
def _prompt_completion_request(self):
|
||||||
event = Event_Factory.create_event(
|
event = Event_Factory.create_event(
|
||||||
"get_active_view",
|
"get_active_view",
|
||||||
@@ -75,7 +98,7 @@ class Plugin(PluginCode):
|
|||||||
event = Event_Factory.create_event(
|
event = Event_Factory.create_event(
|
||||||
"request_completion",
|
"request_completion",
|
||||||
view = view,
|
view = view,
|
||||||
provider = lsp_manager.provider
|
provider = lsp_controller.provider
|
||||||
)
|
)
|
||||||
self.emit_to("completion", event)
|
self.emit_to("completion", event)
|
||||||
|
|
||||||
@@ -98,7 +121,7 @@ class Handler:
|
|||||||
column = iter.get_line_offset()
|
column = iter.get_line_offset()
|
||||||
|
|
||||||
if char_str == "g":
|
if char_str == "g":
|
||||||
lsp_manager.provider.response_cache.process_goto_definition(
|
lsp_controller.lsp_client_controller.process_goto_definition(
|
||||||
file.ftype, file.fpath, line, column
|
file.ftype, file.fpath, line, column
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,4 +130,4 @@ class Handler:
|
|||||||
if char_str == "i":
|
if char_str == "i":
|
||||||
return
|
return
|
||||||
|
|
||||||
lsp_manager.hide() if lsp_manager.is_visible() else lsp_manager.show()
|
lsp_controller.lsp_manager_ui.hide() if lsp_controller.lsp_manager_ui.is_visible() else lsp_controller.lsp_manager_ui.show()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Provider, self).__init__()
|
super(Provider, self).__init__()
|
||||||
|
|
||||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
self.response_cache: ProviderResponseCache = None
|
||||||
|
|
||||||
|
|
||||||
def pre_populate(self, context):
|
def pre_populate(self, context):
|
||||||
@@ -32,13 +32,19 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|||||||
return "LSP Code Completion"
|
return "LSP Code Completion"
|
||||||
|
|
||||||
def do_match(self, context):
|
def do_match(self, context):
|
||||||
|
# Note: If provider is in interactive activation then need to check
|
||||||
|
# view focus as otherwise non focus views start trying to grab it.
|
||||||
|
# completion = context.get_property("completion")
|
||||||
|
# if not completion.get_view().has_focus(): return
|
||||||
|
|
||||||
iter = self.response_cache.get_iter_correctly(context)
|
iter = self.response_cache.get_iter_correctly(context)
|
||||||
iter.backward_char()
|
iter.backward_char()
|
||||||
ch = iter.get_char()
|
ch = iter.get_char()
|
||||||
|
|
||||||
# NOTE: Look to re-add or apply supprting logic to use spaces
|
# NOTE: Look to re-add or apply supporting logic to use spaces
|
||||||
# As is it slows down the editor in certain contexts...
|
# As is it slows down the editor in certain contexts...
|
||||||
if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
||||||
|
if not (ch in ('_', '.') or ch.isalnum()):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
buffer = iter.get_buffer()
|
buffer = iter.get_buffer()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
import asyncio
|
|
||||||
from asyncio import Queue
|
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
@@ -14,113 +12,17 @@ from libs.dto.code.lsp.lsp_message_structs import LSPResponseTypes, LSPResponseR
|
|||||||
|
|
||||||
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
|
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
|
||||||
|
|
||||||
from .controllers.lsp_controller import LSPController
|
|
||||||
from .mixins.lsp_client_events_mixin import LSPClientEventsMixin
|
|
||||||
from .mixins.lsp_server_events_mixin import LSPServerEventsMixin
|
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderResponseCache(ProviderResponseCacheBase):
|
||||||
class ProviderResponseCache(LSPClientEventsMixin, LSPServerEventsMixin, ProviderResponseCacheBase):
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ProviderResponseCache, self).__init__()
|
super(ProviderResponseCache, self).__init__()
|
||||||
|
|
||||||
self.executor = ThreadPoolExecutor(max_workers = 1)
|
|
||||||
self.matchers: dict = {}
|
self.matchers: dict = {}
|
||||||
self.clients: dict = {}
|
|
||||||
self._cache_refresh_timeout_id: int = None
|
|
||||||
self._last_active_language_id: str = None
|
|
||||||
|
|
||||||
|
|
||||||
def create_client(
|
|
||||||
self,
|
|
||||||
lang_id: str = "python",
|
|
||||||
workspace_uri: str = "",
|
|
||||||
init_opts: dict = {
|
|
||||||
}) -> bool:
|
|
||||||
if lang_id in self.clients: return False
|
|
||||||
|
|
||||||
address = "127.0.0.1"
|
|
||||||
port = 9999
|
|
||||||
uri = f"ws://{address}:{port}/{lang_id}"
|
|
||||||
controller = LSPController()
|
|
||||||
controller.handle_lsp_response = self.server_response
|
|
||||||
|
|
||||||
controller.set_language(lang_id)
|
|
||||||
controller.set_socket(uri)
|
|
||||||
controller.start_client()
|
|
||||||
|
|
||||||
if not controller.ws_client.wait_for_connection(timeout = 5.0):
|
|
||||||
logger.error(f"Failed to connect to LSP server for {lang_id}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.clients[lang_id] = controller
|
|
||||||
controller.send_initialize_message(init_opts, "", f"file://{workspace_uri}")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def close_client(self, lang_id: str) -> bool:
|
|
||||||
if lang_id not in self.clients: return False
|
|
||||||
|
|
||||||
controller = self.clients.pop(lang_id)
|
|
||||||
controller.stop_client()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
# TODO: Need to map 'lang_id' to a given language response class and
|
|
||||||
# pass the controller to a 'server_response' method there.
|
|
||||||
# It would allow clean separation of each language's idiosyncracies
|
|
||||||
def server_response(self, lsp_response: LSPResponseTypes):
|
|
||||||
logger.debug(f"LSP Response: { lsp_response }")
|
|
||||||
|
|
||||||
if isinstance(lsp_response, LSPResponseRequest):
|
|
||||||
if not self._last_active_language_id in self.clients:
|
|
||||||
logger.debug(f"No LSP client for '{self._last_active_language_id}', skipping 'server_response'")
|
|
||||||
return
|
|
||||||
|
|
||||||
controller = self.clients[self._last_active_language_id]
|
|
||||||
event = controller.get_event_by_id(lsp_response.id)
|
|
||||||
|
|
||||||
match event:
|
|
||||||
case "textDocument/completion":
|
|
||||||
self._handle_completion_response(lsp_response.result)
|
|
||||||
case "textDocument/definition":
|
|
||||||
result = lsp_response.result
|
|
||||||
if not result: return
|
|
||||||
uri = result[0]["uri"]
|
|
||||||
if "jdt://" in uri:
|
|
||||||
controller._lsp_java_class_file_contents(uri)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._handle_definition_response(uri, result[0]["range"])
|
|
||||||
case "java/classFileContents":
|
|
||||||
self._handle_java_class_file_contents(lsp_response.result)
|
|
||||||
case _:
|
|
||||||
...
|
|
||||||
elif isinstance(lsp_response, LSPResponseNotification):
|
|
||||||
match lsp_response.method:
|
|
||||||
case "textDocument/publishDiagnostics":
|
|
||||||
...
|
|
||||||
case _:
|
|
||||||
...
|
|
||||||
|
|
||||||
def filter(self, word: str) -> list[dict]:
|
def filter(self, word: str) -> list[dict]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
|
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
|
||||||
response = []
|
return list( self.matchers.values() )
|
||||||
iter = self.get_iter_correctly(context)
|
|
||||||
iter.backward_char()
|
|
||||||
char_str = iter.get_char()
|
|
||||||
|
|
||||||
if char_str == "." or char_str == " ":
|
|
||||||
for label, item in self.matchers.items():
|
|
||||||
response.append(item)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
word = self.get_word(context).rstrip()
|
|
||||||
for label, item in self.matchers.items():
|
|
||||||
if label.startswith(word):
|
|
||||||
response.append(item)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|||||||
8
plugins/code/ui/lsp_manager/scripts/CONFIG.sh
Normal file
8
plugins/code/ui/lsp_manager/scripts/CONFIG.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# set -o xtrace ## To debug scripts
|
||||||
|
# set -o errexit ## To exit on error
|
||||||
|
# set -o errunset ## To exit if a variable is referenced but not set
|
||||||
|
|
||||||
|
|
||||||
|
CONTAINER="newton-lsp"
|
||||||
38
plugins/code/ui/lsp_manager/scripts/start.sh
Executable file
38
plugins/code/ui/lsp_manager/scripts/start.sh
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
. CONFIG.sh
|
||||||
|
|
||||||
|
# set -o xtrace ## To debug scripts
|
||||||
|
# set -o errexit ## To exit on error
|
||||||
|
# set -o errunset ## To exit if a variable is referenced but not set
|
||||||
|
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
cd "${SCRIPTPATH}"
|
||||||
|
echo "Working Dir: " $(pwd)
|
||||||
|
|
||||||
|
ID=$(podman ps --filter "ancestor=localhost/${CONTAINER}:latest" --format "{{.ID}}")
|
||||||
|
if [ "${ID}" != "" ]; then
|
||||||
|
echo "Is up..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CODE_HOST="${HOME}/Coding"
|
||||||
|
CODE_CONTAINER="${HOME}/Coding"
|
||||||
|
CONFIG_HOST="${HOME}/.config/lsps"
|
||||||
|
CONFIG_CONTAINER="${HOME}/.config/lsps"
|
||||||
|
|
||||||
|
# podman run -d -m 4G \
|
||||||
|
podman run -m 4G \
|
||||||
|
-p 9999:9999 \
|
||||||
|
-e HOME="${HOME}" \
|
||||||
|
-e MAVEN_OPTS="-Duser.home=${HOME}" \
|
||||||
|
-e JAVA_TOOL_OPTIONS="-Duser.home=${HOME}" \
|
||||||
|
-e JDTLS_CONFIG_PATH="${CONFIG_CONTAINER}/jdtls" \
|
||||||
|
-e JDTLS_DATA_PATH="${JDTLS_CONFIG_PATH}/data" \
|
||||||
|
-v "${CODE_HOST}":"${CODE_CONTAINER}" \
|
||||||
|
-v "${CONFIG_HOST}":"${CONFIG_CONTAINER}" \
|
||||||
|
"${CONTAINER}:latest"
|
||||||
|
}
|
||||||
|
main $@;
|
||||||
23
plugins/code/ui/lsp_manager/scripts/stop.sh
Executable file
23
plugins/code/ui/lsp_manager/scripts/stop.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
. CONFIG.sh
|
||||||
|
|
||||||
|
# set -o xtrace ## To debug scripts
|
||||||
|
# set -o errexit ## To exit on error
|
||||||
|
# set -o errunset ## To exit if a variable is referenced but not set
|
||||||
|
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
cd "${SCRIPTPATH}"
|
||||||
|
echo "Working Dir: " $(pwd)
|
||||||
|
|
||||||
|
ID=$(podman ps --filter "ancestor=localhost/${CONTAINER}:latest" --format "{{.ID}}")
|
||||||
|
if [ "${ID}" == "" ]; then
|
||||||
|
echo "Is not up..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman container stop "${ID}"
|
||||||
|
}
|
||||||
|
main $@;
|
||||||
@@ -9,6 +9,7 @@ from gi.repository import GtkSource
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from ..command_helpers import update_info_bar_if_focused
|
from ..command_helpers import update_info_bar_if_focused
|
||||||
@@ -35,6 +36,10 @@ def execute(
|
|||||||
update_info_bar_if_focused(view.command, view)
|
update_info_bar_if_focused(view.command, view)
|
||||||
view.emit("focus-in-event", Gdk.Event())
|
view.emit("focus-in-event", Gdk.Event())
|
||||||
|
|
||||||
|
def scroll_to_insert_itr(view):
|
||||||
buffer = view.get_buffer()
|
buffer = view.get_buffer()
|
||||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
view.scroll_to_iter(itr, 0.2, False, 0, 0)
|
view.scroll_to_iter(itr, 0.2, False, 0, 0)
|
||||||
|
|
||||||
|
GLib.idle_add(scroll_to_insert_itr, view)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user