Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ab919f925 | |||
| 383db1270e | |||
| 2d4c8e4f31 | |||
| a8ad015e05 | |||
| 0dc21cbb82 | |||
| 890c6cdfcc | |||
| b13d9c2397 | |||
| e367e31890 | |||
| bd277c0214 | |||
| 12a5e4935e | |||
| 01ede1ac49 |
@@ -25,7 +25,7 @@ class Plugin(PluginCode):
|
||||
if len(history) == history_size:
|
||||
history.pop(0)
|
||||
|
||||
history.append(event.file)
|
||||
history.append(event.file.fpath)
|
||||
|
||||
def load(self):
|
||||
self._manage_signals("register_command")
|
||||
@@ -60,6 +60,6 @@ class Handler:
|
||||
|
||||
view._on_uri_data_received(
|
||||
[
|
||||
history.pop().replace("file://", "")
|
||||
f"file://{history.pop()}"
|
||||
]
|
||||
)
|
||||
|
||||
@@ -14,31 +14,44 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
|
||||
|
||||
class Handler:
|
||||
@staticmethod
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
def execute(view: GtkSource.View, *args, **kwargs):
|
||||
logger.debug("Command: Cut to Temp Buffer")
|
||||
|
||||
clear_temp_cut_buffer_delayed(view)
|
||||
|
||||
buffer = view.get_buffer()
|
||||
itr = buffer.get_iter_at_mark(buffer.get_insert())
|
||||
|
||||
start_itr = itr.copy()
|
||||
start_itr.set_line_offset(0)
|
||||
if buffer.get_has_selection():
|
||||
start_itr, end_itr = buffer.get_selection_bounds()
|
||||
|
||||
end_itr = start_itr.copy()
|
||||
if not end_itr.forward_line():
|
||||
end_itr = buffer.get_end_iter()
|
||||
start_itr.set_line_offset(0)
|
||||
|
||||
if not end_itr.ends_line():
|
||||
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"):
|
||||
view._cut_buffer = ""
|
||||
|
||||
line_str = buffer.get_text(start_itr, end_itr, True)
|
||||
view._cut_buffer += line_str
|
||||
text = buffer.get_text(start_itr, end_itr, True)
|
||||
|
||||
if not text.endswith("\n"):
|
||||
text += "\n"
|
||||
|
||||
view._cut_buffer += text
|
||||
|
||||
buffer.delete(start_itr, end_itr)
|
||||
buffer.place_cursor(start_itr)
|
||||
|
||||
set_temp_cut_buffer_delayed(view)
|
||||
set_temp_cut_buffer_delayed(view)
|
||||
@@ -23,7 +23,7 @@ class Plugin(PluginCode):
|
||||
def load(self):
|
||||
self._manage_signals("register_command")
|
||||
|
||||
def load(self):
|
||||
def unload(self):
|
||||
self._manage_signals("unregister_command")
|
||||
|
||||
def _manage_signals(self, action: str):
|
||||
|
||||
3
plugins/code/commands/split_pane/__init__.py
Normal file
3
plugins/code/commands/split_pane/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Module
|
||||
"""
|
||||
3
plugins/code/commands/split_pane/__main__.py
Normal file
3
plugins/code/commands/split_pane/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Package
|
||||
"""
|
||||
77
plugins/code/commands/split_pane/close_split_view.py
Normal file
77
plugins/code/commands/split_pane/close_split_view.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# 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()
|
||||
77
plugins/code/commands/split_pane/create_split_view.py
Normal file
77
plugins/code/commands/split_pane/create_split_view.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# 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)
|
||||
pane.set_wide_handle(True)
|
||||
|
||||
container.remove(scrolled_win1)
|
||||
pane.pack1( scrolled_win1, True, True )
|
||||
pane.pack2( scrolled_win2, True, True )
|
||||
container.add(pane)
|
||||
|
||||
def _show(pane, alloc, is_vertical: bool):
|
||||
if is_vertical:
|
||||
pane.set_position(alloc.width / 2)
|
||||
else:
|
||||
pane.set_position(alloc.height / 2)
|
||||
|
||||
pane.disconnect(pane.show_id)
|
||||
|
||||
is_control, is_shift, is_alt = modkeys_states
|
||||
alloc = container.get_allocation()
|
||||
if char_str == "|":
|
||||
pane.show_id = pane.connect("show", _show, alloc, True)
|
||||
pane.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
elif char_str == "\\":
|
||||
pane.show_id = pane.connect("show", _show, alloc, False)
|
||||
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
pane.show_all()
|
||||
|
||||
source_view2.command.exec("new_file")
|
||||
source_view2.grab_focus()
|
||||
@@ -7,6 +7,7 @@ gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
@@ -18,5 +19,4 @@ def execute(
|
||||
):
|
||||
logger.debug("Command: Focus Left Sibling")
|
||||
if not view.sibling_left: return
|
||||
view.sibling_left.get_parent().show()
|
||||
view.sibling_left.grab_focus()
|
||||
@@ -18,5 +18,4 @@ def execute(
|
||||
):
|
||||
logger.debug("Command: Focus Right Sibling")
|
||||
if not view.sibling_right: return
|
||||
view.sibling_right.get_parent().show()
|
||||
view.sibling_right.grab_focus()
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Toggle Source View",
|
||||
"name": "Split Pane",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
100
plugins/code/commands/split_pane/plugin.py
Normal file
100
plugins/code/commands/split_pane/plugin.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# 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):
|
||||
...
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Plugin Module
|
||||
"""
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Plugin Package
|
||||
"""
|
||||
@@ -1,64 +0,0 @@
|
||||
# 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()
|
||||
|
||||
@@ -51,7 +51,10 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||
...
|
||||
|
||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
|
||||
...
|
||||
|
||||
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
|
||||
...
|
||||
|
||||
def filter(self, word: str) -> list[dict]:
|
||||
|
||||
@@ -49,7 +49,10 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||
...
|
||||
|
||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
|
||||
...
|
||||
|
||||
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
|
||||
...
|
||||
|
||||
def filter(self, word: str) -> list[dict]:
|
||||
|
||||
@@ -35,11 +35,14 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||
...
|
||||
|
||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
buffer = event.file.buffer
|
||||
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
|
||||
buffer = event.buffer
|
||||
self._clear_temp_delay()
|
||||
self._set_temp_delay(buffer)
|
||||
|
||||
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
|
||||
...
|
||||
|
||||
def _clear_temp_delay(self):
|
||||
if self._temp_timeout_id:
|
||||
GLib.source_remove(self._temp_timeout_id)
|
||||
|
||||
@@ -24,6 +24,7 @@ class Plugin(PluginCode):
|
||||
self.emit_to("files", event)
|
||||
|
||||
file = event.response
|
||||
if not file: return
|
||||
if file.ftype == "buffer": return
|
||||
|
||||
file.check_file_on_disk()
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
# Python imports
|
||||
import threading
|
||||
from os import path
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from ..config import get_lsp_init_config
|
||||
from ..dto.code.lsp.lsp_messages import get_message_str
|
||||
from ..dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseTypes, ClientRequest, ClientNotification
|
||||
@@ -19,38 +15,30 @@ class LSPClient(LSPClientWebsocket):
|
||||
def __init__(self):
|
||||
super(LSPClient, self).__init__()
|
||||
|
||||
# https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers
|
||||
# initialize-params-slim.json was created off of jedi_language_server one
|
||||
# self._init_params = settings_manager.get_lsp_init_data()
|
||||
|
||||
self._socket: str = ""
|
||||
self._language: str = ""
|
||||
self._init_params: dict = {}
|
||||
self._workspace_path: str = ""
|
||||
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] = {}
|
||||
|
||||
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):
|
||||
self._language = language
|
||||
|
||||
def set_workspace_path(self, workspace_path: str):
|
||||
self._workspace_path = workspace_path
|
||||
|
||||
def set_init_opts(self, init_opts: dict[str, str]):
|
||||
self._init_opts = init_opts
|
||||
|
||||
def set_socket(self, socket: str):
|
||||
self._socket = socket
|
||||
|
||||
def unset_socket(self):
|
||||
self._socket = None
|
||||
self._socket = ""
|
||||
|
||||
def send_notification(self, method: str, params: dict = {}):
|
||||
self._send_message( ClientNotification(method, params) )
|
||||
@@ -64,5 +52,5 @@ class LSPClient(LSPClientWebsocket):
|
||||
if not message_id in self._event_history: return
|
||||
return self._event_history[message_id]
|
||||
|
||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes):
|
||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes | dict):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -9,19 +9,24 @@ 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 didclose_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 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 symbols_request
|
||||
from ..dto.code.lsp.lsp_messages import shutdown_request
|
||||
from ..dto.code.lsp.lsp_messages import exit_request
|
||||
|
||||
|
||||
|
||||
class LSPClientEvents:
|
||||
def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str):
|
||||
folder_name = os.path.basename(workspace_file)
|
||||
def send_initialize_message(self):
|
||||
folder_name = os.path.basename(self._workspace_path)
|
||||
workspace_uri = f"file://{self._workspace_path}"
|
||||
|
||||
self._init_params["processId"] = None
|
||||
self._init_params["rootPath"] = workspace_file
|
||||
self._init_params["rootPath"] = self._workspace_path
|
||||
self._init_params["rootUri"] = workspace_uri
|
||||
self._init_params["workspaceFolders"] = [
|
||||
{
|
||||
@@ -30,15 +35,22 @@ class LSPClientEvents:
|
||||
}
|
||||
]
|
||||
|
||||
self._init_params["initializationOptions"] = init_ops
|
||||
self._init_params["initializationOptions"] = self._init_opts
|
||||
self.send_request("initialize", self._init_params)
|
||||
|
||||
def send_initialized_message(self):
|
||||
def send_initialized_notification(self):
|
||||
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):
|
||||
method = "textDocument/didOpen"
|
||||
params = didopen_notification["params"]
|
||||
self.doc_vers[ data["uri"] ] = -1
|
||||
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["textDocument"]["languageId"] = data["language_id"]
|
||||
@@ -76,24 +88,24 @@ class LSPClientEvents:
|
||||
|
||||
self.send_notification( method, params )
|
||||
|
||||
# def _lsp_did_change(self, data: dict):
|
||||
# method = "textDocument/didChange"
|
||||
# params = didchange_notification_range["params"]
|
||||
def _lsp_did_change_range(self, data: dict):
|
||||
method = "textDocument/didChange"
|
||||
params = didchange_notification_range["params"]
|
||||
|
||||
# params["textDocument"]["uri"] = data["uri"]
|
||||
# params["textDocument"]["languageId"] = data["language_id"]
|
||||
# params["textDocument"]["version"] = data["version"]
|
||||
params["textDocument"]["uri"] = data["uri"]
|
||||
params["textDocument"]["languageId"] = data["language_id"]
|
||||
params["textDocument"]["version"] = data["version"]
|
||||
|
||||
# contentChanges = params["contentChanges"][0]
|
||||
# start = contentChanges["range"]["start"]
|
||||
# end = contentChanges["range"]["end"]
|
||||
# contentChanges["text"] = data["text"]
|
||||
# start["line"] = data["line"]
|
||||
# start["character"] = 0
|
||||
# end["line"] = data["line"]
|
||||
# end["character"] = data["column"]
|
||||
contentChanges = params["contentChanges"][0]
|
||||
start = contentChanges["range"]["start"]
|
||||
end = contentChanges["range"]["end"]
|
||||
contentChanges["text"] = data["text"]
|
||||
start["line"] = data["line"]
|
||||
start["character"] = data["column"]
|
||||
end["line"] = data["end_line"]
|
||||
end["character"] = data["end_column"]
|
||||
|
||||
# self.send_notification( method, params )
|
||||
self.send_notification( method, params )
|
||||
|
||||
def _lsp_definition(self, data: dict):
|
||||
method = "textDocument/definition"
|
||||
@@ -107,13 +119,33 @@ class LSPClientEvents:
|
||||
|
||||
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):
|
||||
method = "textDocument/completion"
|
||||
params = completion_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"]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from ..dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
|
||||
|
||||
from .lsp_client_base import LSPClientBase
|
||||
from .websocket_client import WebsocketClient
|
||||
from .websocket import Websocket
|
||||
|
||||
|
||||
|
||||
@@ -24,26 +24,26 @@ class LSPClientWebsocket(LSPClientBase):
|
||||
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
|
||||
|
||||
logger.debug(f"Client: {message_str}")
|
||||
self.ws_client.send(message_str)
|
||||
self.websocket.send(message_str)
|
||||
|
||||
def start_client(self):
|
||||
self.ws_client = WebsocketClient()
|
||||
self.ws_client.set_socket(self._socket)
|
||||
self.ws_client.set_callback(self._monitor_lsp_response)
|
||||
self.ws_client.start_client()
|
||||
self.websocket = Websocket()
|
||||
self.websocket.set_socket(self._socket)
|
||||
self.websocket.set_callback(self._monitor_lsp_response)
|
||||
self.websocket.start_client()
|
||||
|
||||
return self.ws_client
|
||||
return self.websocket
|
||||
|
||||
def stop_client(self):
|
||||
if not hasattr(self, "ws_client"): return
|
||||
self.ws_client.close_client()
|
||||
if not hasattr(self, "websocket"): return
|
||||
self.websocket.close_client()
|
||||
|
||||
def _monitor_lsp_response(self, data: dict | None):
|
||||
if not data: return
|
||||
if not data: return {}
|
||||
|
||||
message = get_message_obj(data)
|
||||
keys = message.keys()
|
||||
lsp_response = None
|
||||
lsp_response = data
|
||||
|
||||
if "result" in keys:
|
||||
lsp_response = LSPResponseRequest(**get_message_obj(data))
|
||||
@@ -51,6 +51,7 @@ class LSPClientWebsocket(LSPClientBase):
|
||||
if "method" in keys:
|
||||
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
|
||||
|
||||
if not lsp_response: return
|
||||
if isinstance(lsp_response, str):
|
||||
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)
|
||||
|
||||
@@ -9,7 +9,7 @@ from ..libs import websocket
|
||||
|
||||
|
||||
|
||||
class WebsocketClient:
|
||||
class Websocket:
|
||||
def __init__(self):
|
||||
self.ws = None
|
||||
self._socket = None
|
||||
@@ -59,4 +59,4 @@ class WebsocketClient:
|
||||
on_error = self.on_error,
|
||||
on_close = self.on_close)
|
||||
|
||||
self.ws.run_forever(reconnect = 0.5)
|
||||
self.ws.run_forever(reconnect = 0.5)
|
||||
@@ -4,14 +4,15 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .mixins.lsp_client_events_mixin import LSPClientEventsMixin
|
||||
from .config import get_lsp_connect_timout
|
||||
from .mixins.client_manager_events_mixin import ClientManagerEventsMixin
|
||||
from .client.lsp_client import LSPClient
|
||||
|
||||
|
||||
|
||||
class LSPManagerClient(LSPClientEventsMixin):
|
||||
class ClientManager(ClientManagerEventsMixin):
|
||||
def __init__(self):
|
||||
super(LSPManagerClient, self).__init__()
|
||||
super(ClientManager, self).__init__()
|
||||
|
||||
self._cache_refresh_timeout_id: int = None
|
||||
|
||||
@@ -22,22 +23,24 @@ class LSPManagerClient(LSPClientEventsMixin):
|
||||
|
||||
def create_client(
|
||||
self,
|
||||
lang_id: str = "python",
|
||||
workspace_uri: str = "",
|
||||
init_opts: dict = {}
|
||||
lang_id: str,
|
||||
workspace_path: str,
|
||||
init_opts: dict[str, str],
|
||||
address: str = "127.0.0.1",
|
||||
port: str = "9999"
|
||||
) -> LSPClient:
|
||||
if lang_id in self.clients: return None
|
||||
|
||||
address = "127.0.0.1"
|
||||
port = 9999
|
||||
uri = f"ws://{address}:{port}/{lang_id}"
|
||||
uri = f"ws://{address}:{port}/{lang_id}?workspace={workspace_path}"
|
||||
client = LSPClient()
|
||||
|
||||
client.set_language(lang_id)
|
||||
client.set_socket(uri)
|
||||
client.set_language(lang_id)
|
||||
client.set_workspace_path(workspace_path)
|
||||
client.set_init_opts(init_opts)
|
||||
client.start_client()
|
||||
|
||||
if not client.ws_client.wait_for_connection(timeout = 5.0):
|
||||
if not client.websocket.wait_for_connection(timeout = get_lsp_connect_timout()):
|
||||
logger.error(f"Failed to connect to LSP server for {lang_id}")
|
||||
return None
|
||||
|
||||
87
plugins/code/language_server_clients/lsp_manager/commands.py
Normal file
87
plugins/code/language_server_clients/lsp_manager/commands.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
)
|
||||
38
plugins/code/language_server_clients/lsp_manager/config.py
Normal file
38
plugins/code/language_server_clients/lsp_manager/config.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# 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 {}
|
||||
@@ -96,10 +96,10 @@ didchange_notification_range = {
|
||||
"uri": "file://",
|
||||
"languageId": "python",
|
||||
"version": 1,
|
||||
"text": ""
|
||||
},
|
||||
"contentChanges": [
|
||||
{
|
||||
"text": "",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
@@ -108,9 +108,8 @@ didchange_notification_range = {
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 1,
|
||||
},
|
||||
"rangeLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -125,19 +124,11 @@ completion_request = {
|
||||
"method": "textDocument/completion",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file://",
|
||||
"languageId": "python",
|
||||
"version": 1,
|
||||
"text": ""
|
||||
"uri": "file://"
|
||||
},
|
||||
"position": {
|
||||
"line": 5,
|
||||
"character": 12,
|
||||
"offset": 0
|
||||
},
|
||||
"contet": {
|
||||
"triggerKind": 3,
|
||||
"triggerCharacter": ""
|
||||
"character": 12
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,6 +150,19 @@ definition_request = {
|
||||
}
|
||||
}
|
||||
|
||||
implementation_request = {
|
||||
"method": "textDocument/implementation",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file://"
|
||||
},
|
||||
"position": {
|
||||
"line": 5,
|
||||
"character": 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
references_request = {
|
||||
"method": "textDocument/references",
|
||||
"params": {
|
||||
@@ -179,7 +183,6 @@ references_request = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
symbols_request = {
|
||||
"method": "textDocument/documentSymbol",
|
||||
"params": {
|
||||
@@ -190,4 +193,15 @@ symbols_request = {
|
||||
"text": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shutdown_request = {
|
||||
"method": "shutdown",
|
||||
"params": None
|
||||
}
|
||||
|
||||
exit_request = {
|
||||
"method": "exit",
|
||||
"params": None
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from libs.controllers.controller_base import ControllerBase
|
||||
@@ -11,10 +14,11 @@ from .dto.code.events import \
|
||||
from .dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
|
||||
|
||||
from .ui_manager import UIManager
|
||||
|
||||
from .provider import Provider
|
||||
from .provider_response_cache import ProviderResponseCache
|
||||
from .lsp_manager_ui import LSPManagerUI
|
||||
from .lsp_manager_client import LSPManagerClient
|
||||
from .client_manager import ClientManager
|
||||
from .response_handlers.response_registry import ResponseRegistry
|
||||
|
||||
|
||||
@@ -31,16 +35,16 @@ class LSPManager(ControllerBase):
|
||||
def _init(self):
|
||||
self.provider: Provider = Provider()
|
||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
||||
self.lsp_manager_client: LSPManagerClient = LSPManagerClient()
|
||||
self.client_manager: ClientManager = ClientManager()
|
||||
self.response_registry: ResponseRegistry = ResponseRegistry()
|
||||
|
||||
def _load_widgets(self):
|
||||
self.lsp_manager_ui: LSPManagerUI = LSPManagerUI()
|
||||
self.lsp_manager_ui.connect('create-client', self._on_create_client)
|
||||
self.lsp_manager_ui.connect('close-client', self._on_close_client)
|
||||
self.ui_manager: LSPManagerUI = UIManager()
|
||||
self.ui_manager.connect('create-client', self._on_create_client)
|
||||
self.ui_manager.connect('close-client', self._on_close_client)
|
||||
|
||||
def _do_bind_mapping(self):
|
||||
self.response_cache.set_lsp_manager_client(self.lsp_manager_client)
|
||||
self.response_cache.set_lsp_manager_client(self.client_manager)
|
||||
self.provider.response_cache = self.response_cache
|
||||
self.response_registry.set_event_hub(
|
||||
self.emit, self.emit_to, self.provider
|
||||
@@ -49,39 +53,51 @@ class LSPManager(ControllerBase):
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.RegisterLspClientEvent):
|
||||
self.response_registry.register_handler(event.lang_id, event.handler)
|
||||
self.lsp_manager_ui.add_client_listing(event.lang_id, event.lang_config)
|
||||
self.ui_manager.add_client_listing(event.lang_id, event.lang_config)
|
||||
elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent):
|
||||
self.response_registry.unregister_handler(event.lang_id)
|
||||
self.lsp_manager_ui.remove_client_listing(event.lang_id)
|
||||
self.ui_manager.remove_client_listing(event.lang_id)
|
||||
|
||||
def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool:
|
||||
def _on_create_client(self, ui, lang_id: str, workspace_path: str) -> bool:
|
||||
init_opts = ui.get_init_opts(lang_id)
|
||||
result = self.create_client(lang_id, workspace_uri, init_opts)
|
||||
result = self.create_client(
|
||||
lang_id,
|
||||
workspace_path,
|
||||
init_opts,
|
||||
ui.adddress_entry.get_text(),
|
||||
f"{ int( ui.adddress_port.get_value() ) }"
|
||||
)
|
||||
|
||||
if result:
|
||||
ui.toggle_client_buttons(show_close=True)
|
||||
ui.toggle_client_buttons(show_close = True)
|
||||
|
||||
return result
|
||||
|
||||
def _on_close_client(self, ui, lang_id: str) -> bool:
|
||||
result = self.close_client(lang_id)
|
||||
|
||||
if result:
|
||||
ui.toggle_client_buttons(show_close=False)
|
||||
ui.toggle_client_buttons(show_close = False)
|
||||
|
||||
return result
|
||||
|
||||
def handle_destroy(self):
|
||||
self.lsp_manager_ui.disconnect_by_func(self._on_create_client)
|
||||
self.lsp_manager_ui.disconnect_by_func(self._on_close_client)
|
||||
self.ui_manager.disconnect_by_func(self._on_create_client)
|
||||
self.ui_manager.disconnect_by_func(self._on_close_client)
|
||||
|
||||
def create_client(
|
||||
self,
|
||||
lang_id: str = "python",
|
||||
workspace_uri: str = "",
|
||||
init_opts: dict = {}
|
||||
lang_id: str,
|
||||
workspace_path: str,
|
||||
init_opts: dict[str, str],
|
||||
address: str,
|
||||
port: str
|
||||
) -> bool:
|
||||
client = self.lsp_manager_client.create_client(
|
||||
lang_id, workspace_uri, init_opts
|
||||
client = self.client_manager.create_client(
|
||||
lang_id, workspace_path, init_opts, address, port
|
||||
)
|
||||
handler = self.response_registry.get_handler(lang_id)
|
||||
self.lsp_manager_client.active_language_id = lang_id
|
||||
self.client_manager.active_language_id = lang_id
|
||||
|
||||
if not client or not handler:
|
||||
logger.error(f"LSP Manager: Either 'client' or 'handler' didn't get created...'")
|
||||
@@ -92,28 +108,46 @@ class LSPManager(ControllerBase):
|
||||
handler.set_response_cache(self.response_cache)
|
||||
|
||||
client.handle_lsp_response = self.server_response
|
||||
client.send_initialize_message(init_opts, "", f"file://{workspace_uri}")
|
||||
|
||||
return True
|
||||
|
||||
def close_client(self, lang_id: str) -> bool:
|
||||
self.lsp_manager_client.close_client(lang_id)
|
||||
self.response_registry.close_handler(lang_id)
|
||||
controller = self.client_manager.get_active_client()
|
||||
controller.send_shutdown_request()
|
||||
|
||||
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
|
||||
|
||||
def server_response(self, lsp_response: LSPResponseTypes):
|
||||
def server_response(self, lsp_response: LSPResponseTypes | dict):
|
||||
logger.debug(f"LSP Response: { lsp_response }")
|
||||
|
||||
if isinstance(lsp_response, LSPResponseRequest):
|
||||
if not self.lsp_manager_client.active_language_id in self.lsp_manager_client.clients:
|
||||
logger.debug(f"No LSP client for '{self.lsp_manager_client.active_language_id}', skipping 'server_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.lsp_manager_client.get_active_client()
|
||||
|
||||
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 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()
|
||||
event = controller.get_event_by_id(lsp_response.id)
|
||||
handler = self.response_registry.get_handler(
|
||||
self.lsp_manager_client.active_language_id, event
|
||||
self.client_manager.active_language_id, event
|
||||
)
|
||||
|
||||
if not handler: return
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,191 @@
|
||||
# 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)
|
||||
@@ -1,144 +0,0 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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", {})
|
||||
@@ -0,0 +1,77 @@
|
||||
# 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() )
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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)
|
||||
@@ -12,6 +12,7 @@ from libs.dto.states import SourceViewStates
|
||||
from plugins.plugin_types import PluginCode
|
||||
|
||||
from .dto.code import events as lsp_events
|
||||
from .commands import Commands
|
||||
from .lsp_manager import LSPManager
|
||||
|
||||
|
||||
@@ -35,23 +36,11 @@ class Plugin(PluginCode):
|
||||
|
||||
window = self.request_ui_element("main-window")
|
||||
|
||||
lsp_manager.lsp_manager_ui.map_parent_resize_event(window)
|
||||
lsp_manager.ui_manager.map_parent_resize_event(window)
|
||||
|
||||
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_signals("register_command")
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"register_provider",
|
||||
provider_name = "LSP Completer",
|
||||
provider = lsp_manager.provider,
|
||||
language_ids = []
|
||||
)
|
||||
self.emit_to("completion", event)
|
||||
self._manage_provider("register_provider")
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"create_source_view",
|
||||
@@ -59,8 +48,8 @@ class Plugin(PluginCode):
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
source_view = event.response
|
||||
lsp_manager.lsp_manager_ui.set_source_view(source_view)
|
||||
scrolled_win, source_view = event.response
|
||||
lsp_manager.ui_manager.set_source_view(scrolled_win, source_view)
|
||||
|
||||
def unload(self):
|
||||
Event_Factory.unregister_events( lsp_events.__dict__.items() )
|
||||
@@ -69,56 +58,61 @@ class Plugin(PluginCode):
|
||||
|
||||
window = self.request_ui_element("main-window")
|
||||
|
||||
lsp_manager.lsp_manager_ui.unmap_parent_resize_event(window)
|
||||
lsp_manager.ui_manager.unmap_parent_resize_event(window)
|
||||
|
||||
event = Event_Factory.create_event("unregister_command",
|
||||
command_name = "LSP Manager",
|
||||
command = Handler,
|
||||
self._manage_signals("unregister_command")
|
||||
|
||||
self._manage_provider("unregister_provider")
|
||||
|
||||
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 = ["<Shift><Control>l", "<Control>g", "<Control>i"]
|
||||
binding = "<Shift><Control>l"
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"unregister_provider",
|
||||
provider_name = "LSP Completer"
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "lsp_references",
|
||||
command = _commands.lsp_references,
|
||||
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)
|
||||
|
||||
lsp_manager.handle_destroy()
|
||||
|
||||
def run(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()
|
||||
|
||||
@@ -32,20 +32,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
||||
return "LSP Code Completion"
|
||||
|
||||
def do_match(self, context):
|
||||
# Note: If provider is in interactive activation then need to check
|
||||
# view focus as otherwise non focus views start trying to grab it.
|
||||
# completion = context.get_property("completion")
|
||||
# if not completion.get_view().has_focus(): return
|
||||
|
||||
iter = self.response_cache.get_iter_correctly(context)
|
||||
iter.backward_char()
|
||||
ch = iter.get_char()
|
||||
|
||||
# NOTE: Look to re-add or apply 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()
|
||||
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
|
||||
@@ -68,6 +55,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
||||
# return GtkSource.CompletionActivation.NONE
|
||||
return GtkSource.CompletionActivation.USER_REQUESTED
|
||||
# return GtkSource.CompletionActivation.INTERACTIVE
|
||||
# return GtkSource.CompletionActivation.USER_REQUESTED | GtkSource.CompletionActivation.INTERACTIVE
|
||||
|
||||
def do_populate(self, context):
|
||||
results = self.response_cache.filter_with_context(context)
|
||||
|
||||
@@ -33,9 +33,13 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
||||
if self.lsp_manager_client:
|
||||
self.lsp_manager_client.process_file_save(event)
|
||||
|
||||
def process_file_change(self, event):
|
||||
def process_file_text_inserted(self, event):
|
||||
if self.lsp_manager_client:
|
||||
self.lsp_manager_client.process_file_change(event)
|
||||
self.lsp_manager_client.process_file_text_inserted(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]:
|
||||
return []
|
||||
|
||||
@@ -17,10 +17,18 @@ class DefaultHandler(BaseHandler):
|
||||
|
||||
def handle(self, method: str, response, controller):
|
||||
match method:
|
||||
case "initialize":
|
||||
controller.send_initialized_notification()
|
||||
case "shutdown":
|
||||
controller.send_exit_notification()
|
||||
case "textDocument/completion":
|
||||
self._handle_completion(response)
|
||||
case "textDocument/definition":
|
||||
self._handle_definition(response, controller)
|
||||
case "textDocument/references":
|
||||
...
|
||||
case "textDocument/implementation":
|
||||
...
|
||||
case "textDocument/publishDiagnostics":
|
||||
self._handle_diagnostics(response)
|
||||
|
||||
@@ -130,5 +138,5 @@ class DefaultHandler(BaseHandler):
|
||||
view = view,
|
||||
provider = self.context._provider
|
||||
)
|
||||
self.emit_to("completion", event)
|
||||
|
||||
self.emit_to("completion", event)
|
||||
|
||||
@@ -40,7 +40,7 @@ class ResponseRegistry:
|
||||
handler_cls = self._lang_handlers.get(
|
||||
lang_id, self._lang_handlers.get("default", DefaultHandler)
|
||||
)
|
||||
|
||||
|
||||
if not handler_cls: return None
|
||||
|
||||
return self._get_instance(handler_cls)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,16 @@
|
||||
},
|
||||
"jedi_completion": {
|
||||
"enabled": true,
|
||||
"fuzzy": true,
|
||||
"include_params": false,
|
||||
"include_class_objects": true,
|
||||
"include_function_objects": true,
|
||||
"fuzzy": false
|
||||
"include_function_objects": true
|
||||
},
|
||||
"jedi_signature_help": {
|
||||
"enabled": true
|
||||
},
|
||||
"jedi_references": {
|
||||
"enabled": true
|
||||
},
|
||||
"jedi": {
|
||||
"root_dir": "file://{workspace.folder}",
|
||||
@@ -60,41 +67,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ class Plugin(PluginCode):
|
||||
|
||||
event = Event_Factory.create_event("get_active_view")
|
||||
self.emit_to("source_views", event)
|
||||
if not event.response: return
|
||||
code_minimap.set_smini_view(event.response)
|
||||
|
||||
def unload(self):
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
@@ -25,8 +28,19 @@ class Plugin(PluginCode):
|
||||
|
||||
self.register_controller("tabs", self.tabs_controller)
|
||||
|
||||
code_container.add( self.tabs_controller.tabs_widget )
|
||||
code_container.reorder_child(self.tabs_controller.tabs_widget, 0)
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
viewport = Gtk.Viewport()
|
||||
|
||||
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")
|
||||
self.emit_to("files", event)
|
||||
@@ -36,7 +50,14 @@ class Plugin(PluginCode):
|
||||
def unload(self):
|
||||
self.unregister_controller("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 = None
|
||||
|
||||
@@ -27,7 +27,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_scrollable(True)
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("page-added", self._page_added)
|
||||
@@ -67,6 +67,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
)
|
||||
|
||||
self.emit(event)
|
||||
self._scroll_to_center(tab)
|
||||
|
||||
def _bind_tab_menu(self, tab, page_widget):
|
||||
def do_context_menu(tab, eve, page_widget):
|
||||
@@ -81,6 +82,21 @@ class TabsWidget(Gtk.Notebook):
|
||||
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:
|
||||
context_menu = Gtk.Menu()
|
||||
close_submenu = Gtk.Menu()
|
||||
@@ -130,6 +146,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
self.page_num(page_widget)
|
||||
)
|
||||
self.handler_unblock(self.switch_page_id)
|
||||
self._scroll_to_center(tab)
|
||||
|
||||
break
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.webkit.webkit_ui import WebkitUI
|
||||
from core.widgets.webkit.webkit_ui import WebkitUI
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class CenterContainer(Gtk.Box):
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
self.set_size_request(320, -1)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("show", self._handle_show)
|
||||
|
||||
@@ -6,10 +6,6 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ...widgets.code.code_base import CodeBase
|
||||
|
||||
from ...widgets.separator_widget import Separator
|
||||
|
||||
from .editors_container import EditorsContainer
|
||||
|
||||
|
||||
@@ -27,6 +23,9 @@ class CodeContainer(Gtk.Box):
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("code-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
def _setup_signals(self):
|
||||
@@ -37,18 +36,4 @@ class CodeContainer(Gtk.Box):
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("code-container", self)
|
||||
|
||||
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
|
||||
self.add( EditorsContainer() )
|
||||
|
||||
@@ -4,18 +4,17 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
#from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from core.widgets.code.code_base import CodeBase
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
|
||||
|
||||
class EditorsContainer(Gtk.Paned):
|
||||
def __init__(self, code_base: any):
|
||||
class EditorsContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(EditorsContainer, self).__init__()
|
||||
|
||||
self.code_base = code_base
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
@@ -24,11 +23,11 @@ class EditorsContainer(Gtk.Paned):
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("paned-editors-container")
|
||||
self.ctx.add_class("editors-container")
|
||||
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
self.set_wide_handle(True)
|
||||
self.set_size_request(320, -1)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("map", self._init_map)
|
||||
@@ -37,26 +36,16 @@ class EditorsContainer(Gtk.Paned):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.scrolled_win1, \
|
||||
self.scrolled_win2 = self._create_views()
|
||||
box = Gtk.Box()
|
||||
widget_registery.expose_object("editors-container", self)
|
||||
self.code_base = CodeBase()
|
||||
scrolled_win, \
|
||||
source_view = self.code_base.create_source_view()
|
||||
|
||||
self.pack1( self.scrolled_win1, True, True )
|
||||
self.pack2( self.scrolled_win2, True, True )
|
||||
|
||||
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
|
||||
box.add( scrolled_win )
|
||||
self.add( Separator("separator_left") )
|
||||
self.add( box )
|
||||
self.add( Separator("separator_right") )
|
||||
|
||||
def _init_map(self, view):
|
||||
self.disconnect_by_func( self._init_map )
|
||||
|
||||
@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from ..widgets.controls.open_files_button import OpenFilesButton
|
||||
from ..widgets.controls.transparency_scale import TransparencyScale
|
||||
from core.widgets.separator_widget import Separator
|
||||
from core.widgets.controls.open_files_button import OpenFilesButton
|
||||
from core.widgets.controls.transparency_scale import TransparencyScale
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
from .code.code_container import CodeContainer
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from ..widgets.vte_widget import VteWidget
|
||||
from core.widgets.separator_widget import Separator
|
||||
from core.widgets.vte_widget import VteWidget
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,16 +18,12 @@ class BaseControllerMixin:
|
||||
files = []
|
||||
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
if os.path.isdir( arg.replace("file://", "") ):
|
||||
files.append( f"DIR|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
if os.path.isfile(arg):
|
||||
files.append(f"{arg}")
|
||||
|
||||
# NOTE: If passing line number with file split against :
|
||||
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
||||
files.append( f"FILE|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
|
||||
logger.info(f"Not a File: {arg}")
|
||||
if os.path.isdir(arg):
|
||||
message = f"DIR|{arg}"
|
||||
ipc_server.send_ipc_message(message)
|
||||
|
||||
if not files: return
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ class CodeBase:
|
||||
completion_controller = CompletionController()
|
||||
source_views_controller = SourceViewsController()
|
||||
|
||||
# self.controller_manager.register_controller("base", self)
|
||||
self.controller_manager.register_controller("files", files_controller)
|
||||
self.controller_manager.register_controller("commands", commands_controller)
|
||||
self.controller_manager.register_controller("completion", completion_controller)
|
||||
@@ -43,12 +42,13 @@ class CodeBase:
|
||||
self.controller_manager.register_controller("widgets", widget_registery)
|
||||
|
||||
def create_source_view(self):
|
||||
source_view = self.controller_manager["source_views"].create_source_view()
|
||||
scrolled_win, \
|
||||
source_view = self.controller_manager["source_views"].create_source_view()
|
||||
self.controller_manager["completion"].register_completer(
|
||||
source_view.get_completion()
|
||||
)
|
||||
|
||||
return source_view
|
||||
return scrolled_win, source_view
|
||||
|
||||
def first_map_load(self):
|
||||
self.controller_manager["source_views"].first_map_load()
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
Code Command System Package
|
||||
"""
|
||||
|
||||
from .command_system import CommandSystem
|
||||
from .source_view_command_system import SourceViewCommandSystem
|
||||
|
||||
@@ -8,7 +8,10 @@ from gi.repository import GtkSource
|
||||
|
||||
|
||||
def set_language_and_style(view, file):
|
||||
language = view.language_manager.guess_language(file.fname, None)
|
||||
language = None
|
||||
if not file.fname == "buffer":
|
||||
language = view.language_manager.guess_language(file.fname, None)
|
||||
|
||||
file.buffer.set_language(language)
|
||||
file.buffer.set_style_scheme(view.syntax_theme)
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# 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)
|
||||
@@ -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")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
|
||||
if not buffer.get_has_selection():
|
||||
had_selection = False
|
||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
start_itr = itr.copy()
|
||||
end_itr = itr.copy()
|
||||
start_line = itr.get_line() + 1
|
||||
start_char = itr.get_line_offset()
|
||||
if buffer.get_has_selection():
|
||||
start_itr, \
|
||||
end_itr = buffer.get_selection_bounds()
|
||||
start_line = start_itr.get_line()
|
||||
end_line = end_itr.get_line()
|
||||
scol = start_itr.get_line_offset()
|
||||
ecol = end_itr.get_line_offset()
|
||||
else:
|
||||
had_selection = True
|
||||
start_itr, end_itr = buffer.get_selection_bounds()
|
||||
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
|
||||
itr = buffer.get_iter_at_mark(buffer.get_insert())
|
||||
start_line = end_line = itr.get_line()
|
||||
col = itr.get_line_offset()
|
||||
|
||||
start_itr.backward_visible_line()
|
||||
start_itr.forward_line()
|
||||
end_itr.forward_line()
|
||||
end_itr.backward_char()
|
||||
start_itr = buffer.get_iter_at_line(start_line)
|
||||
end_itr = buffer.get_iter_at_line(end_line)
|
||||
|
||||
line_str = buffer.get_slice(start_itr, end_itr, True)
|
||||
end_itr.forward_char()
|
||||
buffer.insert(end_itr, f"{line_str}\n", -1)
|
||||
if not end_itr.ends_line():
|
||||
end_itr.forward_to_line_end()
|
||||
|
||||
if not had_selection:
|
||||
new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
|
||||
buffer.place_cursor(new_itr)
|
||||
if not end_itr.is_end():
|
||||
end_itr.forward_char()
|
||||
|
||||
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:
|
||||
new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
|
||||
new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char)
|
||||
buffer.select_range(new_itr, new_end_itr)
|
||||
new_start = buffer.get_iter_at_line_offset(end_line + 1, col)
|
||||
buffer.place_cursor(new_start)
|
||||
|
||||
@@ -20,9 +20,14 @@ def execute(
|
||||
ctx = view.get_parent().get_style_context()
|
||||
ctx.add_class("source-view-focused")
|
||||
|
||||
if view.sibling_right:
|
||||
ctx = view.sibling_right.get_parent().get_style_context()
|
||||
elif view.sibling_left:
|
||||
ctx = view.sibling_left.get_parent().get_style_context()
|
||||
lview = view.sibling_left
|
||||
while lview is not None:
|
||||
ctx = lview.get_parent().get_style_context()
|
||||
ctx.remove_class("source-view-focused")
|
||||
lview = lview.sibling_left
|
||||
|
||||
ctx.remove_class("source-view-focused")
|
||||
rview = view.sibling_right
|
||||
while rview is not None:
|
||||
ctx = rview.get_parent().get_style_context()
|
||||
ctx.remove_class("source-view-focused")
|
||||
rview = rview.sibling_right
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
from libs.command_system import CommandSystem
|
||||
|
||||
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):
|
||||
event = Event_Factory.create_event( "toggle_plugins_ui" )
|
||||
|
||||
@@ -81,3 +88,11 @@ class CommandSystemMixin:
|
||||
)
|
||||
|
||||
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... """
|
||||
...
|
||||
@@ -36,8 +36,11 @@ class ProviderResponseCacheBase:
|
||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...")
|
||||
|
||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_change' not implemented...")
|
||||
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
|
||||
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_text_inserted' 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]:
|
||||
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...")
|
||||
|
||||
@@ -7,7 +7,7 @@ from libs.controllers.controller_base import ControllerBase
|
||||
|
||||
from libs.event_factory import Code_Event_Types
|
||||
|
||||
from ..command_system import CommandSystem
|
||||
from ..command_system import SourceViewCommandSystem
|
||||
|
||||
|
||||
|
||||
@@ -17,14 +17,19 @@ class CommandsController(ControllerBase, list):
|
||||
|
||||
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent):
|
||||
event.response = self.get_new_command_system()
|
||||
if isinstance(event, Code_Event_Types.CreateCommandSystemEvent):
|
||||
event.response = self.create_command_system()
|
||||
elif isinstance(event, Code_Event_Types.RemovedSourceViewEvent):
|
||||
self.remove_command_system(event)
|
||||
|
||||
def get_new_command_system(self):
|
||||
command_system = CommandSystem()
|
||||
def create_command_system(self):
|
||||
command_system = SourceViewCommandSystem()
|
||||
command_system.emit = self.emit
|
||||
command_system.emit_to = self.emit_to
|
||||
|
||||
self.append(command_system)
|
||||
|
||||
return command_system
|
||||
|
||||
def remove_command_system(self, event: Code_Event_Types.RemovedSourceViewEvent):
|
||||
self.remove(event.view.command)
|
||||
|
||||
@@ -41,8 +41,10 @@ class CompletionController(ControllerBase):
|
||||
self.provider_process_file_close(event)
|
||||
elif isinstance(event, Code_Event_Types.SavedFileEvent):
|
||||
self.provider_process_file_save(event)
|
||||
elif isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||
self.provider_process_file_change(event)
|
||||
elif isinstance(event, Code_Event_Types.TextInsertedEvent):
|
||||
self.provider_process_file_text_inserted(event)
|
||||
elif isinstance(event, Code_Event_Types.DeleteRangeEvent):
|
||||
self.provider_process_file_delete_range(event)
|
||||
elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
|
||||
self.request_unbound_completion(event)
|
||||
|
||||
@@ -88,9 +90,13 @@ class CompletionController(ControllerBase):
|
||||
for provider in self._providers.values():
|
||||
provider.response_cache.process_file_save(event)
|
||||
|
||||
def provider_process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
def provider_process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
|
||||
for provider in self._providers.values():
|
||||
provider.response_cache.process_file_change(event)
|
||||
provider.response_cache.process_file_text_inserted(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):
|
||||
completer = event.view.get_completion()
|
||||
|
||||
@@ -46,47 +46,57 @@ class MarkerManager(MarkSupportMixin):
|
||||
start_itr = buffer.get_iter_at_mark(start_mark)
|
||||
end_itr = buffer.get_iter_at_mark(end_mark)
|
||||
|
||||
if is_selection:
|
||||
self._proc_move(
|
||||
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._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)
|
||||
|
||||
buffer.move_mark(start_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,
|
||||
buffer, mark_hash, start_mark, end_mark, is_forward: bool
|
||||
):
|
||||
self.buffer_markers[mark_hash]["is_selection"] = False
|
||||
if mark_hash:
|
||||
self.buffer_markers[mark_hash]["is_selection"] = False
|
||||
|
||||
start_itr = buffer.get_iter_at_mark(start_mark)
|
||||
end_itr = buffer.get_iter_at_mark(end_mark)
|
||||
@@ -105,19 +115,28 @@ class MarkerManager(MarkSupportMixin):
|
||||
buffer.move_mark(start_mark, collapse_itr)
|
||||
buffer.move_mark(end_mark, collapse_itr)
|
||||
|
||||
def move_word_snake_case(self, itr: Gtk.TextIter, count: int):
|
||||
def is_word(ch):
|
||||
def move_along_word(self, itr: Gtk.TextIter, count: int):
|
||||
def not_is_word(ch: str):
|
||||
return not is_word(ch)
|
||||
|
||||
def is_word(ch: str):
|
||||
return ch and (ch.isalnum() or ch == "_")
|
||||
|
||||
def step(fwd):
|
||||
def is_special(ch: str):
|
||||
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()
|
||||
|
||||
def peek(fwd):
|
||||
def peek(fwd: bool):
|
||||
if fwd: return itr.get_char()
|
||||
tmp = itr.copy()
|
||||
return tmp.backward_cursor_position() and tmp.get_char()
|
||||
|
||||
def walk(fwd, cond):
|
||||
def walk(fwd, cond: callable):
|
||||
while True:
|
||||
ch = peek(fwd)
|
||||
if not cond(ch): break
|
||||
@@ -126,23 +145,22 @@ class MarkerManager(MarkSupportMixin):
|
||||
return True
|
||||
|
||||
fwd = count > 0
|
||||
for _ in range( abs(count) ):
|
||||
ch = peek(fwd)
|
||||
|
||||
for _ in range(abs(count)):
|
||||
ch = itr.get_char() if fwd else peek(False)
|
||||
|
||||
if is_word(ch):
|
||||
# inside word
|
||||
if is_special(ch) or is_punct(ch):
|
||||
step(fwd)
|
||||
elif is_word(ch):
|
||||
if not walk(fwd, is_word): return
|
||||
else:
|
||||
# in separators -> skip them, then the word
|
||||
if not walk(fwd, lambda c: not is_word(c)): return
|
||||
if not walk(fwd, not_is_word): return
|
||||
if not walk(fwd, is_word): return
|
||||
|
||||
def _move_iter(self, buffer, itr_, mode: str, is_forward: bool):
|
||||
if mode == "char":
|
||||
itr_.forward_char() if is_forward else itr_.backward_char()
|
||||
elif mode == "word":
|
||||
self.move_word_snake_case(itr_, 1 if is_forward else -1)
|
||||
self.move_along_word(itr_, 1 if is_forward else -1)
|
||||
elif mode == "line":
|
||||
line = itr_.get_line()
|
||||
offset = itr_.get_line_offset()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.controllers.controller_base import ControllerBase
|
||||
@@ -29,6 +32,8 @@ class SourceViewsController(ControllerBase, list):
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.CreateSourceViewEvent):
|
||||
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):
|
||||
self._remove_file(event)
|
||||
elif isinstance(event, Code_Event_Types.RegisterCommandEvent):
|
||||
@@ -86,7 +91,7 @@ class SourceViewsController(ControllerBase, list):
|
||||
)
|
||||
|
||||
def _get_command_system(self):
|
||||
event = Event_Factory.create_event("get_new_command_system")
|
||||
event = Event_Factory.create_event("create_command_system")
|
||||
self.message_to("commands", event)
|
||||
command = event.response
|
||||
|
||||
@@ -103,15 +108,34 @@ class SourceViewsController(ControllerBase, list):
|
||||
|
||||
source_view.set_buffer(event.next_file.buffer)
|
||||
|
||||
def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT):
|
||||
source_view: SourceView = SourceView(state)
|
||||
source_view.command = self._get_command_system()
|
||||
source_view.command.set_data(source_view)
|
||||
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):
|
||||
scrolled_win: Gtk.ScrolledWindow = Gtk.ScrolledWindow()
|
||||
source_view: SourceView = SourceView(state)
|
||||
source_view.command = self._get_command_system()
|
||||
|
||||
scrolled_win.set_hexpand(True)
|
||||
scrolled_win.set_vexpand(True)
|
||||
|
||||
source_view.command.set_data(source_view)
|
||||
self.signal_mapper.connect_signals(source_view)
|
||||
|
||||
self.append(source_view)
|
||||
return source_view
|
||||
scrolled_win.add(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):
|
||||
for source_view in self:
|
||||
|
||||
@@ -59,30 +59,56 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
|
||||
buffer.insert(start_itr, text, -1)
|
||||
|
||||
self.marker_manager.apply_to_marks(buffer, replace_word)
|
||||
|
||||
return True
|
||||
|
||||
def move_cursor(self, source_view, step, count, is_selection, emit):
|
||||
is_forward = count > 0
|
||||
buffer = source_view.get_buffer()
|
||||
def move_cursor(
|
||||
self, source_view, step, count, is_selection,
|
||||
emit = None, ignore_leader: bool = False
|
||||
):
|
||||
is_forward = count > 0
|
||||
buffer = source_view.get_buffer()
|
||||
|
||||
if step in [
|
||||
Gtk.MovementStep.LOGICAL_POSITIONS,
|
||||
Gtk.MovementStep.VISUAL_POSITIONS
|
||||
]:
|
||||
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)
|
||||
start_mark = buffer.get_insert()
|
||||
end_mark = buffer.get_selection_bound()
|
||||
start_itr = buffer.get_iter_at_mark(start_mark)
|
||||
end_itr = buffer.get_iter_at_mark(end_mark)
|
||||
has_selection = not start_itr.equal(end_itr)
|
||||
|
||||
self._signal_cursor_moved(source_view, emit)
|
||||
step_map = {
|
||||
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),
|
||||
}
|
||||
|
||||
return False
|
||||
kind, move_fn = step_map[step]
|
||||
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):
|
||||
char = key_mapper.get_raw_keyname(event).upper()
|
||||
self.is_control = key_mapper.is_control(event)
|
||||
self.is_shift = key_mapper.is_shift(event)
|
||||
self.is_super = key_mapper.is_super(event)
|
||||
|
||||
if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]:
|
||||
self.marker_manager.process_cursor_action(
|
||||
@@ -91,6 +117,9 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
|
||||
)
|
||||
return False
|
||||
|
||||
if self._do_cursor_moved(source_view, char):
|
||||
return True
|
||||
|
||||
return super().key_press_event(source_view, event, key_mapper)
|
||||
|
||||
def button_press_event(self, source_view, event):
|
||||
@@ -99,6 +128,56 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
|
||||
def button_release_event(self, 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):
|
||||
buffer = source_view.get_buffer()
|
||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
|
||||
@@ -145,7 +145,7 @@ class KeyMapper:
|
||||
is_shift, \
|
||||
is_alt = self.get_modkeys_states(eve)
|
||||
|
||||
self.state = NoKeyState
|
||||
self.state = NoKeyState
|
||||
if is_control:
|
||||
self.state = self.state | CtrlKeyState
|
||||
if is_shift:
|
||||
@@ -161,6 +161,10 @@ class KeyMapper:
|
||||
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_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:
|
||||
return Gdk.keyval_name(eve.keyval)
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
#gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
#from gi.repository import Gdk
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
@@ -12,25 +15,41 @@ from gi.repository import Gtk
|
||||
class SourceViewDnDMixin:
|
||||
|
||||
def _set_up_dnd(self):
|
||||
PLAIN_TEXT_TARGET_TYPE = 70
|
||||
URI_TARGET_TYPE = 80
|
||||
text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE)
|
||||
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
||||
targets = [ text_target, uri_target ]
|
||||
URI_TARGET_TYPE = 10
|
||||
PLAIN_TEXT_TARGET_TYPE = 50
|
||||
|
||||
uri_target = Gtk.TargetEntry.new(
|
||||
'text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE
|
||||
)
|
||||
text_target = Gtk.TargetEntry.new(
|
||||
'text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE
|
||||
)
|
||||
targets = Gtk.TargetList.new([ uri_target, text_target ])
|
||||
|
||||
self.drag_dest_set_target_list(targets)
|
||||
|
||||
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||
if info == 70: return
|
||||
def _on_drag_data_received(
|
||||
self, widget, drag_context, x, y, data, info, time
|
||||
):
|
||||
target = data.get_target().name()
|
||||
|
||||
if info == 80:
|
||||
if (info == 10) or (target == "text/uri-list"):
|
||||
uris = data.get_uris()
|
||||
|
||||
if not uris:
|
||||
uris = data.get_text().split("\n")
|
||||
|
||||
self._on_uri_data_received(uris)
|
||||
|
||||
drag_context.finish(True, False, time)
|
||||
|
||||
return
|
||||
elif (info == 50) or (target == "text/plain"):
|
||||
...
|
||||
else:
|
||||
logger.info(f"DnD Dropped File Type: {target}")
|
||||
|
||||
drag_context.finish(False, False, time)
|
||||
|
||||
def _on_uri_data_received(self, uris: list[str]):
|
||||
uris = self.command.filter_out_loaded_files(uris)
|
||||
if not uris: return
|
||||
|
||||
@@ -31,6 +31,7 @@ class SourceBuffer(GtkSource.Buffer):
|
||||
_insert_text,
|
||||
_after_insert_text,
|
||||
_modified_changed,
|
||||
_delete_range,
|
||||
):
|
||||
|
||||
self._handler_ids = [
|
||||
@@ -39,7 +40,8 @@ class SourceBuffer(GtkSource.Buffer):
|
||||
self.connect("mark-set", _mark_set),
|
||||
self.connect("insert-text", _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):
|
||||
@@ -54,6 +56,9 @@ class SourceBuffer(GtkSource.Buffer):
|
||||
def block_modified_changed_signal(self):
|
||||
self.handler_block(self._handler_ids[5])
|
||||
|
||||
def block_delete_range(self):
|
||||
self.handler_block(self._handler_ids[6])
|
||||
|
||||
def unblock_changed_signal(self):
|
||||
self.handler_unblock(self._handler_ids[0])
|
||||
|
||||
@@ -66,6 +71,9 @@ class SourceBuffer(GtkSource.Buffer):
|
||||
def unblock_modified_changed_signal(self):
|
||||
self.handler_unblock(self._handler_ids[5])
|
||||
|
||||
def unblock_delete_range(self):
|
||||
self.handler_block(self._handler_ids[6])
|
||||
|
||||
def clear_signals(self):
|
||||
for handle_id in self._handler_ids:
|
||||
self.disconnect(handle_id)
|
||||
|
||||
@@ -40,7 +40,8 @@ class SourceFile(GtkSource.File):
|
||||
self._mark_set,
|
||||
self._insert_text,
|
||||
self._after_insert_text,
|
||||
self._modified_changed
|
||||
self._modified_changed,
|
||||
self._delete_range
|
||||
)
|
||||
|
||||
def _changed(self, buffer: SourceBuffer):
|
||||
@@ -60,7 +61,20 @@ class SourceFile(GtkSource.File):
|
||||
location: Gtk.TextIter,
|
||||
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(
|
||||
self,
|
||||
@@ -99,7 +113,19 @@ class SourceFile(GtkSource.File):
|
||||
def _modified_changed(self, buffer: SourceBuffer):
|
||||
event = Event_Factory.create_event(
|
||||
"modified_changed",
|
||||
file = self, buffer = buffer
|
||||
file = self,
|
||||
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)
|
||||
@@ -155,9 +181,10 @@ class SourceFile(GtkSource.File):
|
||||
self.buffer.unblock_modified_changed_signal()
|
||||
|
||||
def is_externally_modified(self) -> bool:
|
||||
stat = os.stat(self.fpath)
|
||||
current = (stat.st_mtime_ns, stat.st_size)
|
||||
if self.fname == "buffer": return
|
||||
|
||||
stat = os.stat(self.fpath)
|
||||
current = (stat.st_mtime_ns, stat.st_size)
|
||||
is_modified = \
|
||||
hasattr(self, "last_state") and not current == self.last_state
|
||||
|
||||
@@ -169,7 +196,12 @@ class SourceFile(GtkSource.File):
|
||||
loaded, contents, etag_out = gfile.load_contents()
|
||||
if not loaded: raise Exception("File couldn't be loaded...'")
|
||||
|
||||
text = contents.decode("UTF-8")
|
||||
# Note:
|
||||
# "strict" (default) -> raises an error on invalid bytes
|
||||
# "ignore" -> skips invalid bytes entirely
|
||||
# "replace" -> replaces invalid bytes with <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)
|
||||
content_type = info.get_content_type()
|
||||
self.ftype = Gio.content_type_get_mime_type(content_type) \
|
||||
@@ -177,6 +209,7 @@ class SourceFile(GtkSource.File):
|
||||
.replace("text/", "") \
|
||||
.replace("x-", "")
|
||||
|
||||
del contents
|
||||
self.set_path(gfile)
|
||||
logger.debug(f"File content type: {self.ftype}")
|
||||
self._load_data(text)
|
||||
|
||||
@@ -19,10 +19,10 @@ class SourceView(GtkSource.View, SourceViewDnDMixin):
|
||||
def __init__(self, state: SourceViewStates = SourceViewStates.INSERT):
|
||||
super(SourceView, self).__init__()
|
||||
|
||||
self.state = state
|
||||
self.state = state
|
||||
|
||||
self.sibling_right = None
|
||||
self.sibling_left = None
|
||||
self.sibling_right = None
|
||||
self.sibling_left = None
|
||||
|
||||
self._setup_styles()
|
||||
self._setup_signals()
|
||||
|
||||
@@ -28,8 +28,8 @@ class VteWidget(Vte.Terminal):
|
||||
def __init__(self):
|
||||
super(VteWidget, self).__init__()
|
||||
|
||||
self.cd_cmd_prefix = ("cd".encode(), "cd ".encode())
|
||||
self.dont_process = False
|
||||
self.cd_cmd_prefix: tuple = ("cd".encode(), "cd ".encode())
|
||||
self.dont_process: bool = False
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
@@ -48,9 +48,17 @@ class VteWidget(Vte.Terminal):
|
||||
self.set_hexpand(True)
|
||||
self.set_enable_sixel(True)
|
||||
self.set_cursor_shape( Vte.CursorShape.IBEAM )
|
||||
self.set_audible_bell(False)
|
||||
self.set_scroll_on_output(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("commit", self._commit)
|
||||
self.connect("commit", self._handle_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):
|
||||
event_system.subscribe("update_term_path", self.update_term_path)
|
||||
@@ -59,35 +67,87 @@ class VteWidget(Vte.Terminal):
|
||||
...
|
||||
|
||||
def _do_session_spawn(self):
|
||||
env = [
|
||||
"DISPLAY=:0",
|
||||
"LC_ALL=C",
|
||||
"TERM='xterm-256color'",
|
||||
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 = os.environ.copy()
|
||||
existing_pc = env_dict.get("PROMPT_COMMAND", "")
|
||||
# Note: Needed for 'current-directory-uri-changed' to work.
|
||||
# Make sure user .bashrc doesn't affect it...
|
||||
osc7 = 'printf "\\033]7;file://%s%s\\007" "$PWD"'
|
||||
|
||||
self.spawn_sync(
|
||||
env_dict.update({
|
||||
"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,
|
||||
settings_manager.path_manager.get_home_path(),
|
||||
["/bin/bash"],
|
||||
env,
|
||||
GLib.SpawnFlags.DEFAULT,
|
||||
None, None,
|
||||
None, None, -1, None, None,
|
||||
)
|
||||
|
||||
startup_cmds = [
|
||||
]
|
||||
|
||||
self.set_scrollback_lines(15000)
|
||||
for i in startup_cmds:
|
||||
self.run_command(i)
|
||||
|
||||
def _commit(self, terminal, text, size):
|
||||
def _handle_destroy(self, terminal):
|
||||
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:
|
||||
self.dont_process = False
|
||||
return
|
||||
@@ -127,4 +187,4 @@ class VteWidget(Vte.Terminal):
|
||||
self.run_command(cmd)
|
||||
|
||||
def run_command(self, cmd: str):
|
||||
self.feed_child_binary(bytes(cmd, 'utf8'))
|
||||
self.feed_child_binary(bytes(cmd, 'utf8'))
|
||||
|
||||
50
src/libs/command_system.py
Normal file
50
src/libs/command_system.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# 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)
|
||||
@@ -6,6 +6,9 @@
|
||||
from .code_event import CodeEvent
|
||||
from .toggle_plugins_ui_event import TogglePluginsUiEvent
|
||||
from .create_source_view_event import CreateSourceViewEvent
|
||||
from .created_source_view_event import CreatedSourceViewEvent
|
||||
from .remove_source_view_event import RemoveSourceViewEvent
|
||||
from .removed_source_view_event import RemovedSourceViewEvent
|
||||
from .register_completer_event import RegisterCompleterEvent
|
||||
from .unregister_completer_event import UnregisterCompleterEvent
|
||||
from .register_provider_event import RegisterProviderEvent
|
||||
@@ -20,11 +23,13 @@ from .filter_out_loaded_files_event import FilterOutLoadedFilesEvent
|
||||
from .get_active_view_event import GetActiveViewEvent
|
||||
from .get_source_views_event import GetSourceViewsEvent
|
||||
|
||||
from .get_new_command_system_event import GetNewCommandSystemEvent
|
||||
from .create_command_system_event import CreateCommandSystemEvent
|
||||
from .request_completion_event import RequestCompletionEvent
|
||||
from .cursor_moved_event import CursorMovedEvent
|
||||
from .delete_range_event import DeleteRangeEvent
|
||||
from .modified_changed_event import ModifiedChangedEvent
|
||||
from .text_changed_event import TextChangedEvent
|
||||
from .text_insert_event import TextInsertEvent
|
||||
from .text_inserted_event import TextInsertedEvent
|
||||
from .focused_view_event import FocusedViewEvent
|
||||
from .set_active_file_event import SetActiveFileEvent
|
||||
|
||||
@@ -9,5 +9,5 @@ from .code_event import CodeEvent
|
||||
|
||||
|
||||
@dataclass
|
||||
class GetNewCommandSystemEvent(CodeEvent):
|
||||
class CreateCommandSystemEvent(CodeEvent):
|
||||
...
|
||||
14
src/libs/dto/code/events/created_source_view_event.py
Normal file
14
src/libs/dto/code/events/created_source_view_event.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
from libs.dto.states.source_view_states import SourceViewStates
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class CreatedSourceViewEvent(CodeEvent):
|
||||
...
|
||||
19
src/libs/dto/code/events/delete_range_event.py
Normal file
19
src/libs/dto/code/events/delete_range_event.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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
|
||||
14
src/libs/dto/code/events/remove_source_view_event.py
Normal file
14
src/libs/dto/code/events/remove_source_view_event.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
from libs.dto.states.source_view_states import SourceViewStates
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class RemoveSourceViewEvent(CodeEvent):
|
||||
...
|
||||
14
src/libs/dto/code/events/removed_source_view_event.py
Normal file
14
src/libs/dto/code/events/removed_source_view_event.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
from libs.dto.states.source_view_states import SourceViewStates
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class RemovedSourceViewEvent(CodeEvent):
|
||||
...
|
||||
20
src/libs/dto/code/events/text_insert_event.py
Normal file
20
src/libs/dto/code/events/text_insert_event.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
@@ -1,7 +1,11 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
@@ -10,4 +14,6 @@ from .code_event import CodeEvent
|
||||
|
||||
@dataclass
|
||||
class UnregisterProviderEvent(CodeEvent):
|
||||
provider_name: str = ""
|
||||
provider_name: str = ""
|
||||
provider: GtkSource.CompletionProvider = None
|
||||
language_ids: list = field(default_factory=lambda: [])
|
||||
|
||||
@@ -13,9 +13,11 @@ from .requests import Requests
|
||||
class Manifest:
|
||||
name: str = ""
|
||||
author: str = ""
|
||||
credit: str = ""
|
||||
description: str = ""
|
||||
version: str = "0.0.1"
|
||||
support: str = "support@mail.com"
|
||||
credit: str = ""
|
||||
copyright: str = "GPLv2"
|
||||
pre_launch: bool = False
|
||||
autoload: bool = True
|
||||
requests: Requests = field(default_factory = lambda: Requests())
|
||||
|
||||
@@ -56,7 +56,7 @@ class ManifestManager:
|
||||
|
||||
if not manifest.autoload:
|
||||
self.manual_launch_manifests.append(manifest_meta)
|
||||
return
|
||||
return manifest_meta
|
||||
|
||||
if manifest.pre_launch:
|
||||
self.pre_launch_manifests.append(manifest_meta)
|
||||
|
||||
@@ -48,13 +48,13 @@ class PluginReloadMixin:
|
||||
|
||||
def remove_plugin(self, file: str) -> None:
|
||||
logger.info(f"Removing plugin: {file.get_uri()}")
|
||||
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)
|
||||
self.plugins_ui.remove_row(manifest_meta)
|
||||
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 manifest_meta in self._manifest_manager.pre_launch_manifests:
|
||||
self._manifest_manager.pre_launch_manifests.remove(manifest_meta)
|
||||
@@ -63,4 +63,15 @@ class PluginReloadMixin:
|
||||
elif manifest_meta in self._manifest_manager.manual_launch_manifests:
|
||||
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
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
@@ -25,6 +28,7 @@ class PluginsUI(Gtk.Dialog):
|
||||
|
||||
self.set_title("Plugins")
|
||||
self.set_size_request(450, 530)
|
||||
self.set_modal(False)
|
||||
self.set_deletable(False)
|
||||
self.set_skip_pager_hint(True)
|
||||
self.set_skip_taskbar_hint(True)
|
||||
@@ -35,9 +39,13 @@ class PluginsUI(Gtk.Dialog):
|
||||
|
||||
window = widget_registery.get_object("main-window")
|
||||
self.set_transient_for(window)
|
||||
self.set_destroy_with_parent(True)
|
||||
|
||||
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
|
||||
|
||||
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):
|
||||
...
|
||||
@@ -59,6 +67,19 @@ class PluginsUI(Gtk.Dialog):
|
||||
|
||||
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):
|
||||
box = Gtk.Box()
|
||||
plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name)
|
||||
@@ -76,6 +97,7 @@ class PluginsUI(Gtk.Dialog):
|
||||
|
||||
toggle_bttn.toggle_id = \
|
||||
toggle_bttn.connect("toggled", callback, manifest_meta)
|
||||
box.toggle_bttn = toggle_bttn
|
||||
|
||||
box.add(plugin_lbl)
|
||||
box.add(author_lbl)
|
||||
@@ -96,5 +118,5 @@ class PluginsUI(Gtk.Dialog):
|
||||
toggle_bttn.disconnect(toggle_bttn.toggle_id)
|
||||
|
||||
self.list_box.remove(row)
|
||||
box.destroy()
|
||||
child.destroy()
|
||||
break
|
||||
|
||||
@@ -42,17 +42,8 @@
|
||||
"save_file_as": {
|
||||
"released": "<Control><Shift>s"
|
||||
},
|
||||
"focus_left_sibling": {
|
||||
"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"
|
||||
"toggle_plugins_ui": {
|
||||
"released": "<Control><Shift>p"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user