Compare commits

..

3 Commits

Author SHA1 Message Date
d42ed7e460 Merge pull request 'develop' (#3) from develop into master
Reviewed-on: #3
2026-03-23 04:51:22 +00:00
ff27fbdf6c Merge pull request 'develop' (#2) from develop into master
Reviewed-on: #2
2025-11-30 05:25:34 +00:00
619fae8a20 Merge pull request 'develop' (#1) from develop into master
Reviewed-on: #1
2024-02-11 01:53:00 +00:00
98 changed files with 1084 additions and 2001 deletions

View File

@@ -25,7 +25,7 @@ class Plugin(PluginCode):
if len(history) == history_size: if len(history) == history_size:
history.pop(0) history.pop(0)
history.append(event.file.fpath) history.append(event.file)
def load(self): def load(self):
self._manage_signals("register_command") self._manage_signals("register_command")
@@ -60,6 +60,6 @@ class Handler:
view._on_uri_data_received( view._on_uri_data_received(
[ [
f"file://{history.pop()}" history.pop().replace("file://", "")
] ]
) )

View File

@@ -14,44 +14,31 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
class Handler: class Handler:
@staticmethod @staticmethod
def execute(view: GtkSource.View, *args, **kwargs): def execute(
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Cut to Temp Buffer") logger.debug("Command: Cut to Temp Buffer")
clear_temp_cut_buffer_delayed(view) clear_temp_cut_buffer_delayed(view)
buffer = view.get_buffer() buffer = view.get_buffer()
itr = buffer.get_iter_at_mark(buffer.get_insert())
if buffer.get_has_selection(): start_itr = itr.copy()
start_itr, end_itr = buffer.get_selection_bounds() start_itr.set_line_offset(0)
start_itr.set_line_offset(0) end_itr = start_itr.copy()
if not end_itr.forward_line():
if not end_itr.ends_line(): end_itr = buffer.get_end_iter()
end_itr.forward_to_line_end()
if not end_itr.is_end():
end_itr.forward_char()
else:
itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy()
start_itr.set_line_offset(0)
end_itr = start_itr.copy()
if not end_itr.forward_line():
end_itr = buffer.get_end_iter()
if not hasattr(view, "_cut_buffer"): if not hasattr(view, "_cut_buffer"):
view._cut_buffer = "" view._cut_buffer = ""
text = buffer.get_text(start_itr, end_itr, True) line_str = buffer.get_text(start_itr, end_itr, True)
view._cut_buffer += line_str
if not text.endswith("\n"):
text += "\n"
view._cut_buffer += text
buffer.delete(start_itr, end_itr) buffer.delete(start_itr, end_itr)
buffer.place_cursor(start_itr)
set_temp_cut_buffer_delayed(view) set_temp_cut_buffer_delayed(view)

View File

@@ -23,7 +23,7 @@ class Plugin(PluginCode):
def load(self): def load(self):
self._manage_signals("register_command") self._manage_signals("register_command")
def unload(self): def load(self):
self._manage_signals("unregister_command") self._manage_signals("unregister_command")
def _manage_signals(self, action: str): def _manage_signals(self, action: str):

View File

@@ -1,3 +0,0 @@
"""
Pligin Module
"""

View File

@@ -1,3 +0,0 @@
"""
Pligin Package
"""

View File

@@ -1,77 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
emit_to: callable = None
def get_source_view(widget):
if isinstance(widget, GtkSource.View):
return widget
if isinstance(widget, Gtk.ScrolledWindow):
return widget.get_child()
if isinstance(widget, Gtk.Paned):
return get_source_view(widget.get_child1())
return None
def execute(
source_view,
char_str,
modkeys_states
):
logger.debug("Command: Close Split Pane")
scrolled_win = source_view.get_parent()
pane = scrolled_win.get_parent()
if not isinstance(pane, Gtk.Paned): return
container = pane.get_parent()
source_view1 = pane.get_child1()
source_view2 = pane.get_child2()
if scrolled_win == source_view1:
remaining = source_view2
closing_view = source_view
else:
remaining = source_view1
closing_view = source_view
remaining_view = get_source_view(remaining)
left = closing_view.sibling_left
right = closing_view.sibling_right
if left:
left.sibling_right = right
if right:
right.sibling_left = left
pane.remove(source_view1)
pane.remove(source_view2)
container.remove(pane)
container.add(remaining)
event = Event_Factory.create_event(
"remove_source_view",
view = closing_view
)
emit_to("source_views", event)
remaining_view.grab_focus()

View File

@@ -1,67 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
emit_to: callable = None
def execute(
source_view1,
char_str,
modkeys_states
):
logger.debug("Command: Split Pane")
scrolled_win1 = source_view1.get_parent()
container = scrolled_win1.get_parent()
pane = Gtk.Paned()
event = Event_Factory.create_event(
"create_source_view",
state = SourceViewStates.INSERT
)
emit_to("source_views", event)
scrolled_win2, \
source_view2 = event.response
old_sibling_right = None
if source_view1.sibling_right:
old_sibling_right = source_view1.sibling_right
source_view1.sibling_right = source_view2
if old_sibling_right:
old_sibling_right.sibling_left = source_view2
source_view2.sibling_right = old_sibling_right
source_view2.sibling_left = source_view1
pane.set_hexpand(True)
pane.set_vexpand(True)
scrolled_win1.set_hexpand(True)
scrolled_win2.set_vexpand(True)
pane.set_wide_handle(True)
container.remove(scrolled_win1)
pane.pack1( scrolled_win1, True, True )
pane.pack2( scrolled_win2, True, True )
container.add(pane)
if char_str == "|":
pane.set_orientation(Gtk.Orientation.VERTICAL)
elif char_str == "\\":
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
pane.show_all()
source_view2.command.exec("new_file")
source_view2.grab_focus()

View File

@@ -1,100 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from plugins.plugin_types import PluginCode
from libs.event_factory import Event_Factory, Code_Event_Types
from . import create_split_view, \
close_split_view, \
focus_left_sibling, \
focus_right_sibling, \
move_to_left_sibling, \
move_to_right_sibling
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
gemit_to = self.emit_to
self._manage_signals("register_command")
def unload(self):
self._manage_signals("unregister_command")
def _manage_signals(self, action: str):
_create_split_view = create_split_view
_close_split_view = close_split_view
_create_split_view.emit_to = self.emit_to
_close_split_view.emit_to = self.emit_to
event = Event_Factory.create_event(action,
command_name = "create_split_view",
command = _create_split_view,
binding_mode = "released",
binding = ["<Control>\\", "<Shift><Control>|"]
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "close_split_view",
command = _close_split_view,
binding_mode = "released",
binding = "<Alt>\\"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_left_sibling",
command = focus_left_sibling,
binding_mode = "released",
binding = "<Control>Page_Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_right_sibling",
command = focus_right_sibling,
binding_mode = "released",
binding = "<Control>Page_Down"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_left_sibling",
command = move_to_left_sibling,
binding_mode = "released",
binding = "<Control><Shift>Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_right_sibling",
command = move_to_right_sibling,
binding_mode = "released",
binding = "<Control><Shift>Down"
)
self.emit_to("source_views", event)
def run(self):
...

View File

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

View File

@@ -0,0 +1,3 @@
"""
Plugin Package
"""

View File

@@ -1,5 +1,5 @@
{ {
"name": "Split Pane", "name": "Toggle Source View",
"author": "ITDominator", "author": "ITDominator",
"version": "0.0.1", "version": "0.0.1",
"support": "", "support": "",

View File

@@ -0,0 +1,64 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
event = Event_Factory.create_event("register_command",
command_name = "toggle_source_view",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>h"
)
self.emit_to("source_views", event)
def unload(self):
event = Event_Factory.create_event("unregister_command",
command_name = "toggle_source_view",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>h"
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
char_str: str,
*args,
**kwargs
):
logger.debug("Command: Toggle Source View")
target = view.get_parent()
target.hide() if target.is_visible() else target.show()
if view.sibling_left:
target = view.sibling_left.get_parent()
target.show()
view.sibling_left.grab_focus()
if view.sibling_right:
target = view.sibling_right.get_parent()
target.show()
view.sibling_right.grab_focus()

View File

@@ -51,10 +51,7 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent): def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
... ...
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent): def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
...
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
... ...
def filter(self, word: str) -> list[dict]: def filter(self, word: str) -> list[dict]:

View File

@@ -49,10 +49,7 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent): def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
... ...
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent): def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
...
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
... ...
def filter(self, word: str) -> list[dict]: def filter(self, word: str) -> list[dict]:

View File

@@ -35,14 +35,11 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent): def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
... ...
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent): def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
buffer = event.buffer buffer = event.file.buffer
self._clear_temp_delay() self._clear_temp_delay()
self._set_temp_delay(buffer) self._set_temp_delay(buffer)
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
...
def _clear_temp_delay(self): def _clear_temp_delay(self):
if self._temp_timeout_id: if self._temp_timeout_id:
GLib.source_remove(self._temp_timeout_id) GLib.source_remove(self._temp_timeout_id)

View File

@@ -24,7 +24,6 @@ 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()

View File

@@ -1,9 +1,13 @@
# Python imports # Python imports
import threading
from os import path
import json
# Lib imports # Lib imports
import gi
from gi.repository import GLib
# Application imports # Application imports
from ..config import get_lsp_init_config
from ..dto.code.lsp.lsp_messages import get_message_str from ..dto.code.lsp.lsp_messages import get_message_str
from ..dto.code.lsp.lsp_message_structs import \ from ..dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification LSPResponseTypes, ClientRequest, ClientNotification
@@ -15,30 +19,38 @@ class LSPClient(LSPClientWebsocket):
def __init__(self): def __init__(self):
super(LSPClient, self).__init__() super(LSPClient, self).__init__()
self._socket: str = "" # https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers
self._language: str = "" # initialize-params-slim.json was created off of jedi_language_server one
self._workspace_path: str = "" # self._init_params = settings_manager.get_lsp_init_data()
self._message_id: int = -1
self._event_history: dict[int, str] = {}
self._init_params: dict = get_lsp_init_config()
self._init_opts: dict[str, str] = {}
self.doc_vers: dict[str, int] = {}
self._language: str = ""
self._init_params: dict = {}
self._event_history: dict[int, str] = {}
try:
_USER_HOME = path.expanduser('~')
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
with open(_LSP_INIT_CONFIG) as file:
data = file.read().replace("{user.home}", _USER_HOME)
self._init_params = json.loads(data)
except Exception as e:
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
self._message_id: int = -1
self._socket = None
self.read_lock = threading.Lock()
self.write_lock = threading.Lock()
def set_language(self, language: 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
def unset_socket(self): def unset_socket(self):
self._socket = "" self._socket = None
def send_notification(self, method: str, params: dict = {}): def send_notification(self, method: str, params: dict = {}):
self._send_message( ClientNotification(method, params) ) self._send_message( ClientNotification(method, params) )
@@ -52,5 +64,5 @@ class LSPClient(LSPClientWebsocket):
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]
def handle_lsp_response(self, lsp_response: LSPResponseTypes | dict): def handle_lsp_response(self, lsp_response: LSPResponseTypes):
raise NotImplementedError raise NotImplementedError

View File

@@ -9,24 +9,19 @@ from ..dto.code.lsp.lsp_messages import didopen_notification
from ..dto.code.lsp.lsp_messages import didsave_notification from ..dto.code.lsp.lsp_messages import didsave_notification
from ..dto.code.lsp.lsp_messages import didclose_notification from ..dto.code.lsp.lsp_messages import didclose_notification
from ..dto.code.lsp.lsp_messages import didchange_notification from ..dto.code.lsp.lsp_messages import didchange_notification
from ..dto.code.lsp.lsp_messages import didchange_notification_range
from ..dto.code.lsp.lsp_messages import completion_request from ..dto.code.lsp.lsp_messages import completion_request
from ..dto.code.lsp.lsp_messages import definition_request from ..dto.code.lsp.lsp_messages import definition_request
from ..dto.code.lsp.lsp_messages import implementation_request
from ..dto.code.lsp.lsp_messages import references_request from ..dto.code.lsp.lsp_messages import references_request
from ..dto.code.lsp.lsp_messages import symbols_request from ..dto.code.lsp.lsp_messages import symbols_request
from ..dto.code.lsp.lsp_messages import shutdown_request
from ..dto.code.lsp.lsp_messages import exit_request
class LSPClientEvents: class LSPClientEvents:
def send_initialize_message(self): def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str):
folder_name = os.path.basename(self._workspace_path) folder_name = os.path.basename(workspace_file)
workspace_uri = f"file://{self._workspace_path}"
self._init_params["processId"] = None self._init_params["processId"] = None
self._init_params["rootPath"] = self._workspace_path self._init_params["rootPath"] = workspace_file
self._init_params["rootUri"] = workspace_uri self._init_params["rootUri"] = workspace_uri
self._init_params["workspaceFolders"] = [ self._init_params["workspaceFolders"] = [
{ {
@@ -35,22 +30,15 @@ class LSPClientEvents:
} }
] ]
self._init_params["initializationOptions"] = self._init_opts self._init_params["initializationOptions"] = init_ops
self.send_request("initialize", self._init_params) self.send_request("initialize", self._init_params)
def send_initialized_notification(self): def send_initialized_message(self):
self.send_notification("initialized") self.send_notification("initialized")
def send_shutdown_request(self):
self.send_request("shutdown")
def send_exit_notification(self):
self.send_notification("exit")
def _lsp_did_open(self, data: dict): def _lsp_did_open(self, data: dict):
method = "textDocument/didOpen" method = "textDocument/didOpen"
params = didopen_notification["params"] params = didopen_notification["params"]
self.doc_vers[ data["uri"] ] = -1
params["textDocument"]["uri"] = data["uri"] params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"] params["textDocument"]["languageId"] = data["language_id"]
@@ -88,24 +76,24 @@ class LSPClientEvents:
self.send_notification( method, params ) self.send_notification( method, params )
def _lsp_did_change_range(self, data: dict): # def _lsp_did_change(self, data: dict):
method = "textDocument/didChange" # method = "textDocument/didChange"
params = didchange_notification_range["params"] # params = didchange_notification_range["params"]
params["textDocument"]["uri"] = data["uri"] # params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"] # params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"] # params["textDocument"]["version"] = data["version"]
contentChanges = params["contentChanges"][0] # contentChanges = params["contentChanges"][0]
start = contentChanges["range"]["start"] # start = contentChanges["range"]["start"]
end = contentChanges["range"]["end"] # end = contentChanges["range"]["end"]
contentChanges["text"] = data["text"] # contentChanges["text"] = data["text"]
start["line"] = data["line"] # start["line"] = data["line"]
start["character"] = data["column"] # start["character"] = 0
end["line"] = data["end_line"] # end["line"] = data["line"]
end["character"] = data["end_column"] # end["character"] = data["column"]
self.send_notification( method, params ) # self.send_notification( method, params )
def _lsp_definition(self, data: dict): def _lsp_definition(self, data: dict):
method = "textDocument/definition" method = "textDocument/definition"
@@ -119,33 +107,13 @@ class LSPClientEvents:
self.send_request( method, params ) self.send_request( method, params )
def _lsp_implementation(self, data: dict):
method = "textDocument/implementation"
params = implementation_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_references(self, data: dict):
method = "textDocument/references"
params = references_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_completion(self, data: dict): def _lsp_completion(self, data: dict):
method = "textDocument/completion" method = "textDocument/completion"
params = completion_request["params"] params = completion_request["params"]
params["textDocument"]["uri"] = data["uri"] params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
params["position"]["line"] = data["line"] params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"] params["position"]["character"] = data["column"]

View File

@@ -11,7 +11,7 @@ from ..dto.code.lsp.lsp_message_structs import \
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
from .lsp_client_base import LSPClientBase from .lsp_client_base import LSPClientBase
from .websocket import Websocket from .websocket_client import WebsocketClient
@@ -24,26 +24,26 @@ class LSPClientWebsocket(LSPClientBase):
message = f"Content-Length: {message_size}\r\n\r\n{message_str}" message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
logger.debug(f"Client: {message_str}") logger.debug(f"Client: {message_str}")
self.websocket.send(message_str) self.ws_client.send(message_str)
def start_client(self): def start_client(self):
self.websocket = Websocket() self.ws_client = WebsocketClient()
self.websocket.set_socket(self._socket) self.ws_client.set_socket(self._socket)
self.websocket.set_callback(self._monitor_lsp_response) self.ws_client.set_callback(self._monitor_lsp_response)
self.websocket.start_client() self.ws_client.start_client()
return self.websocket return self.ws_client
def stop_client(self): def stop_client(self):
if not hasattr(self, "websocket"): return if not hasattr(self, "ws_client"): return
self.websocket.close_client() self.ws_client.close_client()
def _monitor_lsp_response(self, data: dict | None): 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)
keys = message.keys() keys = message.keys()
lsp_response = data lsp_response = None
if "result" in keys: if "result" in keys:
lsp_response = LSPResponseRequest(**get_message_obj(data)) lsp_response = LSPResponseRequest(**get_message_obj(data))
@@ -51,7 +51,6 @@ class LSPClientWebsocket(LSPClientBase):
if "method" in keys: if "method" in keys:
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) ) lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
if isinstance(lsp_response, str): if not lsp_response: return
lsp_response = get_message_obj(lsp_response)
GLib.idle_add(self.handle_lsp_response, lsp_response) GLib.idle_add(self.handle_lsp_response, lsp_response)

View File

@@ -9,7 +9,7 @@ from ..libs import websocket
class Websocket: class WebsocketClient:
def __init__(self): def __init__(self):
self.ws = None self.ws = None
self._socket = None self._socket = None

View File

@@ -1,87 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
class Commands:
lsp_manager: callable = None
class lsp_manager_toggle:
@staticmethod
def execute(
source_view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP Manager Toggle")
if Commands.lsp_manager.ui_manager.is_visible():
Commands.lsp_manager.ui_manager.hide()
else:
Commands.lsp_manager.ui_manager.show()
class lsp_references:
@staticmethod
def execute(
view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP References")
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
Commands.lsp_manager.client_manager.process_references_definition(
file.ftype, file.fpath, line, column
)
class lsp_implementation:
@staticmethod
def execute(
view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP Implements")
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
Commands.lsp_manager.client_manager.process_implementation_definition(
file.ftype, file.fpath, line, column
)
class lsp_definition:
@staticmethod
def execute(
view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP Definition (Go-To)")
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
Commands.lsp_manager.client_manager.process_definition(
file.ftype, file.fpath, line, column
)

View File

@@ -1,38 +0,0 @@
# Python imports
from os import path
import json
# Lib imports
# Application imports
LSP_HOST: str = "127.0.0.1"
LSP_PORT: int = 9999
LSP_CONNECT_TIMOUT: float = 5.0
def get_lsp_host_addr() -> str:
return LSP_HOST
def get_lsp_host_port() -> int:
return LSP_PORT
def get_lsp_connect_timout() -> float:
return LSP_CONNECT_TIMOUT
def get_lsp_init_config() -> dict:
try:
_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()
return json.loads(data)
except Exception as e:
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
return {}

View File

@@ -96,10 +96,10 @@ didchange_notification_range = {
"uri": "file://", "uri": "file://",
"languageId": "python", "languageId": "python",
"version": 1, "version": 1,
"text": ""
}, },
"contentChanges": [ "contentChanges": [
{ {
"text": "",
"range": { "range": {
"start": { "start": {
"line": 1, "line": 1,
@@ -108,8 +108,9 @@ didchange_notification_range = {
"end": { "end": {
"line": 1, "line": 1,
"character": 1, "character": 1,
} },
}, "rangeLength": 0
}
} }
] ]
} }
@@ -124,11 +125,19 @@ completion_request = {
"method": "textDocument/completion", "method": "textDocument/completion",
"params": { "params": {
"textDocument": { "textDocument": {
"uri": "file://" "uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
}, },
"position": { "position": {
"line": 5, "line": 5,
"character": 12 "character": 12,
"offset": 0
},
"contet": {
"triggerKind": 3,
"triggerCharacter": ""
} }
} }
} }
@@ -150,19 +159,6 @@ definition_request = {
} }
} }
implementation_request = {
"method": "textDocument/implementation",
"params": {
"textDocument": {
"uri": "file://"
},
"position": {
"line": 5,
"character": 12
}
}
}
references_request = { references_request = {
"method": "textDocument/references", "method": "textDocument/references",
"params": { "params": {
@@ -183,6 +179,7 @@ references_request = {
} }
} }
symbols_request = { symbols_request = {
"method": "textDocument/documentSymbol", "method": "textDocument/documentSymbol",
"params": { "params": {
@@ -194,14 +191,3 @@ symbols_request = {
} }
} }
} }
shutdown_request = {
"method": "shutdown",
"params": None
}
exit_request = {
"method": "exit",
"params": None
}

View File

@@ -1,9 +1,6 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import GLib
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -14,11 +11,10 @@ from .dto.code.events import \
from .dto.code.lsp.lsp_message_structs import \ from .dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, LSPResponseRequest, LSPResponseNotification LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
from .ui_manager import UIManager
from .provider import Provider from .provider import Provider
from .provider_response_cache import ProviderResponseCache from .provider_response_cache import ProviderResponseCache
from .client_manager import ClientManager from .lsp_manager_ui import LSPManagerUI
from .lsp_manager_client import LSPManagerClient
from .response_handlers.response_registry import ResponseRegistry from .response_handlers.response_registry import ResponseRegistry
@@ -35,16 +31,16 @@ class LSPManager(ControllerBase):
def _init(self): def _init(self):
self.provider: Provider = Provider() self.provider: Provider = Provider()
self.response_cache: ProviderResponseCache = ProviderResponseCache() self.response_cache: ProviderResponseCache = ProviderResponseCache()
self.client_manager: ClientManager = ClientManager() self.lsp_manager_client: LSPManagerClient = LSPManagerClient()
self.response_registry: ResponseRegistry = ResponseRegistry() self.response_registry: ResponseRegistry = ResponseRegistry()
def _load_widgets(self): def _load_widgets(self):
self.ui_manager: LSPManagerUI = UIManager() self.lsp_manager_ui: LSPManagerUI = LSPManagerUI()
self.ui_manager.connect('create-client', self._on_create_client) self.lsp_manager_ui.connect('create-client', self._on_create_client)
self.ui_manager.connect('close-client', self._on_close_client) self.lsp_manager_ui.connect('close-client', self._on_close_client)
def _do_bind_mapping(self): def _do_bind_mapping(self):
self.response_cache.set_lsp_manager_client(self.client_manager) self.response_cache.set_lsp_manager_client(self.lsp_manager_client)
self.provider.response_cache = self.response_cache self.provider.response_cache = self.response_cache
self.response_registry.set_event_hub( self.response_registry.set_event_hub(
self.emit, self.emit_to, self.provider self.emit, self.emit_to, self.provider
@@ -53,51 +49,39 @@ class LSPManager(ControllerBase):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.RegisterLspClientEvent): if isinstance(event, Code_Event_Types.RegisterLspClientEvent):
self.response_registry.register_handler(event.lang_id, event.handler) self.response_registry.register_handler(event.lang_id, event.handler)
self.ui_manager.add_client_listing(event.lang_id, event.lang_config) self.lsp_manager_ui.add_client_listing(event.lang_id, event.lang_config)
elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent): elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent):
self.response_registry.unregister_handler(event.lang_id) self.response_registry.unregister_handler(event.lang_id)
self.ui_manager.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_path: str) -> bool: def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool:
init_opts = ui.get_init_opts(lang_id) init_opts = ui.get_init_opts(lang_id)
result = self.create_client( result = self.create_client(lang_id, workspace_uri, init_opts)
lang_id,
workspace_path,
init_opts,
ui.adddress_entry.get_text(),
f"{ int( ui.adddress_port.get_value() ) }"
)
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):
self.ui_manager.disconnect_by_func(self._on_create_client) self.lsp_manager_ui.disconnect_by_func(self._on_create_client)
self.ui_manager.disconnect_by_func(self._on_close_client) self.lsp_manager_ui.disconnect_by_func(self._on_close_client)
def create_client( def create_client(
self, self,
lang_id: str, lang_id: str = "python",
workspace_path: str, workspace_uri: str = "",
init_opts: dict[str, str], init_opts: dict = {}
address: str,
port: str
) -> bool: ) -> bool:
client = self.client_manager.create_client( client = self.lsp_manager_client.create_client(
lang_id, workspace_path, init_opts, address, port lang_id, workspace_uri, init_opts
) )
handler = self.response_registry.get_handler(lang_id) handler = self.response_registry.get_handler(lang_id)
self.client_manager.active_language_id = lang_id self.lsp_manager_client.active_language_id = lang_id
if not client or not handler: if not client or not handler:
logger.error(f"LSP Manager: Either 'client' or 'handler' didn't get created...'") logger.error(f"LSP Manager: Either 'client' or 'handler' didn't get created...'")
@@ -108,46 +92,28 @@ 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}")
return True return True
def close_client(self, lang_id: str) -> bool: def close_client(self, lang_id: str) -> bool:
controller = self.client_manager.get_active_client() self.lsp_manager_client.close_client(lang_id)
controller.send_shutdown_request() self.response_registry.close_handler(lang_id)
def _close():
self.client_manager.close_client(lang_id)
self.response_registry.close_handler(lang_id)
return False
GLib.timeout_add(5000, _close)
return True return True
def server_response(self, lsp_response: LSPResponseTypes | dict): def server_response(self, lsp_response: LSPResponseTypes):
logger.debug(f"LSP Response: { lsp_response }") logger.debug(f"LSP Response: { lsp_response }")
if isinstance(lsp_response, dict):
if not self.client_manager.active_language_id in self.client_manager.clients:
logger.debug(f"No LSP client for '{self.client_manager.active_language_id}', skipping 'server_response'")
return
controller = self.client_manager.get_active_client()
if "type" in lsp_response and lsp_response["type"] == "connected":
controller.send_initialize_message()
return
if isinstance(lsp_response, LSPResponseRequest): if isinstance(lsp_response, LSPResponseRequest):
if not self.client_manager.active_language_id in self.client_manager.clients: if not self.lsp_manager_client.active_language_id in self.lsp_manager_client.clients:
logger.debug(f"No LSP client for '{self.client_manager.active_language_id}', skipping 'server_response'") logger.debug(f"No LSP client for '{self.lsp_manager_client.active_language_id}', skipping 'server_response'")
return return
controller = self.client_manager.get_active_client() controller = self.lsp_manager_client.get_active_client()
event = controller.get_event_by_id(lsp_response.id) event = controller.get_event_by_id(lsp_response.id)
handler = self.response_registry.get_handler( handler = self.response_registry.get_handler(
self.client_manager.active_language_id, event self.lsp_manager_client.active_language_id, event
) )
if not handler: return if not handler: return

View File

@@ -4,15 +4,14 @@ from concurrent.futures import ThreadPoolExecutor
# Lib imports # Lib imports
# Application imports # Application imports
from .config import get_lsp_connect_timout from .mixins.lsp_client_events_mixin import LSPClientEventsMixin
from .mixins.client_manager_events_mixin import ClientManagerEventsMixin
from .client.lsp_client import LSPClient from .client.lsp_client import LSPClient
class ClientManager(ClientManagerEventsMixin): class LSPManagerClient(LSPClientEventsMixin):
def __init__(self): def __init__(self):
super(ClientManager, self).__init__() super(LSPManagerClient, self).__init__()
self._cache_refresh_timeout_id: int = None self._cache_refresh_timeout_id: int = None
@@ -23,24 +22,22 @@ class ClientManager(ClientManagerEventsMixin):
def create_client( def create_client(
self, self,
lang_id: str, lang_id: str = "python",
workspace_path: str, workspace_uri: str = "",
init_opts: dict[str, str], init_opts: dict = {}
address: str = "127.0.0.1",
port: str = "9999"
) -> LSPClient: ) -> LSPClient:
if lang_id in self.clients: return None if lang_id in self.clients: return None
uri = f"ws://{address}:{port}/{lang_id}?workspace={workspace_path}" address = "127.0.0.1"
port = 9999
uri = f"ws://{address}:{port}/{lang_id}"
client = LSPClient() client = LSPClient()
client.set_socket(uri)
client.set_language(lang_id) client.set_language(lang_id)
client.set_workspace_path(workspace_path) client.set_socket(uri)
client.set_init_opts(init_opts)
client.start_client() client.start_client()
if not client.websocket.wait_for_connection(timeout = get_lsp_connect_timout()): if not client.ws_client.wait_for_connection(timeout = 5.0):
logger.error(f"Failed to connect to LSP server for {lang_id}") logger.error(f"Failed to connect to LSP server for {lang_id}")
return None return None

View File

@@ -0,0 +1,227 @@
# Python imports
import json
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GtkSource
# Application imports
class LSPManagerUI(Gtk.Dialog):
__gsignals__ = {
'create-client': (GObject.SignalFlags.RUN_LAST, None, (str, str)),
'close-client': (GObject.SignalFlags.RUN_LAST, None, (str,)),
}
def __init__(self):
super(LSPManagerUI, self).__init__()
self.client_configs: dict[str, str] = {}
self.source_view = None
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_modal(True)
self.set_decorated(False)
self.set_vexpand(True)
self.set_hexpand(True)
def _setup_signals(self):
self.connect("show", self._handle_show)
self.connect("destroy", self._handle_destroy)
def _subscribe_to_events(self):
...
def _load_widgets(self):
content_area = self.get_content_area()
self.main_box = Gtk.Grid()
self.path_entry = Gtk.SearchEntry()
self.path_bttn = Gtk.FileChooserButton.new(
title = "Workspace Folder",
action = Gtk.FileChooserAction.SELECT_FOLDER
)
self.combo_box = Gtk.ComboBoxText()
self.hide_bttn = Gtk.Button(label = "X")
bttn_box = Gtk.Box()
self.create_client_bttn = Gtk.Button(label = "Create Language Client")
self.close_client_bttn = Gtk.Button(label = "Close Language Client")
self.path_entry.set_can_focus(False)
self.path_entry.set_placeholder_text("Workspace Folder...")
self.path_entry.connect("changed", self._path_changed, bttn_box)
self.path_bttn.set_halign(Gtk.Align.FILL)
self.path_bttn.connect("file-set", self._file_set)
self.combo_box.connect("changed", self._on_combo_changed)
self.hide_bttn_id = self.hide_bttn.connect("clicked", lambda widget: self.hide())
self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn)
self.close_client_bttn.connect("clicked", self._close_client, self.create_client_bttn)
self.main_box.set_column_spacing(15)
self.main_box.set_row_spacing(15)
bttn_box.pack_start(self.create_client_bttn, False, False, 0)
bttn_box.pack_start(self.close_client_bttn, False, False, 0)
self.main_box.attach(child = self.path_entry, left = 0, top = 0, width = 4, height = 1)
self.main_box.attach(child = self.path_bttn, left = 4, top = 0, width = 1, height = 1)
self.main_box.attach(child = self.combo_box, left = 5, top = 0, width = 1, height = 1)
self.main_box.attach(child = self.hide_bttn, left = 6, top = 0, width = 1, height = 1)
self.main_box.attach(child = bttn_box, left = 0, top = 1, width = 1, height = 1)
content_area.set_vexpand(True)
content_area.set_hexpand(True)
content_area.add(self.main_box)
content_area.show_all()
self.close_client_bttn.hide()
bttn_box.hide()
def _handle_show(self, widget):
GLib.idle_add(self.path_entry.grab_focus)
def _handle_destroy(self, widget):
self.disconnect_by_func(self._show)
self.disconnect_by_func(self._handle_destroy)
self.path_bttn.disconnect_by_func(self._file_set)
self.combo_box.disconnect_by_func(self._on_combo_changed)
self.hide_bttn.disconnect(self.hide_bttn_id)
self.create_client_bttn.disconnect_by_func(self._create_client)
self.close_client_bttn.disconnect_by_func(self._close_client)
def _map_resize(self, widget, parent):
parent_x, parent_y = parent.get_position()
parent_width, parent_height = parent.get_size()
if parent_width == 0 or parent_height == 0: return
width = int(parent_width * 0.75)
height = int(parent_height * 0.75)
widget.resize(width, height)
x = parent_x + (parent_width - width) // 2
y = parent_y + (parent_height - height) // 2
widget.move(x, y)
def _path_changed(self, widget, buttons_widget):
if not widget.get_text():
self.path_bttn.unselect_all()
self.path_bttn.emit("file-set")
buttons_widget.hide()
return
self.set_source_view_text( self.path_entry.get_text() )
buttons_widget.show()
def _file_set(self, widget):
fname = widget.get_filename()
fname = "" if not fname else fname
self.path_entry.set_text(fname)
lang_id = self.combo_box.get_active_text()
if not lang_id or lang_id not in self.client_configs: return
self.set_source_view_text(
"{workspace.folder}" if not fname else fname
)
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
workspace_dir = self.path_entry.get_text()
self.emit('create-client', lang_id, workspace_dir)
def _close_client(self, widget, sibling):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
self.emit('close-client', lang_id)
def _on_combo_changed(self, combo: Gtk.ComboBoxText):
lang_id = combo.get_active_text()
self.set_source_view_text( self.path_entry.get_text() )
def set_source_view_text(self, workspace_dir: str):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
json_str = self.client_configs[lang_id].replace("{workspace.folder}", workspace_dir)
buffer = self.source_view.get_buffer()
buffer.set_text(json_str, -1)
def map_parent_resize_event(self, parent):
self.size_allocate_id = parent.connect("size-allocate", lambda w, r: self._map_resize(self, parent))
def unmap_parent_resize_event(self, parent):
parent.disconnect(self.size_allocate_id)
def set_source_view(self, source_view):
scrolled_win = Gtk.ScrolledWindow()
lang_manager = GtkSource.LanguageManager()
buffer = source_view.get_buffer()
language = lang_manager.get_language("json")
self.source_view = source_view
buffer.set_language(language)
buffer.set_style_scheme(self.source_view.syntax_theme)
scrolled_win.set_hexpand(True)
scrolled_win.set_vexpand(True)
scrolled_win.add(self.source_view)
self.main_box.attach(child = scrolled_win, left = 0, top = 2, width = 7, height = 1)
scrolled_win.show_all()
def add_client_listing(self, lang_id: str, lang_config: str):
self.combo_box.append_text(lang_id)
self.client_configs[lang_id] = lang_config
def remove_client_listing(self, lang_id: str):
model = self.combo_box.get_model()
for i, row in enumerate(model):
if row[0] == lang_id: # assuming text is in column 0
self.combo_box.remove(i)
break
if lang_id in self.client_configs:
del self.client_configs[lang_id]
def get_init_opts(self, lang_id: str) -> dict:
if not lang_id or lang_id not in self.client_configs: return {}
try:
lang_config = json.loads(self.client_configs[lang_id])
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON for {lang_id}: {e}")
return {}
return lang_config.get("initialization-options", {})
def toggle_client_buttons(self, show_close: bool):
self.create_client_bttn.set_visible(not show_close)
self.close_client_bttn.set_visible(show_close)

View File

@@ -1,191 +0,0 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Code_Event_Types
class ClientManagerEventsMixin:
def _get_controller(self, lang_id, action: str):
controller = self.clients.get(lang_id)
if not controller:
logger.debug(f"No LSP client for '{lang_id}', skipping {action}...")
return controller
def _uri(self, fpath: str) -> str:
return fpath if fpath.startswith("file://") else f"file://{fpath}"
def _text(self, buffer, *, hidden = False):
return buffer.get_text(
*buffer.get_bounds(),
include_hidden_chars=hidden
)
def _version(self, controller, uri, bump = False):
if bump:
controller.doc_vers[uri] = controller.doc_vers.get(uri, -1) + 1
return controller.doc_vers.get(uri, 0)
def _activate(self, lang_id):
self.active_language_id = lang_id
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
f = event.file
if not (c := self._get_controller(f.ftype, "didOpen")): return
uri = self._uri(f.fpath)
self._activate(f.ftype)
c._lsp_did_open({
"uri": uri,
"language_id": f.ftype,
"text": self._text(f.buffer),
})
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
f = event.file
if not (c := self._get_controller(f.ftype, "didClose")): return
uri = self._uri(f.fpath)
c.doc_vers.pop(uri, None)
c._lsp_did_close({"uri": uri})
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
f = event.file
if not (c := self._get_controller(f.ftype, "didSave")): return
uri = self._uri(f.fpath)
self._activate(f.ftype)
c._lsp_did_save({
"uri": uri,
"text": self._text(f.buffer),
})
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
f = event.file
if not (c := self._get_controller(f.ftype, "didChange")): return
uri = self._uri(f.fpath)
self._activate(f.ftype)
version = self._version(c, uri, bump = True)
c._lsp_did_change({
"uri": uri,
"language_id": f.ftype,
"version": version,
"text": self._text(f.buffer, hidden = True),
})
it = f.buffer.get_iter_at_mark(f.buffer.get_insert())
self._set_cache_refresh_trigger(
f.ftype, f.fpath,
it.get_line(),
it.get_line_offset() + 1
)
def _iter_pos(self, it):
return it.get_line(), it.get_line_offset()
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
f = event.file
if not (c := self._get_controller(f.ftype, "didChange")): return
uri = self._uri(f.fpath)
self._activate(f.ftype)
start_it = event.location.copy()
end_it = event.location.copy()
if event.length > 1:
start_it.backward_chars(event.length)
sl, sc = self._iter_pos(start_it)
el, ec = self._iter_pos(end_it)
sc -= 0 if event.length > 1 else 1
ec -= 1
version = self._version(c, uri, bump = True)
c._lsp_did_change_range({
"uri": uri,
"language_id": f.ftype,
"version": version,
"text": event.text,
"line": sl,
"column": sc,
"end_line": el,
"end_column": ec,
})
it = event.buffer.get_iter_at_mark(event.buffer.get_insert())
self._set_cache_refresh_trigger(
f.ftype, f.fpath, *self._iter_pos(it)
)
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
f = event.file
if not (c := self._get_controller(f.ftype, "didChange")): return
uri = self._uri(f.fpath)
self._activate(f.ftype)
start_it, end_it = event.start.copy(), event.end.copy()
if start_it.compare(end_it) > 0:
start_it, end_it = end_it, start_it
sl, sc = self._iter_pos(start_it)
el, ec = self._iter_pos(end_it)
version = self._version(c, uri, bump = True)
c._lsp_did_change_range({
"uri": uri,
"language_id": f.ftype,
"version": version,
"text": "",
"line": sl,
"column": sc,
"end_line": el,
"end_column": ec,
})
it = event.buffer.get_iter_at_mark(event.buffer.get_insert())
self._set_cache_refresh_trigger(
f.ftype, f.fpath, *self._iter_pos(it)
)
def _request(self, method, lang_id, fpath, **extra):
if not (c := self._get_controller(lang_id, method)): return
uri = self._uri(fpath)
self._activate(lang_id)
payload = {
"uri": uri,
"language_id": lang_id,
"version": self._version(c, uri),
**extra
}
getattr(c, method)(payload)
def process_definition(self, lang_id, fpath, line, column):
self._request("_lsp_definition", lang_id, fpath, line = line, column = column)
def process_implementation_definition(self, lang_id, fpath, line, column):
self._request("_lsp_implementation", lang_id, fpath, line = line, column = column)
def process_references_definition(self, lang_id, fpath, line, column):
self._request("_lsp_references", lang_id, fpath, line = line, column = column)
def process_completion_request(self, lang_id, fpath, line, column):
self._request("_lsp_completion", lang_id, fpath, line = line, column = column)
def _set_cache_refresh_trigger(self, lang_id, fpath, line, column):
self.process_completion_request(lang_id, fpath, line, column)

View File

@@ -0,0 +1,144 @@
# Python imports
# Lib imports
import gi
from gi.repository import GLib
# Application imports
from libs.event_factory import Code_Event_Types
class LSPClientEventsMixin:
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
lang_id = event.file.ftype
if lang_id not in self.clients:
logger.debug(f"No LSP client for '{lang_id}', skipping didOpen")
return
controller = self.clients[lang_id]
fpath = event.file.fpath
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
buffer = event.file.buffer
text = buffer.get_text(*buffer.get_bounds())
self.active_language_id = lang_id
controller._lsp_did_open({
"uri": uri,
"language_id": lang_id,
"text": text
})
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
lang_id = event.file.ftype
if lang_id not in self.clients:
logger.debug(f"No LSP client for '{lang_id}', skipping didClose")
return
controller = self.clients[lang_id]
fpath = event.file.fpath
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
controller._lsp_did_close({"uri": uri})
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
lang_id = event.file.ftype
if lang_id not in self.clients:
logger.debug(f"No LSP client for '{lang_id}', skipping didSave")
return
controller = self.clients[lang_id]
fpath = event.file.fpath
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
buffer = event.file.buffer
text = buffer.get_text(*buffer.get_bounds())
self.active_language_id = lang_id
controller._lsp_did_save({"uri": uri, "text": text})
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
self._clear_delayed_cache_refresh_trigger()
lang_id = event.file.ftype
if lang_id not in self.clients:
logger.debug(f"No LSP client for '{lang_id}', skipping didChange")
return
controller = self.clients[lang_id]
fpath = event.file.fpath
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
buffer = event.file.buffer
text = buffer.get_text(*buffer.get_bounds())
self.active_language_id = lang_id
controller._lsp_did_change({
"uri": uri,
"language_id": lang_id,
"version": 1,
"text": text
})
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
self._set_cache_refresh_trigger(
lang_id, fpath, line, column
)
def process_goto_definition(
self, lang_id: str, fpath: str, line: int, column: int
):
if lang_id not in self.clients:
logger.debug(f"No LSP client for '{lang_id}', skipping goto definition")
return
controller = self.clients[lang_id]
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
self.active_language_id = lang_id
controller._lsp_definition({
"uri": uri,
"language_id": lang_id,
"version": 1,
"line": line,
"column": column
})
def process_completion_request(
self, lang_id: str, fpath: str, line: int, column: int
):
if lang_id not in self.clients:
logger.debug(f"No LSP client for '{lang_id}', skipping completion")
return
controller = self.clients[lang_id]
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
self.active_language_id = lang_id
controller._lsp_completion({
"uri": uri,
"language_id": lang_id,
"version": 1,
"line": line,
"column": column
})
def _clear_delayed_cache_refresh_trigger(self):
if self._cache_refresh_timeout_id:
GLib.source_remove(self._cache_refresh_timeout_id)
def _set_cache_refresh_trigger(
self, lang_id: str, fpath: str, line: int, column: int
):
def trigger_cache_refresh(lang_id, fpath, line, column):
self._cache_refresh_timeout_id = None
self.process_completion_request(
lang_id, fpath, line, column
)
return False
self._cache_refresh_timeout_id = GLib.timeout_add(1500, trigger_cache_refresh, lang_id, fpath, line, column)

View File

@@ -1,72 +0,0 @@
# Python imports
import json
# Lib imports
import gi
from gi.repository import GObject
# Application imports
class UIManagerClientsMixin:
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
workspace_dir = self.path_entry.get_text()
self.emit('create-client', lang_id, workspace_dir)
def _close_client(self, widget, sibling):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
self.emit('close-client', lang_id)
def set_source_view_text(self, workspace_dir: str):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
json_str = self.client_configs[lang_id]\
.replace("{workspace.folder}", workspace_dir)\
.replace("{user.home}", self._USER_HOME)
self.source_view.get_buffer().set_text(json_str, -1)
def add_client_listing(self, lang_id: str, lang_config: str):
self.combo_box.append_text(lang_id)
self.client_configs[lang_id] = lang_config
def remove_client_listing(self, lang_id: str):
model = self.combo_box.get_model()
for i, row in enumerate(model):
if row[0] == lang_id:
self.combo_box.remove(i)
break
self.client_configs.pop(lang_id, None)
def toggle_client_buttons(self, show_close: bool):
self.create_client_bttn.set_visible(not show_close)
self.close_client_bttn.set_visible(show_close)
def get_init_opts(self, lang_id: str) -> dict:
if not lang_id or lang_id not in self.client_configs: return {}
try:
buffer = self.source_view.get_buffer()
json_str = buffer.get_text(*buffer.get_bounds(), -1)
lang_config = json.loads(json_str)
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON for {lang_id}: {e}")
return {}
return lang_config.get("initialization-options", {})

View File

@@ -1,77 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib
from gi.repository import Gtk
# Application imports
class UIManagerEventsMixin:
def _setup_signals(self):
self.connect("show", self._handle_show)
self.connect("destroy", self._handle_destroy)
def _subscribe_to_events(self):
...
def _handle_show(self, widget):
GLib.idle_add(self.path_entry.grab_focus)
def _handle_destroy(self, widget):
self.disconnect_by_func(self._handle_show)
self.disconnect_by_func(self._handle_destroy)
self.path_bttn.disconnect_by_func(self._file_set)
self.combo_box.disconnect_by_func(self._on_combo_changed)
self.hide_bttn.disconnect(self.hide_bttn_id)
self.create_client_bttn.disconnect_by_func(self._create_client)
self.close_client_bttn.disconnect_by_func(self._close_client)
def _map_resize(self, widget, parent):
parent_x, \
parent_y = parent.get_position()
parent_width, \
parent_height = parent.get_size()
if parent_width == 0 or parent_height == 0: return
width = int(parent_width * 0.75)
height = int(parent_height * 0.75)
widget.resize(width, height)
x = parent_x + (parent_width - width) // 2
y = parent_y + (parent_height - height) // 2
widget.move(x, y)
def _path_changed(self, widget, buttons_widget):
if not widget.get_text():
self.path_bttn.unselect_all()
self.path_bttn.emit("file-set")
buttons_widget.hide()
return
self.set_source_view_text( self.path_entry.get_text() )
buttons_widget.show()
def _file_set(self, widget):
fname = widget.get_filename()
fname = "" if not fname else fname
self.path_entry.set_text(fname)
lang_id = self.combo_box.get_active_text()
if not lang_id or lang_id not in self.client_configs: return
self.set_source_view_text(
"{workspace.folder}" if not fname else fname
)
def _on_combo_changed(self, combo: Gtk.ComboBoxText):
lang_id = combo.get_active_text()
self.set_source_view_text( self.path_entry.get_text() )

View File

@@ -1,98 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
from ..config import get_lsp_host_addr, get_lsp_host_port
class UIManagerSetupMixin:
def _setup_styling(self):
self.set_modal(True)
self.set_decorated(False)
self.set_vexpand(True)
self.set_hexpand(True)
def _load_widgets(self):
content_area = self.get_content_area()
self.main_box = Gtk.Grid()
self.path_entry = Gtk.SearchEntry()
self.path_bttn = Gtk.FileChooserButton.new(
title = "Workspace Folder",
action = Gtk.FileChooserAction.SELECT_FOLDER
)
self.combo_box = Gtk.ComboBoxText()
self.hide_bttn = Gtk.Button(label = "X")
self.adddress_entry = Gtk.Entry()
adjustment = Gtk.Adjustment(
value = get_lsp_host_port(),
lower = 1,
upper = 65535,
step_increment = 1,
page_increment = 10,
page_size = 0
)
self.adddress_port = Gtk.SpinButton()
self.adddress_port.set_adjustment(adjustment)
self.adddress_port.set_digits(0) # integers only
bttn_box = Gtk.Box()
self.create_client_bttn = Gtk.Button(label = "Create Language Client")
self.close_client_bttn = Gtk.Button(label = "Close Language Client")
self.path_entry.set_can_focus(False)
self.path_entry.set_placeholder_text("Workspace Folder...")
self.path_entry.connect("changed", self._path_changed, bttn_box)
self.path_bttn.set_halign(Gtk.Align.FILL)
self.adddress_entry.set_placeholder_text("Address...")
self.adddress_entry.set_text( get_lsp_host_addr() )
self.path_bttn.connect("file-set", self._file_set)
self.combo_box.connect("changed", self._on_combo_changed)
self.hide_bttn_id = self.hide_bttn.connect("clicked", lambda widget: self.hide())
self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn)
self.close_client_bttn.connect("clicked", self._close_client, self.create_client_bttn)
self.main_box.set_column_spacing(15)
self.main_box.set_row_spacing(15)
bttn_box.pack_start(self.create_client_bttn, False, False, 0)
bttn_box.pack_start(self.close_client_bttn, False, False, 0)
self.main_box.attach(child = self.path_entry, left = 0, top = 0, width = 4, height = 1)
self.main_box.attach(child = self.path_bttn, left = 4, top = 0, width = 1, height = 1)
self.main_box.attach(child = self.combo_box, left = 5, top = 0, width = 1, height = 1)
self.main_box.attach(child = self.hide_bttn, left = 6, top = 0, width = 1, height = 1)
self.main_box.attach(child = self.adddress_entry, left = 0, top = 1, width = 2, height = 1)
self.main_box.attach(child = self.adddress_port, left = 2, top = 1, width = 2, height = 1)
self.main_box.attach(child = bttn_box, left = 4, top = 1, width = 3, height = 1)
content_area.set_vexpand(True)
content_area.set_hexpand(True)
content_area.add(self.main_box)
content_area.show_all()
self.close_client_bttn.hide()
bttn_box.hide()
def set_source_view(self, scrolled_win, source_view):
lang_manager = GtkSource.LanguageManager()
buffer = source_view.get_buffer()
language = lang_manager.get_language("json")
self.source_view = source_view
buffer.set_language(language)
buffer.set_style_scheme(self.source_view.syntax_theme)
self.main_box.attach(scrolled_win, 0, 2, 7, 1)

View File

@@ -12,7 +12,6 @@ from libs.dto.states import SourceViewStates
from plugins.plugin_types import PluginCode from plugins.plugin_types import PluginCode
from .dto.code import events as lsp_events from .dto.code import events as lsp_events
from .commands import Commands
from .lsp_manager import LSPManager from .lsp_manager import LSPManager
@@ -36,11 +35,23 @@ class Plugin(PluginCode):
window = self.request_ui_element("main-window") window = self.request_ui_element("main-window")
lsp_manager.ui_manager.map_parent_resize_event(window) lsp_manager.lsp_manager_ui.map_parent_resize_event(window)
self._manage_signals("register_command") event = Event_Factory.create_event("register_command",
command_name = "LSP Manager",
command = Handler,
binding_mode = "released",
binding = ["<Shift><Control>l", "<Control>g", "<Control>i"]
)
self.emit_to("source_views", event)
self._manage_provider("register_provider") event = Event_Factory.create_event(
"register_provider",
provider_name = "LSP Completer",
provider = lsp_manager.provider,
language_ids = []
)
self.emit_to("completion", event)
event = Event_Factory.create_event( event = Event_Factory.create_event(
"create_source_view", "create_source_view",
@@ -48,8 +59,8 @@ class Plugin(PluginCode):
) )
self.emit_to("source_views", event) self.emit_to("source_views", event)
scrolled_win, source_view = event.response source_view = event.response
lsp_manager.ui_manager.set_source_view(scrolled_win, source_view) lsp_manager.lsp_manager_ui.set_source_view(source_view)
def unload(self): def unload(self):
Event_Factory.unregister_events( lsp_events.__dict__.items() ) Event_Factory.unregister_events( lsp_events.__dict__.items() )
@@ -58,61 +69,56 @@ class Plugin(PluginCode):
window = self.request_ui_element("main-window") window = self.request_ui_element("main-window")
lsp_manager.ui_manager.unmap_parent_resize_event(window) lsp_manager.lsp_manager_ui.unmap_parent_resize_event(window)
self._manage_signals("unregister_command") event = Event_Factory.create_event("unregister_command",
command_name = "LSP Manager",
self._manage_provider("unregister_provider") command = Handler,
lsp_manager.handle_destroy()
def _manage_signals(self, action: str):
_commands = Commands
_commands.lsp_manager = lsp_manager
event = Event_Factory.create_event(action,
command_name = "lsp_manager_toggle",
command = _commands.lsp_manager_toggle,
binding_mode = "released", binding_mode = "released",
binding = "<Shift><Control>l" binding = ["<Shift><Control>l", "<Control>g", "<Control>i"]
) )
self.emit_to("source_views", event) self.emit_to("source_views", event)
event = Event_Factory.create_event(action, event = Event_Factory.create_event(
command_name = "lsp_references", "unregister_provider",
command = _commands.lsp_references, provider_name = "LSP Completer"
binding_mode = "released",
binding = "<Control>i"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "lsp_implementation",
command = _commands.lsp_implementation,
binding_mode = "released",
binding = "<Shift><Control>i"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "lsp_definition",
command = _commands.lsp_definition,
binding_mode = "released",
binding = "<Control>g"
)
self.emit_to("source_views", event)
def _manage_provider(self, action: str):
event = Event_Factory.create_event(
action,
provider_name = "LSP Completer",
provider = lsp_manager.provider,
language_ids = []
) )
self.emit_to("completion", event) self.emit_to("completion", event)
lsp_manager.handle_destroy()
def run(self): def run(self):
... ...
def generate_plugin_element(self): def generate_plugin_element(self):
... ...
class Handler:
@staticmethod
def execute(
view: any,
*args,
**kwargs
):
logger.debug("Command: LSP Manager")
char_str = args[0]
if char_str in ["g", "i"]:
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
if char_str == "g":
lsp_manager.lsp_manager_client.process_goto_definition(
file.ftype, file.fpath, line, column
)
return
if char_str == "i":
return
lsp_manager.lsp_manager_ui.hide() if lsp_manager.lsp_manager_ui.is_visible() else lsp_manager.lsp_manager_ui.show()

View File

@@ -32,7 +32,20 @@ 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()
ch = iter.get_char()
# 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()):
return False
buffer = iter.get_buffer() buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']: if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
@@ -55,7 +68,6 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
# return GtkSource.CompletionActivation.NONE # return GtkSource.CompletionActivation.NONE
return GtkSource.CompletionActivation.USER_REQUESTED return GtkSource.CompletionActivation.USER_REQUESTED
# return GtkSource.CompletionActivation.INTERACTIVE # return GtkSource.CompletionActivation.INTERACTIVE
# return GtkSource.CompletionActivation.USER_REQUESTED | GtkSource.CompletionActivation.INTERACTIVE
def do_populate(self, context): def do_populate(self, context):
results = self.response_cache.filter_with_context(context) results = self.response_cache.filter_with_context(context)

View File

@@ -33,13 +33,9 @@ class ProviderResponseCache(ProviderResponseCacheBase):
if self.lsp_manager_client: if self.lsp_manager_client:
self.lsp_manager_client.process_file_save(event) self.lsp_manager_client.process_file_save(event)
def process_file_text_inserted(self, event): def process_file_change(self, event):
if self.lsp_manager_client: if self.lsp_manager_client:
self.lsp_manager_client.process_file_text_inserted(event) self.lsp_manager_client.process_file_change(event)
def process_file_delete_range(self, event):
if self.lsp_manager_client:
self.lsp_manager_client.process_file_delete_range(event)
def filter(self, word: str) -> list[dict]: def filter(self, word: str) -> list[dict]:
return [] return []

View File

@@ -17,18 +17,10 @@ class DefaultHandler(BaseHandler):
def handle(self, method: str, response, controller): def handle(self, method: str, response, controller):
match method: match method:
case "initialize":
controller.send_initialized_notification()
case "shutdown":
controller.send_exit_notification()
case "textDocument/completion": case "textDocument/completion":
self._handle_completion(response) self._handle_completion(response)
case "textDocument/definition": case "textDocument/definition":
self._handle_definition(response, controller) self._handle_definition(response, controller)
case "textDocument/references":
...
case "textDocument/implementation":
...
case "textDocument/publishDiagnostics": case "textDocument/publishDiagnostics":
self._handle_diagnostics(response) self._handle_diagnostics(response)
@@ -138,5 +130,5 @@ class DefaultHandler(BaseHandler):
view = view, view = view,
provider = self.context._provider provider = self.context._provider
) )
self.emit_to("completion", event) self.emit_to("completion", event)

View File

@@ -1,44 +0,0 @@
# Python imports
from os import path
# Lib imports
import gi
from gi.repository import GObject
from gi.repository import Gtk
# Application imports
from .mixins.ui_manager_setup_mixin import UIManagerSetupMixin
from .mixins.ui_manager_events_mixin import UIManagerEventsMixin
from .mixins.ui_manager_clients_mixin import UIManagerClientsMixin
class UIManager(
Gtk.Dialog,
UIManagerSetupMixin,
UIManagerEventsMixin,
UIManagerClientsMixin
):
__gsignals__ = {
'create-client': (GObject.SignalFlags.RUN_LAST, None, (str, str)),
'close-client': (GObject.SignalFlags.RUN_LAST, None, (str,)),
}
def __init__(self):
super(UIManager, self).__init__()
self._USER_HOME = path.expanduser("~")
self.client_configs: dict[str, str] = {}
self.source_view = None
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def map_parent_resize_event(self, parent):
self.size_allocate_id = parent.connect("size-allocate", lambda w, r: self._map_resize(self, parent))
def unmap_parent_resize_event(self, parent):
parent.disconnect(self.size_allocate_id)

View File

@@ -1,100 +0,0 @@
{
"info": "https://github.com/python-lsp/python-lsp-server",
"command": "lsp-ws-proxy -- pylsp",
"alt-command": "pylsp",
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
"alt-command3": "pylsp --ws --port 4114",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
"initialization-options": {
"pylsp": {
"rope": {
"ropeFolder": "{user.home}/.config/newton/lsps/ropeproject"
},
"plugins": {
"ruff": {
"enabled": true,
"extendSelect": ["I"],
"lineLength": 80
},
"pycodestyle": {
"enabled": false
},
"pyflakes": {
"enabled": false
},
"pylint": {
"enabled": true
},
"mccabe": {
"enabled": false
},
"pylsp_rope": {
"rename": false
},
"rope_rename": {
"enabled": false
},
"rope_autoimport": {
"enabled": true
},
"rope_completion": {
"enabled": false,
"eager": false
},
"jedi_rename": {
"enabled": true
},
"jedi_completion": {
"enabled": true,
"include_class_objects": true,
"include_function_objects": true,
"fuzzy": false
},
"jedi": {
"root_dir": "file://{workspace.folder}",
"extra_paths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
]
}
}
}
}
},
"python - jedi-language-server": {
"hidden": true,
"info": "https://pypi.org/project/jedi-language-server/",
"command": "jedi-language-server",
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
"initialization-options": {
"jediSettings": {
"autoImportModules": [],
"caseInsensitiveCompletion": true,
"debug": false
},
"completion": {
"disableSnippets": false,
"resolveEagerly": false,
"ignorePatterns": []
},
"markupKindPreferred": "markdown",
"workspace": {
"extraPaths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
],
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
"symbols": {
"ignoreFolders": [
".nox",
".tox",
".venv",
"__pycache__",
"venv"
],
"maxSymbols": 20
}
}
}
}

View File

@@ -47,16 +47,9 @@
}, },
"jedi_completion": { "jedi_completion": {
"enabled": true, "enabled": true,
"fuzzy": true,
"include_params": false,
"include_class_objects": true, "include_class_objects": true,
"include_function_objects": true "include_function_objects": true,
}, "fuzzy": false
"jedi_signature_help": {
"enabled": true
},
"jedi_references": {
"enabled": true
}, },
"jedi": { "jedi": {
"root_dir": "file://{workspace.folder}", "root_dir": "file://{workspace.folder}",
@@ -67,4 +60,41 @@
} }
} }
} }
},
"python - jedi-language-server": {
"hidden": true,
"info": "https://pypi.org/project/jedi-language-server/",
"command": "jedi-language-server",
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
"initialization-options": {
"jediSettings": {
"autoImportModules": [],
"caseInsensitiveCompletion": true,
"debug": false
},
"completion": {
"disableSnippets": false,
"resolveEagerly": false,
"ignorePatterns": []
},
"markupKindPreferred": "markdown",
"workspace": {
"extraPaths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
],
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
"symbols": {
"ignoreFolders": [
".nox",
".tox",
".venv",
"__pycache__",
"venv"
],
"maxSymbols": 20
}
}
}
} }

View File

@@ -30,7 +30,6 @@ class Plugin(PluginCode):
event = Event_Factory.create_event("get_active_view") event = Event_Factory.create_event("get_active_view")
self.emit_to("source_views", event) self.emit_to("source_views", event)
if not event.response: return
code_minimap.set_smini_view(event.response) code_minimap.set_smini_view(event.response)
def unload(self): def unload(self):

View File

@@ -1,9 +1,6 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import Gtk
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
@@ -28,19 +25,8 @@ class Plugin(PluginCode):
self.register_controller("tabs", self.tabs_controller) self.register_controller("tabs", self.tabs_controller)
scrolled_win = Gtk.ScrolledWindow() code_container.add( self.tabs_controller.tabs_widget )
viewport = Gtk.Viewport() code_container.reorder_child(self.tabs_controller.tabs_widget, 0)
scrolled_win.set_overlay_scrolling(False)
scrolled_win.set_size_request(-1, 50)
viewport.add( self.tabs_controller.tabs_widget )
scrolled_win.add( viewport )
code_container.add( scrolled_win )
code_container.reorder_child(scrolled_win, 0)
viewport.show()
scrolled_win.show()
event = Event_Factory.create_event("get_files") event = Event_Factory.create_event("get_files")
self.emit_to("files", event) self.emit_to("files", event)
@@ -50,14 +36,7 @@ class Plugin(PluginCode):
def unload(self): def unload(self):
self.unregister_controller("tabs") self.unregister_controller("tabs")
self.tabs_controller.unload_tabs() self.tabs_controller.unload_tabs()
self.tabs_controller.tabs_widget.destroy()
tabs_widget = self.tabs_controller.tabs_widget
viewport = tabs_widget.get_parent()
scrolled_win = viewport.get_parent()
tabs_widget.destroy()
viewport.destroy()
scrolled_win.destroy()
self.tabs_controller.tabs_widget = None self.tabs_controller.tabs_widget = None
self.tabs_controller = None self.tabs_controller = None

View File

@@ -27,7 +27,7 @@ class TabsWidget(Gtk.Notebook):
def _setup_styling(self): def _setup_styling(self):
... self.set_scrollable(True)
def _setup_signals(self): def _setup_signals(self):
self.connect("page-added", self._page_added) self.connect("page-added", self._page_added)
@@ -67,7 +67,6 @@ class TabsWidget(Gtk.Notebook):
) )
self.emit(event) self.emit(event)
self._scroll_to_center(tab)
def _bind_tab_menu(self, tab, page_widget): def _bind_tab_menu(self, tab, page_widget):
def do_context_menu(tab, eve, page_widget): def do_context_menu(tab, eve, page_widget):
@@ -82,21 +81,6 @@ class TabsWidget(Gtk.Notebook):
page_widget page_widget
) )
def _scroll_to_center(self, tab):
scrolled_win = self.get_parent().get_parent()
alloc = tab.get_allocation()
tab_x = alloc.x
tab_width = alloc.width
view_width = scrolled_win.get_allocated_width()
target = tab_x + tab_width / 2 - view_width / 2
adj = scrolled_win.get_hadjustment()
lower = adj.get_lower()
upper = adj.get_upper()
page_size = adj.get_page_size()
target = max(lower, min(target, upper - page_size))
adj.set_value(target)
def create_menu(self, page_widget) -> Gtk.Menu: def create_menu(self, page_widget) -> Gtk.Menu:
context_menu = Gtk.Menu() context_menu = Gtk.Menu()
close_submenu = Gtk.Menu() close_submenu = Gtk.Menu()
@@ -146,7 +130,6 @@ class TabsWidget(Gtk.Notebook):
self.page_num(page_widget) self.page_num(page_widget)
) )
self.handler_unblock(self.switch_page_id) self.handler_unblock(self.switch_page_id)
self._scroll_to_center(tab)
break break

View File

@@ -4,12 +4,12 @@
import argparse import argparse
import faulthandler import faulthandler
import traceback import traceback
from setproctitle import setproctitle
import tracemalloc import tracemalloc
tracemalloc.start() tracemalloc.start()
# Lib imports # Lib imports
from setproctitle import setproctitle
# Application imports # Application imports
from __builtins__ import * from __builtins__ import *

View File

@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from core.widgets.webkit.webkit_ui import WebkitUI from ..widgets.webkit.webkit_ui import WebkitUI
@@ -28,7 +28,6 @@ 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)

View File

@@ -6,6 +6,10 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ...widgets.code.code_base import CodeBase
from ...widgets.separator_widget import Separator
from .editors_container import EditorsContainer from .editors_container import EditorsContainer
@@ -23,9 +27,6 @@ class CodeContainer(Gtk.Box):
def _setup_styling(self): def _setup_styling(self):
self.ctx = self.get_style_context()
self.ctx.add_class("code-container")
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
def _setup_signals(self): def _setup_signals(self):
@@ -36,4 +37,18 @@ class CodeContainer(Gtk.Box):
def _load_widgets(self): def _load_widgets(self):
widget_registery.expose_object("code-container", self) widget_registery.expose_object("code-container", self)
self.add( EditorsContainer() )
code_base = CodeBase()
self.add( self._create_editor_widget(code_base) )
def _create_editor_widget(self, code_base: CodeBase):
editors_container = Gtk.Box()
widget_registery.expose_object("editors-container", editors_container)
editors_container.add( Separator("separator_left") )
editors_container.add( EditorsContainer(code_base) )
editors_container.add( Separator("separator_right") )
return editors_container

View File

@@ -4,17 +4,18 @@
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
#from gi.repository import GLib
# Application imports # Application imports
from core.widgets.code.code_base import CodeBase
from core.widgets.separator_widget import Separator
class EditorsContainer(Gtk.Box): class EditorsContainer(Gtk.Paned):
def __init__(self): def __init__(self, code_base: any):
super(EditorsContainer, self).__init__() super(EditorsContainer, self).__init__()
self.code_base = code_base
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
@@ -23,11 +24,11 @@ class EditorsContainer(Gtk.Box):
def _setup_styling(self): def _setup_styling(self):
self.ctx = self.get_style_context() self.ctx = self.get_style_context()
self.ctx.add_class("editors-container") self.ctx.add_class("paned-editors-container")
self.set_hexpand(True) self.set_hexpand(True)
self.set_vexpand(True) self.set_vexpand(True)
self.set_size_request(320, -1) self.set_wide_handle(True)
def _setup_signals(self): def _setup_signals(self):
self.connect("map", self._init_map) self.connect("map", self._init_map)
@@ -36,16 +37,26 @@ class EditorsContainer(Gtk.Box):
... ...
def _load_widgets(self): def _load_widgets(self):
box = Gtk.Box() self.scrolled_win1, \
widget_registery.expose_object("editors-container", self) self.scrolled_win2 = self._create_views()
self.code_base = CodeBase()
scrolled_win, \
source_view = self.code_base.create_source_view()
box.add( scrolled_win ) self.pack1( self.scrolled_win1, True, True )
self.add( Separator("separator_left") ) self.pack2( self.scrolled_win2, True, True )
self.add( box )
self.add( Separator("separator_right") ) def _create_views(self):
scrolled_win1 = Gtk.ScrolledWindow()
scrolled_win2 = Gtk.ScrolledWindow()
source_view1 = self.code_base.create_source_view()
source_view2 = self.code_base.create_source_view()
source_view1.sibling_right = source_view2
source_view2.sibling_left = source_view1
scrolled_win1.add( source_view1 )
scrolled_win2.add( source_view2 )
return scrolled_win1, scrolled_win2
def _init_map(self, view): def _init_map(self, view):
self.disconnect_by_func( self._init_map ) self.disconnect_by_func( self._init_map )

View File

@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from core.widgets.separator_widget import Separator from ..widgets.separator_widget import Separator

View File

@@ -6,9 +6,9 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from core.widgets.separator_widget import Separator from ..widgets.separator_widget import Separator
from core.widgets.controls.open_files_button import OpenFilesButton from ..widgets.controls.open_files_button import OpenFilesButton
from core.widgets.controls.transparency_scale import TransparencyScale from ..widgets.controls.transparency_scale import TransparencyScale

View File

@@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from core.widgets.separator_widget import Separator from ..widgets.separator_widget import Separator
from .code.code_container import CodeContainer from .code.code_container import CodeContainer

View File

@@ -6,8 +6,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from core.widgets.separator_widget import Separator from ..widgets.separator_widget import Separator
from core.widgets.vte_widget import VteWidget from ..widgets.vte_widget import VteWidget

View File

@@ -81,7 +81,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
def _load_files(self): def _load_files(self):
for file in settings_manager.get_starting_files(): for file in settings_manager.get_starting_files():
event_system.emit("post-file-to-ipc", f"FILE|{file}") event_system.emit("post-file-to-ipc", file)
def _tggl_top_main_menubar(self): def _tggl_top_main_menubar(self):
logger.debug("_tggl_top_main_menubar > stub...") logger.debug("_tggl_top_main_menubar > stub...")

View File

@@ -18,12 +18,16 @@ class BaseControllerMixin:
files = [] files = []
for arg in unknownargs + [args.new_tab,]: for arg in unknownargs + [args.new_tab,]:
if os.path.isfile(arg): if os.path.isdir( arg.replace("file://", "") ):
files.append(f"{arg}") files.append( f"DIR|{arg.replace('file://', '')}" )
continue
if os.path.isdir(arg): # NOTE: If passing line number with file split against :
message = f"DIR|{arg}" if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
ipc_server.send_ipc_message(message) files.append( f"FILE|{arg.replace('file://', '')}" )
continue
logger.info(f"Not a File: {arg}")
if not files: return if not files: return

View File

@@ -34,6 +34,7 @@ class CodeBase:
completion_controller = CompletionController() completion_controller = CompletionController()
source_views_controller = SourceViewsController() source_views_controller = SourceViewsController()
# self.controller_manager.register_controller("base", self)
self.controller_manager.register_controller("files", files_controller) self.controller_manager.register_controller("files", files_controller)
self.controller_manager.register_controller("commands", commands_controller) self.controller_manager.register_controller("commands", commands_controller)
self.controller_manager.register_controller("completion", completion_controller) self.controller_manager.register_controller("completion", completion_controller)
@@ -42,13 +43,12 @@ class CodeBase:
self.controller_manager.register_controller("widgets", widget_registery) self.controller_manager.register_controller("widgets", widget_registery)
def create_source_view(self): def create_source_view(self):
scrolled_win, \ source_view = self.controller_manager["source_views"].create_source_view()
source_view = self.controller_manager["source_views"].create_source_view()
self.controller_manager["completion"].register_completer( self.controller_manager["completion"].register_completer(
source_view.get_completion() source_view.get_completion()
) )
return scrolled_win, source_view return source_view
def first_map_load(self): def first_map_load(self):
self.controller_manager["source_views"].first_map_load() self.controller_manager["source_views"].first_map_load()

View File

@@ -2,4 +2,4 @@
Code Command System Package Code Command System Package
""" """
from .source_view_command_system import SourceViewCommandSystem from .command_system import CommandSystem

View File

@@ -8,10 +8,7 @@ from gi.repository import GtkSource
def set_language_and_style(view, file): def set_language_and_style(view, file):
language = None language = view.language_manager.guess_language(file.fname, None)
if not file.fname == "buffer":
language = view.language_manager.guess_language(file.fname, None)
file.buffer.set_language(language) file.buffer.set_language(language)
file.buffer.set_style_scheme(view.syntax_theme) file.buffer.set_style_scheme(view.syntax_theme)

View File

@@ -0,0 +1,120 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from ..mixins.command_system_mixin import CommandSystemMixin
from ..source_view import SourceView
from . import commands
class CommandSystem(CommandSystemMixin):
def __init__(self):
super(CommandSystem, self).__init__()
self.data: list = ()
def set_data(self, *args, **kwargs):
self.data = (args, kwargs)
def exec(self, command: str) -> any:
if not hasattr(commands, command): return
method = getattr(commands, command)
args, kwargs = self.data
return method.execute(*args, **kwargs)
def exec_with_args(self, command: str, *args, **kwargs) -> any:
if not hasattr(commands, command): return
method = getattr(commands, command)
return method.execute(*args, **kwargs)
def add_command(self, command_name: str, command: callable):
setattr(commands, command_name, command)
def remove_command(self, command_name: str, command: callable):
if hasattr(commands, command_name):
delattr(commands, command_name)
def emit(self, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit' from command controller... """
...
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit_to' from command controller... """
...
# def filter_out_loaded_files(self, uris: list[str]):
# event = Event_Factory.create_event(
# "filter_out_loaded_files",
# uris = uris
# )
#
# self.emit_to("files", event)
#
# return event.response
#
# def set_info_labels(self, data: tuple[str]):
# event = Event_Factory.create_event(
# "set_info_labels",
# info = data
# )
#
# self.emit_to("plugins", event)
#
# def get_file(self, view: SourceView):
# event = Event_Factory.create_event(
# "get_file",
# view = view,
# buffer = view.get_buffer()
# )
#
# self.emit_to("files", event)
#
# return event.response
#
# def get_swap_file(self, view: SourceView):
# event = Event_Factory.create_event(
# "get_swap_file",
# view = view,
# buffer = view.get_buffer()
# )
#
# self.emit_to("files", event)
#
# return event.response
#
# def new_file(self, view: SourceView):
# event = Event_Factory.create_event("add_new_file", view = view)
#
# self.emit_to("files", event)
#
# return event.response
#
# def remove_file(self, view: SourceView):
# event = Event_Factory.create_event(
# "remove_file",
# view = view,
# buffer = view.get_buffer()
# )
#
# self.emit_to("files", event)
#
# return event.response
#
# def request_completion(self, view: SourceView):
# event = Event_Factory.create_event(
# "request_completion",
# view = view,
# buffer = view.get_buffer()
# )
#
# self.emit_to("completion", event)

View File

@@ -11,45 +11,45 @@ from gi.repository import GtkSource
def execute(view: GtkSource.View, *args, **kwargs): def execute(
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Duplicate Line") logger.debug("Command: Duplicate Line")
buffer = view.get_buffer() buffer = view.get_buffer()
if buffer.get_has_selection(): if not buffer.get_has_selection():
start_itr, \ had_selection = False
end_itr = buffer.get_selection_bounds() itr = buffer.get_iter_at_mark( buffer.get_insert() )
start_line = start_itr.get_line() start_itr = itr.copy()
end_line = end_itr.get_line() end_itr = itr.copy()
scol = start_itr.get_line_offset() start_line = itr.get_line() + 1
ecol = end_itr.get_line_offset() start_char = itr.get_line_offset()
else: else:
itr = buffer.get_iter_at_mark(buffer.get_insert()) had_selection = True
start_line = end_line = itr.get_line() start_itr, end_itr = buffer.get_selection_bounds()
col = itr.get_line_offset() sline = start_itr.get_line()
eline = end_itr.get_line()
start_line = eline + 1
start_char = start_itr.get_line_offset()
end_char = end_itr.get_line_offset()
range_line_size = eline - sline
start_itr = buffer.get_iter_at_line(start_line) start_itr.backward_visible_line()
end_itr = buffer.get_iter_at_line(end_line) start_itr.forward_line()
end_itr.forward_line()
end_itr.backward_char()
if not end_itr.ends_line(): line_str = buffer.get_slice(start_itr, end_itr, True)
end_itr.forward_to_line_end() end_itr.forward_char()
buffer.insert(end_itr, f"{line_str}\n", -1)
if not end_itr.is_end(): if not had_selection:
end_itr.forward_char() new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
buffer.place_cursor(new_itr)
text = buffer.get_text(start_itr, end_itr, True)
insert_itr = buffer.get_iter_at_line(end_line)
insert_itr.forward_to_line_end()
if not insert_itr.is_end():
insert_itr.forward_char()
buffer.insert(insert_itr, text)
if buffer.get_has_selection():
new_start = buffer.get_iter_at_line_offset(end_line + 1, scol)
new_end = buffer.get_iter_at_line_offset(end_line + 1 + (end_line - start_line), ecol)
buffer.select_range(new_start, new_end)
else: else:
new_start = buffer.get_iter_at_line_offset(end_line + 1, col) new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
buffer.place_cursor(new_start) new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char)
buffer.select_range(new_itr, new_end_itr)

View File

@@ -7,7 +7,6 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
@@ -19,4 +18,5 @@ def execute(
): ):
logger.debug("Command: Focus Left Sibling") logger.debug("Command: Focus Left Sibling")
if not view.sibling_left: return if not view.sibling_left: return
view.sibling_left.get_parent().show()
view.sibling_left.grab_focus() view.sibling_left.grab_focus()

View File

@@ -18,4 +18,5 @@ def execute(
): ):
logger.debug("Command: Focus Right Sibling") logger.debug("Command: Focus Right Sibling")
if not view.sibling_right: return if not view.sibling_right: return
view.sibling_right.get_parent().show()
view.sibling_right.grab_focus() view.sibling_right.grab_focus()

View File

@@ -20,14 +20,9 @@ def execute(
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()
ctx.add_class("source-view-focused") ctx.add_class("source-view-focused")
lview = view.sibling_left if view.sibling_right:
while lview is not None: ctx = view.sibling_right.get_parent().get_style_context()
ctx = lview.get_parent().get_style_context() elif view.sibling_left:
ctx.remove_class("source-view-focused") ctx = view.sibling_left.get_parent().get_style_context()
lview = lview.sibling_left
rview = view.sibling_right ctx.remove_class("source-view-focused")
while rview is not None:
ctx = rview.get_parent().get_style_context()
ctx.remove_class("source-view-focused")
rview = rview.sibling_right

View File

@@ -36,11 +36,8 @@ class ProviderResponseCacheBase:
def process_file_save(self, event: Code_Event_Types.SavedFileEvent): def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...") raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...")
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent): def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_text_inserted' not implemented...") raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_change' not implemented...")
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_delete_range' not implemented...")
def filter(self, word: str) -> list[dict]: def filter(self, word: str) -> list[dict]:
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...") raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...")

View File

@@ -7,7 +7,7 @@ from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Code_Event_Types from libs.event_factory import Code_Event_Types
from ..command_system import SourceViewCommandSystem from ..command_system import CommandSystem
@@ -17,19 +17,14 @@ class CommandsController(ControllerBase, list):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.CreateCommandSystemEvent): if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent):
event.response = self.create_command_system() event.response = self.get_new_command_system()
elif isinstance(event, Code_Event_Types.RemovedSourceViewEvent):
self.remove_command_system(event)
def create_command_system(self): def get_new_command_system(self):
command_system = SourceViewCommandSystem() command_system = CommandSystem()
command_system.emit = self.emit command_system.emit = self.emit
command_system.emit_to = self.emit_to command_system.emit_to = self.emit_to
self.append(command_system) self.append(command_system)
return command_system return command_system
def remove_command_system(self, event: Code_Event_Types.RemovedSourceViewEvent):
self.remove(event.view.command)

View File

@@ -4,6 +4,7 @@
import gi import gi
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
@@ -40,10 +41,8 @@ class CompletionController(ControllerBase):
self.provider_process_file_close(event) self.provider_process_file_close(event)
elif isinstance(event, Code_Event_Types.SavedFileEvent): elif isinstance(event, Code_Event_Types.SavedFileEvent):
self.provider_process_file_save(event) self.provider_process_file_save(event)
elif isinstance(event, Code_Event_Types.TextInsertedEvent): elif isinstance(event, Code_Event_Types.TextChangedEvent):
self.provider_process_file_text_inserted(event) self.provider_process_file_change(event)
elif isinstance(event, Code_Event_Types.DeleteRangeEvent):
self.provider_process_file_delete_range(event)
elif isinstance(event, Code_Event_Types.RequestCompletionEvent): elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
self.request_unbound_completion(event) self.request_unbound_completion(event)
@@ -89,13 +88,9 @@ class CompletionController(ControllerBase):
for provider in self._providers.values(): for provider in self._providers.values():
provider.response_cache.process_file_save(event) provider.response_cache.process_file_save(event)
def provider_process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent): def provider_process_file_change(self, event: Code_Event_Types.TextChangedEvent):
for provider in self._providers.values(): for provider in self._providers.values():
provider.response_cache.process_file_text_inserted(event) provider.response_cache.process_file_change(event)
def provider_process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
for provider in self._providers.values():
provider.response_cache.process_file_delete_range(event)
def request_unbound_completion(self, event: Code_Event_Types.RequestCompletionEvent): def request_unbound_completion(self, event: Code_Event_Types.RequestCompletionEvent):
completer = event.view.get_completion() completer = event.view.get_completion()

View File

@@ -46,57 +46,47 @@ class MarkerManager(MarkSupportMixin):
start_itr = buffer.get_iter_at_mark(start_mark) start_itr = buffer.get_iter_at_mark(start_mark)
end_itr = buffer.get_iter_at_mark(end_mark) end_itr = buffer.get_iter_at_mark(end_mark)
self._proc_move( if is_selection:
buffer, is_forward, is_selection, mode, mark_hash,
start_mark, end_mark, has_selection, start_itr, end_itr
)
def _proc_move(
self, buffer, is_forward: bool, is_selection: bool, mode: str,
mark_hash, start_mark, end_mark, has_selection, start_itr, end_itr
):
if is_selection:
if mark_hash:
self.buffer_markers[mark_hash]["is_selection"] = True self.buffer_markers[mark_hash]["is_selection"] = True
self._move_iter(buffer, end_itr, mode, is_forward)
buffer.move_mark(end_mark, end_itr)
self._apply_selection(buffer, start_itr, end_itr)
continue
if has_selection:
caret_itr = buffer.get_iter_at_mark(end_mark)
start_itr = buffer.get_iter_at_mark(start_mark)
is_left_edge = caret_itr.compare(start_itr) <= 0
is_right_edge = not is_left_edge
can_move = (
(is_forward and is_right_edge) or
(not is_forward and is_left_edge)
)
self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward)
if mode == "word":
if not can_move: continue
itr = caret_itr
self._move_iter(buffer, itr, mode, is_forward)
buffer.move_mark(start_mark, itr)
buffer.move_mark(end_mark, itr)
continue
# No selection - move both anchor and caret together
self._move_iter(buffer, end_itr, mode, is_forward) self._move_iter(buffer, end_itr, mode, is_forward)
buffer.move_mark(start_mark, end_itr)
buffer.move_mark(end_mark, end_itr) buffer.move_mark(end_mark, end_itr)
self._apply_selection(buffer, start_itr, end_itr)
return
if has_selection:
caret_itr = buffer.get_iter_at_mark(end_mark)
start_itr = buffer.get_iter_at_mark(start_mark)
is_left_edge = caret_itr.compare(start_itr) <= 0
is_right_edge = not is_left_edge
can_move = (
(is_forward and is_right_edge) or
(not is_forward and is_left_edge)
)
self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward)
if mode == "word":
if not can_move: return
itr = caret_itr
self._move_iter(buffer, itr, mode, is_forward)
buffer.move_mark(start_mark, itr)
buffer.move_mark(end_mark, itr)
return
# No selection - move both anchor and caret together
self._move_iter(buffer, end_itr, mode, is_forward)
buffer.move_mark(start_mark, end_itr)
buffer.move_mark(end_mark, end_itr)
def collapse_selection(self, def collapse_selection(self,
buffer, mark_hash, start_mark, end_mark, is_forward: bool buffer, mark_hash, start_mark, end_mark, is_forward: bool
): ):
if mark_hash: self.buffer_markers[mark_hash]["is_selection"] = False
self.buffer_markers[mark_hash]["is_selection"] = False
start_itr = buffer.get_iter_at_mark(start_mark) start_itr = buffer.get_iter_at_mark(start_mark)
end_itr = buffer.get_iter_at_mark(end_mark) end_itr = buffer.get_iter_at_mark(end_mark)
@@ -115,28 +105,19 @@ class MarkerManager(MarkSupportMixin):
buffer.move_mark(start_mark, collapse_itr) buffer.move_mark(start_mark, collapse_itr)
buffer.move_mark(end_mark, collapse_itr) buffer.move_mark(end_mark, collapse_itr)
def move_along_word(self, itr: Gtk.TextIter, count: int): def move_word_snake_case(self, itr: Gtk.TextIter, count: int):
def not_is_word(ch: str): def is_word(ch):
return not is_word(ch)
def is_word(ch: str):
return ch and (ch.isalnum() or ch == "_") return ch and (ch.isalnum() or ch == "_")
def is_special(ch: str): def step(fwd):
return ch in "-"
def is_punct(ch: str):
return ch in ".;?!"
def step(fwd: bool):
return itr.forward_cursor_position() if fwd else itr.backward_cursor_position() return itr.forward_cursor_position() if fwd else itr.backward_cursor_position()
def peek(fwd: bool): def peek(fwd):
if fwd: return itr.get_char() if fwd: return itr.get_char()
tmp = itr.copy() tmp = itr.copy()
return tmp.backward_cursor_position() and tmp.get_char() return tmp.backward_cursor_position() and tmp.get_char()
def walk(fwd, cond: callable): def walk(fwd, cond):
while True: while True:
ch = peek(fwd) ch = peek(fwd)
if not cond(ch): break if not cond(ch): break
@@ -145,22 +126,23 @@ class MarkerManager(MarkSupportMixin):
return True return True
fwd = count > 0 fwd = count > 0
for _ in range( abs(count) ):
ch = peek(fwd)
if is_special(ch) or is_punct(ch): for _ in range(abs(count)):
step(fwd) ch = itr.get_char() if fwd else peek(False)
elif is_word(ch):
if is_word(ch):
# inside word
if not walk(fwd, is_word): return if not walk(fwd, is_word): return
else: else:
if not walk(fwd, not_is_word): return # in separators -> skip them, then the word
if not walk(fwd, lambda c: not is_word(c)): return
if not walk(fwd, is_word): return if not walk(fwd, is_word): return
def _move_iter(self, buffer, itr_, mode: str, is_forward: bool): def _move_iter(self, buffer, itr_, mode: str, is_forward: bool):
if mode == "char": if mode == "char":
itr_.forward_char() if is_forward else itr_.backward_char() itr_.forward_char() if is_forward else itr_.backward_char()
elif mode == "word": elif mode == "word":
self.move_along_word(itr_, 1 if is_forward else -1) self.move_word_snake_case(itr_, 1 if is_forward else -1)
elif mode == "line": elif mode == "line":
line = itr_.get_line() line = itr_.get_line()
offset = itr_.get_line_offset() offset = itr_.get_line_offset()

View File

@@ -1,9 +1,6 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -32,8 +29,6 @@ class SourceViewsController(ControllerBase, list):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.CreateSourceViewEvent): if isinstance(event, Code_Event_Types.CreateSourceViewEvent):
event.response = self.create_source_view(event.state) event.response = self.create_source_view(event.state)
elif isinstance(event, Code_Event_Types.RemoveSourceViewEvent):
self._remove_source_view(event)
elif isinstance(event, Code_Event_Types.RemovedFileEvent): elif isinstance(event, Code_Event_Types.RemovedFileEvent):
self._remove_file(event) self._remove_file(event)
elif isinstance(event, Code_Event_Types.RegisterCommandEvent): elif isinstance(event, Code_Event_Types.RegisterCommandEvent):
@@ -91,7 +86,7 @@ class SourceViewsController(ControllerBase, list):
) )
def _get_command_system(self): def _get_command_system(self):
event = Event_Factory.create_event("create_command_system") event = Event_Factory.create_event("get_new_command_system")
self.message_to("commands", event) self.message_to("commands", event)
command = event.response command = event.response
@@ -108,34 +103,15 @@ class SourceViewsController(ControllerBase, list):
source_view.set_buffer(event.next_file.buffer) source_view.set_buffer(event.next_file.buffer)
def _remove_source_view(self, event: Code_Event_Types.RemovedFileEvent):
event = Event_Factory.create_event("removed_source_view", view = event.view)
self.message(event)
self.remove(event.view)
self.signal_mapper.disconnect_signals(event.view)
def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT): def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT):
scrolled_win: Gtk.ScrolledWindow = Gtk.ScrolledWindow() source_view: SourceView = SourceView(state)
source_view: SourceView = SourceView(state) source_view.command = self._get_command_system()
source_view.command = self._get_command_system()
scrolled_win.set_hexpand(True)
scrolled_win.set_vexpand(True)
source_view.command.set_data(source_view) source_view.command.set_data(source_view)
self.signal_mapper.connect_signals(source_view) self.signal_mapper.connect_signals(source_view)
self.append(source_view) self.append(source_view)
scrolled_win.add(source_view) return source_view
scrolled_win.show_all()
event = Event_Factory.create_event(
"created_source_view", view = source_view
)
self.emit(event)
return scrolled_win, source_view
def first_map_load(self): def first_map_load(self):
for source_view in self: for source_view in self:

View File

@@ -59,56 +59,30 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
buffer.insert(start_itr, text, -1) buffer.insert(start_itr, text, -1)
self.marker_manager.apply_to_marks(buffer, replace_word) self.marker_manager.apply_to_marks(buffer, replace_word)
return True return True
def move_cursor( def move_cursor(self, source_view, step, count, is_selection, emit):
self, source_view, step, count, is_selection, is_forward = count > 0
emit = None, ignore_leader: bool = False buffer = source_view.get_buffer()
):
is_forward = count > 0
buffer = source_view.get_buffer()
start_mark = buffer.get_insert() if step in [
end_mark = buffer.get_selection_bound() Gtk.MovementStep.LOGICAL_POSITIONS,
start_itr = buffer.get_iter_at_mark(start_mark) Gtk.MovementStep.VISUAL_POSITIONS
end_itr = buffer.get_iter_at_mark(end_mark) ]:
has_selection = not start_itr.equal(end_itr) self.marker_manager.move_by_char(buffer, is_forward, is_selection)
elif step == Gtk.MovementStep.WORDS:
self.marker_manager.move_by_word(buffer, is_forward, is_selection)
elif step == Gtk.MovementStep.DISPLAY_LINES:
self.marker_manager.move_by_line(buffer, is_forward, is_selection)
step_map = { self._signal_cursor_moved(source_view, emit)
Gtk.MovementStep.LOGICAL_POSITIONS: ("char", self.marker_manager.move_by_char),
Gtk.MovementStep.VISUAL_POSITIONS: ("char", self.marker_manager.move_by_char),
Gtk.MovementStep.WORDS: ("word", self.marker_manager.move_by_word),
Gtk.MovementStep.DISPLAY_LINES: ("line", self.marker_manager.move_by_line),
}
kind, move_fn = step_map[step] return False
move_fn(buffer, is_forward, is_selection)
if ignore_leader: return True
self.marker_manager._proc_move(
buffer,
is_forward,
is_selection,
kind,
None,
start_mark,
end_mark,
has_selection,
start_itr,
end_itr,
)
# self._signal_cursor_moved(source_view, emit)
return True
def key_press_event(self, source_view, event, key_mapper): def key_press_event(self, source_view, event, key_mapper):
char = key_mapper.get_raw_keyname(event).upper() char = key_mapper.get_raw_keyname(event).upper()
self.is_control = key_mapper.is_control(event) self.is_control = key_mapper.is_control(event)
self.is_shift = key_mapper.is_shift(event) self.is_shift = key_mapper.is_shift(event)
self.is_super = key_mapper.is_super(event)
if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]: if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]:
self.marker_manager.process_cursor_action( self.marker_manager.process_cursor_action(
@@ -117,9 +91,6 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
) )
return False return False
if self._do_cursor_moved(source_view, char):
return True
return super().key_press_event(source_view, event, key_mapper) return super().key_press_event(source_view, event, key_mapper)
def button_press_event(self, source_view, event): def button_press_event(self, source_view, event):
@@ -128,56 +99,6 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
def button_release_event(self, source_view, event): def button_release_event(self, source_view, event):
self.marker_manager.button_release_event(source_view, event) self.marker_manager.button_release_event(source_view, event)
def _do_cursor_moved(self, source_view, char: str):
key = char.upper()
if key not in {"LEFT", "RIGHT", "UP", "DOWN"}: return False
direction = {
"LEFT": -1,
"RIGHT": 1,
"UP": -1,
"DOWN": 1,
}[key]
is_horizontal = key in {"LEFT", "RIGHT"}
step = \
Gtk.MovementStep.VISUAL_POSITIONS if is_horizontal else Gtk.MovementStep.DISPLAY_LINES
count = direction
is_selection = self.is_shift
if is_horizontal:
if self.is_control:
step = Gtk.MovementStep.WORDS
if self.is_control and self.is_shift:
is_selection = True
if self.is_super:
return self.move_cursor(
source_view,
step,
count,
is_selection = False,
emit = None,
ignore_leader = True,
)
else:
if self.is_super:
return self.move_cursor(
source_view,
step,
count,
is_selection,
emit = None,
ignore_leader = True,
)
return self.move_cursor(
source_view,
step = step,
count = count,
is_selection = is_selection,
)
def _signal_cursor_moved(self, source_view, emit): def _signal_cursor_moved(self, source_view, emit):
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() ) itr = buffer.get_iter_at_mark( buffer.get_insert() )

View File

@@ -145,7 +145,7 @@ class KeyMapper:
is_shift, \ is_shift, \
is_alt = self.get_modkeys_states(eve) is_alt = self.get_modkeys_states(eve)
self.state = NoKeyState self.state = NoKeyState
if is_control: if is_control:
self.state = self.state | CtrlKeyState self.state = self.state | CtrlKeyState
if is_shift: if is_shift:
@@ -161,10 +161,6 @@ class KeyMapper:
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return modifiers & Gdk.ModifierType.SHIFT_MASK return modifiers & Gdk.ModifierType.SHIFT_MASK
def is_super(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return modifiers & Gdk.ModifierType.SUPER_MASK
def get_raw_keyname(self, eve) -> str: def get_raw_keyname(self, eve) -> str:
return Gdk.keyval_name(eve.keyval) return Gdk.keyval_name(eve.keyval)

View File

@@ -4,19 +4,12 @@
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
from libs.command_system import CommandSystem
from ..source_view import SourceView from ..source_view import SourceView
from . import commands
class SourceViewCommandSystem(CommandSystem):
def __init__(self):
super(SourceViewCommandSystem, self).__init__(commands)
class CommandSystemMixin:
def toggle_plugins_ui(self): def toggle_plugins_ui(self):
event = Event_Factory.create_event( "toggle_plugins_ui" ) event = Event_Factory.create_event( "toggle_plugins_ui" )
@@ -88,11 +81,3 @@ class SourceViewCommandSystem(CommandSystem):
) )
self.emit_to("completion", event) self.emit_to("completion", event)
def emit(self, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit' from command controller... """
...
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit_to' from command controller... """
...

View File

@@ -3,10 +3,7 @@
# 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
@@ -15,41 +12,25 @@ from gi.repository import Gtk
class SourceViewDnDMixin: class SourceViewDnDMixin:
def _set_up_dnd(self): def _set_up_dnd(self):
URI_TARGET_TYPE = 10 PLAIN_TEXT_TARGET_TYPE = 70
PLAIN_TEXT_TARGET_TYPE = 50 URI_TARGET_TYPE = 80
text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE)
uri_target = Gtk.TargetEntry.new( uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
'text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE targets = [ text_target, uri_target ]
)
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( def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
self, widget, drag_context, x, y, data, info, time if info == 70: return
):
target = data.get_target().name()
if (info == 10) or (target == "text/uri-list"): if info == 80:
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

View File

@@ -31,7 +31,6 @@ class SourceBuffer(GtkSource.Buffer):
_insert_text, _insert_text,
_after_insert_text, _after_insert_text,
_modified_changed, _modified_changed,
_delete_range,
): ):
self._handler_ids = [ self._handler_ids = [
@@ -40,8 +39,7 @@ class SourceBuffer(GtkSource.Buffer):
self.connect("mark-set", _mark_set), self.connect("mark-set", _mark_set),
self.connect("insert-text", _insert_text), self.connect("insert-text", _insert_text),
self.connect_after("insert-text", _after_insert_text), self.connect_after("insert-text", _after_insert_text),
self.connect("modified-changed", _modified_changed), self.connect("modified-changed", _modified_changed)
self.connect("delete-range", _delete_range)
] ]
def block_changed_signal(self): def block_changed_signal(self):
@@ -56,9 +54,6 @@ class SourceBuffer(GtkSource.Buffer):
def block_modified_changed_signal(self): def block_modified_changed_signal(self):
self.handler_block(self._handler_ids[5]) self.handler_block(self._handler_ids[5])
def block_delete_range(self):
self.handler_block(self._handler_ids[6])
def unblock_changed_signal(self): def unblock_changed_signal(self):
self.handler_unblock(self._handler_ids[0]) self.handler_unblock(self._handler_ids[0])
@@ -71,9 +66,6 @@ class SourceBuffer(GtkSource.Buffer):
def unblock_modified_changed_signal(self): def unblock_modified_changed_signal(self):
self.handler_unblock(self._handler_ids[5]) self.handler_unblock(self._handler_ids[5])
def unblock_delete_range(self):
self.handler_block(self._handler_ids[6])
def clear_signals(self): def clear_signals(self):
for handle_id in self._handler_ids: for handle_id in self._handler_ids:
self.disconnect(handle_id) self.disconnect(handle_id)

View File

@@ -40,8 +40,7 @@ class SourceFile(GtkSource.File):
self._mark_set, self._mark_set,
self._insert_text, self._insert_text,
self._after_insert_text, self._after_insert_text,
self._modified_changed, self._modified_changed
self._delete_range
) )
def _changed(self, buffer: SourceBuffer): def _changed(self, buffer: SourceBuffer):
@@ -61,20 +60,7 @@ class SourceFile(GtkSource.File):
location: Gtk.TextIter, location: Gtk.TextIter,
text: str, length: int text: str, length: int
): ):
event = Event_Factory.create_event( ...
"text_insert",
file = self,
buffer = self.buffer,
location = location,
text = text,
length = length
)
# Note: 'idle_add' needed b/c markers don't get thir positions
# updated relative to the initial insert.
# If not used, seg faults galor during multi insert.
# GLib.idle_add(self.emit, event)
self.emit(event)
def _after_insert_text( def _after_insert_text(
self, self,
@@ -113,19 +99,7 @@ class SourceFile(GtkSource.File):
def _modified_changed(self, buffer: SourceBuffer): def _modified_changed(self, buffer: SourceBuffer):
event = Event_Factory.create_event( event = Event_Factory.create_event(
"modified_changed", "modified_changed",
file = self, file = self, buffer = buffer
buffer = buffer
)
self.emit(event)
def _delete_range(self, buffer: SourceBuffer, start: Gtk.TextIter, end: Gtk.TextIter):
event = Event_Factory.create_event(
"delete_range",
file = self,
buffer = buffer,
start = start,
end = end,
) )
self.emit(event) self.emit(event)
@@ -181,10 +155,9 @@ 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)
current = (stat.st_mtime_ns, stat.st_size)
stat = os.stat(self.fpath)
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
@@ -196,12 +169,7 @@ 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...'")
# Note: text = contents.decode("UTF-8")
# "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) \
@@ -209,7 +177,6 @@ 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)

View File

@@ -19,10 +19,10 @@ class SourceView(GtkSource.View, SourceViewDnDMixin):
def __init__(self, state: SourceViewStates = SourceViewStates.INSERT): def __init__(self, state: SourceViewStates = SourceViewStates.INSERT):
super(SourceView, self).__init__() super(SourceView, self).__init__()
self.state = state self.state = state
self.sibling_right = None self.sibling_right = None
self.sibling_left = None self.sibling_left = None
self._setup_styles() self._setup_styles()
self._setup_signals() self._setup_signals()

View File

@@ -28,8 +28,8 @@ class VteWidget(Vte.Terminal):
def __init__(self): def __init__(self):
super(VteWidget, self).__init__() super(VteWidget, self).__init__()
self.cd_cmd_prefix: tuple = ("cd".encode(), "cd ".encode()) self.cd_cmd_prefix = ("cd".encode(), "cd ".encode())
self.dont_process: bool = False self.dont_process = False
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
@@ -48,17 +48,9 @@ class VteWidget(Vte.Terminal):
self.set_hexpand(True) self.set_hexpand(True)
self.set_enable_sixel(True) self.set_enable_sixel(True)
self.set_cursor_shape( Vte.CursorShape.IBEAM ) self.set_cursor_shape( Vte.CursorShape.IBEAM )
self.set_audible_bell(False)
self.set_scroll_on_output(True)
def _setup_signals(self): def _setup_signals(self):
self.connect("commit", self._handle_commit) self.connect("commit", self._commit)
self.connect("current-directory-uri-changed", self._handle_path_change)
self.connect("selection-changed", self._handle_selection)
self.connect("button-press-event", self._on_button_press)
self.connect("key-press-event", self._on_key_press)
self.connect("key-release-event", self._on_key_release)
self.connect("destroy", self._handle_destroy)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("update_term_path", self.update_term_path) event_system.subscribe("update_term_path", self.update_term_path)
@@ -67,87 +59,35 @@ class VteWidget(Vte.Terminal):
... ...
def _do_session_spawn(self): def _do_session_spawn(self):
env_dict = os.environ.copy() env = [
existing_pc = env_dict.get("PROMPT_COMMAND", "") "DISPLAY=:0",
# Note: Needed for 'current-directory-uri-changed' to work. "LC_ALL=C",
# Make sure user .bashrc doesn't affect it... "TERM='xterm-256color'",
osc7 = 'printf "\\033]7;file://%s%s\\007" "$PWD"' f"HOME='{settings_manager.path_manager.get_home_path()}'",
"XDG_RUNTIME_DIR='/run/user/1000'",
f"XAUTHORITY='{settings_manager.path_manager.get_home_path()}/.Xauthority'",
"HISTFILE=/dev/null",
"HISTSIZE=0",
"HISTFILESIZE=0",
"PS1=\\h@\\u \\W -->: ",
]
env_dict.update({ self.spawn_sync(
"LC_ALL": "C",
"TERM": "xterm-256color",
"HISTFILE": "/dev/null",
"HISTSIZE": "0",
"HISTFILESIZE": "0",
"PS1": "\\h@\\u \\W -->: ",
"PROMPT_COMMAND": f"{osc7};{existing_pc}" if existing_pc else osc7,
})
env = [f"{k}={v}" for k, v in env_dict.items()]
self.spawn_async(
Vte.PtyFlags.DEFAULT, Vte.PtyFlags.DEFAULT,
settings_manager.path_manager.get_home_path(), settings_manager.path_manager.get_home_path(),
["/bin/bash", "--rcfile", f"{settings_manager.path_manager.get_home_path()}/.bashrc", "-i"], ["/bin/bash"],
env, env,
GLib.SpawnFlags.DEFAULT, GLib.SpawnFlags.DEFAULT,
None, None, -1, None, None, None, None,
) )
startup_cmds = [ startup_cmds = [
] ]
self.set_scrollback_lines(15000)
for i in startup_cmds: for i in startup_cmds:
self.run_command(i) self.run_command(i)
def _handle_destroy(self, terminal): def _commit(self, terminal, text, size):
logger.debug("Destroying terminal...")
terminal.disconnect_by_func(terminal._handle_commit)
terminal.disconnect_by_func(terminal._handle_path_change)
terminal.disconnect_by_func(terminal._handle_selection)
terminal.disconnect_by_func(terminal._on_button_press)
terminal.disconnect_by_func(terminal._on_key_press)
terminal.disconnect_by_func(terminal._on_key_release)
terminal.disconnect_by_func(terminal._handle_destroy)
def _handle_path_change(self, terminal):
if not hasattr(self, "label"): return
uri = terminal.get_current_directory_uri().replace("file://", "")
terminal.label.set_text(uri)
terminal.label.set_tooltip_text(uri)
def _handle_selection(self, *args):
if self.get_has_selection():
self.copy_primary()
def _on_button_press(self, widget, event):
if event.button == 2: # middle click
self.paste_clipboard()
return True
def _on_key_press(self, widget, event):
ctrl_pressed = event.state & Gdk.ModifierType.CONTROL_MASK
shift_pressed = event.state & Gdk.ModifierType.SHIFT_MASK
if ctrl_pressed:
if shift_pressed:
if event.keyval in [Gdk.KEY_C, Gdk.KEY_V]:
if event.keyval == Gdk.KEY_C:
self.copy_clipboard()
elif event.keyval == Gdk.KEY_V:
self.paste_clipboard()
return True
return False
def _on_key_release(self, widget, event):
...
def _handle_commit(self, terminal, text, size):
if self.dont_process: if self.dont_process:
self.dont_process = False self.dont_process = False
return return

View File

@@ -5,7 +5,7 @@ import json
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
gi.require_version('WebKit2', '4.1') gi.require_version('WebKit2', '4.0')
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gio from gi.repository import Gio

View File

@@ -1,50 +0,0 @@
# Python imports
import types
# Lib imports
# Application imports
from .event_factory import Event_Factory, Code_Event_Types
class CommandSystem:
def __init__(self, commands: dict | types.ModuleType):
super(CommandSystem, self).__init__()
self.commands: dict | types.ModuleType = commands
self.data: tuple = ()
def set_data(self, *args, **kwargs):
self.data = (args, kwargs)
def exec(self, command: str) -> any:
"""
The 'exec' method passes the default 'self.data' to commands where custom args are not needed.
Ex: The 'code' widget has many internally created commands that
only need 'source_view' and so 'set_data' is called to set that.
"""
if not hasattr(self.commands, command): return
method = getattr(self.commands, command)
args, kwargs = self.data
return method.execute(*args, **kwargs)
def exec_with_args(self, command: str, *args, **kwargs) -> any:
"""
The 'exec_with_args' method passes custom args with the understanding
that the recipient has proper method signature to accept it- whether
*args or **kwargs or something else entirely.
"""
if not hasattr(self.commands, command): return
method = getattr(self.commands, command)
return method.execute(*args, **kwargs)
def add_command(self, command_name: str, command: callable):
setattr(self.commands, command_name, command)
def remove_command(self, command_name: str, command: callable):
if hasattr(self.commands, command_name):
delattr(self.commands, command_name)

View File

@@ -6,9 +6,6 @@
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 .remove_source_view_event import RemoveSourceViewEvent
from .removed_source_view_event import RemovedSourceViewEvent
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
@@ -23,13 +20,11 @@ from .filter_out_loaded_files_event import FilterOutLoadedFilesEvent
from .get_active_view_event import GetActiveViewEvent from .get_active_view_event import GetActiveViewEvent
from .get_source_views_event import GetSourceViewsEvent from .get_source_views_event import GetSourceViewsEvent
from .create_command_system_event import CreateCommandSystemEvent from .get_new_command_system_event import GetNewCommandSystemEvent
from .request_completion_event import RequestCompletionEvent from .request_completion_event import RequestCompletionEvent
from .cursor_moved_event import CursorMovedEvent from .cursor_moved_event import CursorMovedEvent
from .delete_range_event import DeleteRangeEvent
from .modified_changed_event import ModifiedChangedEvent from .modified_changed_event import ModifiedChangedEvent
from .text_changed_event import TextChangedEvent from .text_changed_event import TextChangedEvent
from .text_insert_event import TextInsertEvent
from .text_inserted_event import TextInsertedEvent from .text_inserted_event import TextInsertedEvent
from .focused_view_event import FocusedViewEvent from .focused_view_event import FocusedViewEvent
from .set_active_file_event import SetActiveFileEvent from .set_active_file_event import SetActiveFileEvent

View File

@@ -1,14 +0,0 @@
# 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):
...

View File

@@ -1,19 +0,0 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .code_event import CodeEvent
@dataclass
class DeleteRangeEvent(CodeEvent):
start: Gtk.TextIter = None
end: Gtk.TextIter = None

View File

@@ -9,5 +9,5 @@ from .code_event import CodeEvent
@dataclass @dataclass
class CreateCommandSystemEvent(CodeEvent): class GetNewCommandSystemEvent(CodeEvent):
... ...

View File

@@ -1,14 +0,0 @@
# 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 RemoveSourceViewEvent(CodeEvent):
...

View File

@@ -1,14 +0,0 @@
# 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 RemovedSourceViewEvent(CodeEvent):
...

View File

@@ -1,20 +0,0 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .code_event import CodeEvent
@dataclass
class TextInsertEvent(CodeEvent):
location: Gtk.TextIter = None
text: str = ""
length: int = 0

View File

@@ -1,11 +1,7 @@
# Python imports # Python imports
from dataclasses import dataclass, field from dataclasses import dataclass
# Lib imports # Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports # Application imports
from .code_event import CodeEvent from .code_event import CodeEvent
@@ -14,6 +10,4 @@ from .code_event import CodeEvent
@dataclass @dataclass
class UnregisterProviderEvent(CodeEvent): class UnregisterProviderEvent(CodeEvent):
provider_name: str = "" provider_name: str = ""
provider: GtkSource.CompletionProvider = None
language_ids: list = field(default_factory=lambda: [])

View File

@@ -13,11 +13,9 @@ from .requests import Requests
class Manifest: class Manifest:
name: str = "" name: str = ""
author: str = "" author: str = ""
description: str = "" credit: 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())

View File

@@ -31,7 +31,7 @@ class EventFactory(Singleton):
event_type = self._class_name_to_event_type(name) event_type = self._class_name_to_event_type(name)
self._event_classes[event_type] = obj self._event_classes[event_type] = obj
App_Event_Types.add_event_class(name, obj) Code_Event_Types.add_event_class(name, obj)
i += 1 i += 1
logger.debug(f"Registered {i} event types:") logger.debug(f"Registered {i} event types:")
@@ -44,7 +44,7 @@ class EventFactory(Singleton):
event_type = self._class_name_to_event_type(name) event_type = self._class_name_to_event_type(name)
del self._event_classes[event_type] del self._event_classes[event_type]
App_Event_Types.remove_event_class(name) Code_Event_Types.remove_event_class(name)
i += 1 i += 1
logger.debug(f"Unregistered {i} event types:") logger.debug(f"Unregistered {i} event types:")
@@ -98,7 +98,6 @@ class EventNamespace:
App_Event_Types = EventNamespace() Code_Event_Types = EventNamespace()
Code_Event_Types = App_Event_Types
Event_Factory = EventFactory() Event_Factory = EventFactory()

View File

@@ -35,5 +35,5 @@ class Config:
main_window_height: int = 600 main_window_height: int = 600
application_dirs: list = field(default_factory=lambda: [ application_dirs: list = field(default_factory=lambda: [
"/usr/share/applications", "/usr/share/applications",
f"{settings_manager.path_manager.get_home_path()}/.local/share/applications" f"{settings_manager.get_home_path()}/.local/share/applications"
]) ])

View File

@@ -88,7 +88,6 @@ class PathManager:
def get_ui_widgets_path(self) -> str: return self._UI_WIDGETS_PATH def get_ui_widgets_path(self) -> str: return self._UI_WIDGETS_PATH
def get_context_path(self) -> str: return self._CONTEXT_PATH def get_context_path(self) -> str: return self._CONTEXT_PATH
def get_plugins_path(self) -> str: return self._PLUGINS_PATH def get_plugins_path(self) -> str: return self._PLUGINS_PATH
def get_icons_path(self) -> str: return self._DEFAULT_ICONS
def get_css_file(self) -> str: return self._CSS_FILE def get_css_file(self) -> str: return self._CSS_FILE
def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH
def get_window_icon(self) -> str: return self._WINDOW_ICON def get_window_icon(self) -> str: return self._WINDOW_ICON

View File

@@ -2,7 +2,7 @@
# Lib imports # Lib imports
import gi import gi
gi.require_version('WebKit2', '4.1') gi.require_version('WebKit2', '4.0')
from gi.repository import WebKit2 from gi.repository import WebKit2
# Application imports # Application imports

View File

@@ -30,7 +30,6 @@ class WidgetRegisteryController(ControllerBase):
widgets = self._builder.get_objects() widgets = self._builder.get_objects()
for widget in widgets: for widget in widgets:
if not hasattr(widget, "get_name"): continue
self.builder_keys.append( widget.get_name() ) self.builder_keys.append( widget.get_name() )
def _controller_message(self, event: BaseEvent): def _controller_message(self, event: BaseEvent):

View File

@@ -13,7 +13,7 @@ from gi.repository import Gtk
from gi.repository import GLib from gi.repository import GLib
# Application imports # Application imports
from libs.event_factory import Event_Factory, App_Event_Types, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
from libs.dto.plugins.manifest_meta import ManifestMeta from libs.dto.plugins.manifest_meta import ManifestMeta
from libs.dto.base_event import BaseEvent from libs.dto.base_event import BaseEvent
@@ -58,7 +58,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
item = Gtk.MenuItem(label = "Plugins") item = Gtk.MenuItem(label = "Plugins")
item.connect("activate", self.toggle_plugins_ui) item.connect("activate", self.toggle_plugins_ui)
event.menu.append(item) event.menu.append(item)
elif isinstance(event, App_Event_Types.TogglePluginsUiEvent): elif isinstance(event, Code_Event_Types.TogglePluginsUiEvent):
self.toggle_plugins_ui() self.toggle_plugins_ui()
def _collect_search_locations(self, path: str, locations: list): def _collect_search_locations(self, path: str, locations: list):

View File

@@ -56,7 +56,7 @@ class ManifestManager:
if not manifest.autoload: if not manifest.autoload:
self.manual_launch_manifests.append(manifest_meta) self.manual_launch_manifests.append(manifest_meta)
return manifest_meta return
if manifest.pre_launch: if manifest.pre_launch:
self.pre_launch_manifests.append(manifest_meta) self.pre_launch_manifests.append(manifest_meta)

View File

@@ -48,14 +48,14 @@ class PluginReloadMixin:
def remove_plugin(self, file: str) -> None: def remove_plugin(self, file: str) -> None:
logger.info(f"Removing plugin: {file.get_uri()}") logger.info(f"Removing plugin: {file.get_uri()}")
for manifest_meta in self._plugin_collection[:]:
manifests = self._manifest_manager.pre_launch_manifests \
+ self._manifest_manager.post_launch_manifests \
+ self._manifest_manager.manual_launch_manifests
for manifest_meta in manifests:
if not manifest_meta.folder in file.get_uri(): continue if not manifest_meta.folder in file.get_uri(): continue
manifest_meta.instance.unload()
manifest_meta.instance = None
self._plugin_collection.remove(manifest_meta)
self.plugins_ui.remove_row(manifest_meta)
if manifest_meta in self._manifest_manager.pre_launch_manifests: if manifest_meta in self._manifest_manager.pre_launch_manifests:
self._manifest_manager.pre_launch_manifests.remove(manifest_meta) self._manifest_manager.pre_launch_manifests.remove(manifest_meta)
elif manifest_meta in self._manifest_manager.post_launch_manifests: elif manifest_meta in self._manifest_manager.post_launch_manifests:
@@ -63,15 +63,4 @@ class PluginReloadMixin:
elif manifest_meta in self._manifest_manager.manual_launch_manifests: elif manifest_meta in self._manifest_manager.manual_launch_manifests:
self._manifest_manager.manual_launch_manifests.remove(manifest_meta) self._manifest_manager.manual_launch_manifests.remove(manifest_meta)
self.plugins_ui.remove_row(manifest_meta)
break
del manifests
for manifest_meta in self._plugin_collection[:]:
if not manifest_meta.folder in file.get_uri(): continue
manifest_meta.instance.unload()
manifest_meta.instance = None
self._plugin_collection.remove(manifest_meta)
break break

View File

@@ -2,10 +2,7 @@
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
# Application imports # Application imports
@@ -28,7 +25,6 @@ class PluginsUI(Gtk.Dialog):
self.set_title("Plugins") self.set_title("Plugins")
self.set_size_request(450, 530) self.set_size_request(450, 530)
self.set_modal(False)
self.set_deletable(False) self.set_deletable(False)
self.set_skip_pager_hint(True) self.set_skip_pager_hint(True)
self.set_skip_taskbar_hint(True) self.set_skip_taskbar_hint(True)
@@ -39,13 +35,9 @@ class PluginsUI(Gtk.Dialog):
window = widget_registery.get_object("main-window") window = widget_registery.get_object("main-window")
self.set_transient_for(window) self.set_transient_for(window)
self.set_destroy_with_parent(True)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
def _setup_signals(self): def _setup_signals(self):
self.connect("focus-out-event", self._on_focus_out) ...
self.connect("key-release-event", self._on_key_release)
def _subscribe_to_events(self): def _subscribe_to_events(self):
... ...
@@ -67,19 +59,6 @@ class PluginsUI(Gtk.Dialog):
scrolled_win.show_all() scrolled_win.show_all()
def _on_key_release(self, widget, event):
ctrl_pressed = event.state & Gdk.ModifierType.CONTROL_MASK
shift_pressed = event.state & Gdk.ModifierType.SHIFT_MASK
if ctrl_pressed:
if shift_pressed:
if event.keyval == Gdk.KEY_P:
self.hide()
def _on_focus_out(self, *args):
self.hide()
GLib.idle_add(self.hide)
def add_row(self, manifest_meta, callback: callable): def add_row(self, manifest_meta, callback: callable):
box = Gtk.Box() box = Gtk.Box()
plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name) plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name)
@@ -97,7 +76,6 @@ class PluginsUI(Gtk.Dialog):
toggle_bttn.toggle_id = \ toggle_bttn.toggle_id = \
toggle_bttn.connect("toggled", callback, manifest_meta) toggle_bttn.connect("toggled", callback, manifest_meta)
box.toggle_bttn = toggle_bttn
box.add(plugin_lbl) box.add(plugin_lbl)
box.add(author_lbl) box.add(author_lbl)
@@ -118,5 +96,5 @@ class PluginsUI(Gtk.Dialog):
toggle_bttn.disconnect(toggle_bttn.toggle_id) toggle_bttn.disconnect(toggle_bttn.toggle_id)
self.list_box.remove(row) self.list_box.remove(row)
child.destroy() box.destroy()
break break

View File

@@ -42,8 +42,17 @@
"save_file_as": { "save_file_as": {
"released": "<Control><Shift>s" "released": "<Control><Shift>s"
}, },
"toggle_plugins_ui": { "focus_left_sibling": {
"released": "<Control><Shift>p" "released": "<Control>Page_Up"
},
"focus_right_sibling": {
"released": "<Control>Page_Down"
},
"move_to_left_sibling": {
"released": "<Control><Shift>Up"
},
"move_to_right_sibling": {
"released": "<Control><Shift>Down"
} }
} }
} }