develop #3

Merged
itdominator merged 69 commits from develop into master 2026-03-23 04:51:23 +00:00
317 changed files with 17912 additions and 881 deletions
Showing only changes of commit 52db0b8a31 - Show all commits

View File

@@ -1,5 +1,7 @@
# Python imports
import threading
from os import path
import json
# Lib imports
import gi
@@ -20,17 +22,15 @@ class LSPController(LSPControllerWebsocket):
# 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] = {}
self._language: str = ""
self._init_params: dict = {}
self._event_history: dict[int, 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)
@@ -42,7 +42,7 @@ class LSPController(LSPControllerWebsocket):
self.read_lock = threading.Lock()
self.write_lock = threading.Lock()
def set_language(self, language):
def set_language(self, language: str):
self._language = language
def set_socket(self, socket: str):
@@ -51,15 +51,15 @@ class LSPController(LSPControllerWebsocket):
def unset_socket(self):
self._socket = None
def send_notification(self, method: str, params: {} = {}):
def send_notification(self, method: str, params: dict = {}):
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._event_history[self._message_id] = method
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
return self._event_history[message_id]

View File

@@ -1,15 +1,14 @@
# 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 libs.dto.code.lsp.lsp_messages import 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
@@ -17,7 +16,7 @@ from .websocket_client import WebsocketClient
class LSPControllerWebsocket(LSPControllerBase):
def _send_message(self, data: ClientRequest or ClientNotification):
def _send_message(self, data: ClientRequest | ClientNotification):
if not data: return
message_str = get_message_str(data)
@@ -39,7 +38,7 @@ class LSPControllerWebsocket(LSPControllerBase):
if not hasattr(self, "ws_client"): return
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
message = get_message_obj(data)

View 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

View 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

View 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")

View 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)

View 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."""
...

View 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)

View 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]

View 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)

View File

@@ -12,20 +12,18 @@ from gi.repository import GLib
from gi.repository import GtkSource
# Application imports
from .provider import Provider
class LSPManager(Gtk.Dialog):
class LSPManagerUI(Gtk.Dialog):
def __init__(self):
super(LSPManager, self).__init__()
super(LSPManagerUI, self).__init__()
self._SCRIPT_PTH: str = path.dirname( path.realpath(__file__) )
self._USER_HOME: str = path.expanduser('~')
self._LSP_SERVERS_CONFIG: str = ""
self.servers_config: dict = {}
self.provider: Provider = Provider()
self.parent = 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.set_halign(Gtk.Align.FILL)
self.hide_bttn.connect("clicked", lambda widget: self.hide())
create_client_bttn.connect("clicked", self.create_client, close_client_bttn)
close_client_bttn.connect("clicked", self.close_client, create_client_bttn)
create_client_bttn.connect("clicked", self._create_client, close_client_bttn)
close_client_bttn.connect("clicked", self._close_client, create_client_bttn)
self.main_box.set_column_spacing(15)
self.main_box.set_row_spacing(15)
@@ -110,8 +108,7 @@ class LSPManager(Gtk.Dialog):
widget.move(x, y)
def _path_changed(self, widget, buttons_widget):
fpath = widget.get_text()
if not fpath:
if not widget.get_text():
buttons_widget.hide()
return
@@ -145,10 +142,16 @@ class LSPManager(Gtk.Dialog):
scrolled_win.show_all()
def load_lsp_servers_config(self):
with open(f"{self._SCRIPT_PTH}/configs/lsp-servers-config.json") as file:
self._LSP_SERVERS_CONFIG = file.read()
try:
with open(f"{self._SCRIPT_PTH}/configs/lsp-servers-config.json") as file:
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):
if not self._LSP_SERVERS_CONFIG: return
if not self.source_view: return
data = self._LSP_SERVERS_CONFIG \
.replace("{user.home}", self._USER_HOME) \
.replace("{workspace.folder}", self.path_entry.get_text())
@@ -162,24 +165,36 @@ class LSPManager(Gtk.Dialog):
buffer.delete(start_itr, end_itr)
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]):
self.combo_box.remove_all()
for lang_id in lang_ids:
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()
lang_id = self.combo_box.get_active_text()
if not lang_id: return
if not lang_id in self.servers_config: return
self.servers_config = json.loads( buffer.get_text( *buffer.get_bounds() ) )
init_opts = self.servers_config[lang_id]["initialization-options"]
workspace_dir = self.path_entry.get_text()
try:
self.servers_config = json.loads(
buffer.get_text( *buffer.get_bounds() )
)
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON: {e}")
return
result = self.provider.response_cache.create_client(
init_opts = self.servers_config[lang_id]["initialization-options"]
workspace_dir = self.path_entry.get_text()
result = None
result = self.create_client(
lang_id, workspace_dir, init_opts
)
@@ -188,11 +203,11 @@ class LSPManager(Gtk.Dialog):
widget.hide()
sibling.show()
def close_client(self, widget, sibling):
def _close_client(self, widget, sibling):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
result = self.provider.response_cache.close_client(lang_id)
result = self.close_client(lang_id)
if not result: return
widget.hide()

View File

@@ -23,7 +23,7 @@ class LSPClientEventsMixin:
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
buffer = event.file.buffer
text = buffer.get_text(*buffer.get_bounds())
self._last_active_language_id = lang_id
self.active_language_id = lang_id
controller._lsp_did_open({
"uri": uri,
@@ -54,7 +54,7 @@ class LSPClientEventsMixin:
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
buffer = event.file.buffer
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})
@@ -71,7 +71,7 @@ class LSPClientEventsMixin:
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
buffer = event.file.buffer
text = buffer.get_text(*buffer.get_bounds())
self._last_active_language_id = lang_id
self.active_language_id = lang_id
controller._lsp_did_change({
"uri": uri,
@@ -97,7 +97,7 @@ class LSPClientEventsMixin:
controller = self.clients[lang_id]
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({
"uri": uri,
@@ -116,7 +116,7 @@ class LSPClientEventsMixin:
controller = self.clients[lang_id]
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({
"uri": uri,

View File

@@ -29,22 +29,25 @@ class LSPServerEventsMixin:
self.matchers.clear()
for item in items:
label = item.get("label", "")
if not label: continue
label = item.get("label")
if not label: return None
text = item.get("insertText")
if not text and "textEdit" in item:
text = item["textEdit"].get("newText", "")
text = (
item.get("insertText")
or item.get("textEdit", {}).get("newText")
or item.get("textEditText", "")
or label
)
info = ""
if "detail" in item:
info = item["detail"]
elif "documentation" in item:
doc = item["documentation"]
if isinstance(doc, dict):
info = doc.get("value", "")
else:
info = str(doc)
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.matchers[label] = {
"label": label,

View File

@@ -1,6 +1,9 @@
# Python imports
# Lib imports
import gi
from gi.repository import GLib
# Application imports
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 .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):
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",
command_name = "LSP Manager",
@@ -37,10 +40,10 @@ class Plugin(PluginCode):
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(
event = Event_Factory.create_event(
"register_provider",
provider_name = "LSP Completer",
provider = lsp_manager.provider,
provider = lsp_controller.provider,
language_ids = []
)
self.emit_to("completion", event)
@@ -52,12 +55,14 @@ class Plugin(PluginCode):
self.emit_to("source_views", event)
source_view = event.response
lsp_manager.load_lsp_servers_config()
lsp_manager.set_source_view(source_view)
lsp_manager.load_lsp_servers_config_placeholders()
lsp_manager.provider.response_cache.emit = self.emit
lsp_manager.provider.response_cache.emit_to = self.emit_to
lsp_manager.provider.response_cache._prompt_completion_request = self._prompt_completion_request
lsp_controller.lsp_manager_ui.load_lsp_servers_config()
lsp_controller.lsp_manager_ui.set_source_view(source_view)
lsp_controller.lsp_manager_ui.load_lsp_servers_config_placeholders()
lsp_controller.handler_registry.emit = self.emit
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):
...
@@ -65,6 +70,24 @@ class Plugin(PluginCode):
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):
event = Event_Factory.create_event(
"get_active_view",
@@ -75,7 +98,7 @@ class Plugin(PluginCode):
event = Event_Factory.create_event(
"request_completion",
view = view,
provider = lsp_manager.provider
provider = lsp_controller.provider
)
self.emit_to("completion", event)
@@ -98,7 +121,7 @@ class Handler:
column = iter.get_line_offset()
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
)
@@ -107,4 +130,4 @@ class Handler:
if char_str == "i":
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()

View File

@@ -22,7 +22,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
def __init__(self):
super(Provider, self).__init__()
self.response_cache: ProviderResponseCache = ProviderResponseCache()
self.response_cache: ProviderResponseCache = None
def pre_populate(self, context):
@@ -32,13 +32,19 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
return "LSP Code Completion"
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.backward_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...
if not (ch in ('_', '.', ' ') or ch.isalnum()):
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
if not (ch in ('_', '.') or ch.isalnum()):
return False
buffer = iter.get_buffer()

View File

@@ -1,7 +1,5 @@
# Python imports
from concurrent.futures import ThreadPoolExecutor
import asyncio
from asyncio import Queue
# Lib imports
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 .controllers.lsp_controller import LSPController
from .mixins.lsp_client_events_mixin import LSPClientEventsMixin
from .mixins.lsp_server_events_mixin import LSPServerEventsMixin
class ProviderResponseCache(LSPClientEventsMixin, LSPServerEventsMixin, ProviderResponseCacheBase):
class ProviderResponseCache(ProviderResponseCacheBase):
def __init__(self):
super(ProviderResponseCache, self).__init__()
self.executor = ThreadPoolExecutor(max_workers = 1)
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]:
return []
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
response = []
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
return list( self.matchers.values() )

View 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"

View 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 $@;

View 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 $@;

View File

@@ -9,6 +9,7 @@ from gi.repository import GtkSource
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
from gi.repository import GLib
# Application imports
from ..command_helpers import update_info_bar_if_focused
@@ -35,6 +36,10 @@ def execute(
update_info_bar_if_focused(view.command, view)
view.emit("focus-in-event", Gdk.Event())
buffer = view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() )
view.scroll_to_iter(itr, 0.2, False, 0, 0)
def scroll_to_insert_itr(view):
buffer = view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() )
view.scroll_to_iter(itr, 0.2, False, 0, 0)
GLib.idle_add(scroll_to_insert_itr, view)