19 Commits
Author SHA1 Message Date
itdominator 15e124edee refactor: Moved bridge event handling to use typed DTOs
- Introduce event type and action fields in `BaseEvent`
- Register common events in the event factory
- Default event creation to the generic event type
- Improve WebKit bridge logging and update example messages
2026-10-13 18:29:32 -05:00
itdominator 0559b69e7e Improved WebKit example setup/fallback 2026-10-10 17:43:21 -05:00
itdominator 6ceb929242 Adding WIP files view widget 2026-10-04 00:19:51 -05:00
itdominator ced86aa2b7 Added git ignore rule 2026-10-01 17:30:37 -05:00
itdominator 262af00278 Updated readme with example image 2026-10-01 17:29:12 -05:00
itdominator 62db247dc4 Added quad pane container option 2026-10-01 17:26:44 -05:00
itdominator b86f10b007 Added disabled TLS error handling signal for convenience 2026-06-06 21:45:38 -05:00
itdominator fac9ee028b fix: clean up GTK/WebKit integration and event handling
- fix split pane expansion/orientation setup logic
- remove deferred pane positioning on show events
- load VTE with interactive bash rcfile
- migrate WebKit2 bindings from 4.0 to 4.1
- add icons path accessor to PathManager
- normalize startup IPC file event payloads with FILE| prefix
- alias App_Event_Types and update plugin event references
- guard widget registry against unnamed builder objects
- fix application_dirs home path lookup
- remove unused GLib import from completion controller
- reorder setproctitle import in __main__
2026-05-22 21:16:01 -05:00
itdominator 7ab919f925 Fix line-based editing behavior for cut and duplicate commands
- Cut to temp buffer:
  - Respect selections by expanding to full line boundaries
  - Normalize end iterator to include full line + newline
  - Ensure consistent newline handling for last line
  - Preserve cursor position after delete
  - Prevent line merging when accumulating cut buffer

- Duplicate line:
  - Simplify logic using line-based iter APIs
  - Fix incorrect selection handling and off-by-one issues
  - Ensure full-line duplication for both selection and cursor cases
  - Correct cursor/selection restoration after duplication
2026-04-15 23:05:05 -05:00
itdominator 383db1270e feat(code): adjust split view shortcut and register text insert event
- Change split pane close shortcut from Ctrl+Shift+W to Alt+\
- Register new TextInsertEvent in event system exports
- Prepare groundwork for handling low-level text insertion events in editor pipeline
2026-04-15 01:58:25 -05:00
itdominator 2d4c8e4f31 feat(lsp): improve shutdown lifecycle and response handling
- Add explicit shutdown and exit requests to LSP client
- Rename initialized message to initialized notification
- Handle initialize → send initialized notification
- Handle shutdown → send exit notification

fix(lsp): delay client teardown to allow graceful shutdown
- Close LSP client and response handlers after timeout (GLib)

feat(vte): major terminal widget improvements
- Switch to spawn_async with full environment inheritance
- Improve PROMPT_COMMAND handling for cwd tracking (OSC7)
- Enable scrollback, disable audible bell, enable scroll-on-output
- Add clipboard shortcuts (Ctrl+Shift+C/V), middle-click paste
- Track current directory changes and update UI label
- Add proper signal wiring and cleanup on destroy

feat(ui): improve Plugins dialog behavior
- Make dialog non-modal and centered on parent window
- Auto-hide on focus loss and Ctrl+Shift+P shortcut
- Ensure proper destroy-with-parent behavior

refactor: add type hints and minor cleanup
- Add explicit typing for VteWidget fields
- Improve signal naming consistency
- Minor formatting and cleanup in folding and LSP modules
2026-04-13 00:54:51 -05:00
itdominator a8ad015e05 Refactor LSP manager architecture and event + completion pipeline
- Replace legacy LSPManagerClient + LSPManagerUI with ClientManager and UIManager
- Remove WebsocketClient in favor of unified Websocket implementation
- Move LSP initialization config loading into centralized config module
- Update LSPClient to support dynamic socket, improved init params, and doc version tracking
- Introduce range-based didChange notifications and add implementation + references requests
- Expand LSPClientEvents to support implementation/references and structured range edits
- Simplify websocket response handling and normalize LSP response parsing flow
- Decouple UI from LSP manager core; UI now emits address/port for client creation
- Refactor completion provider pipeline:
  - Split TextChangedEvent into TextInsertedEvent and DeleteRangeEvent
  - Update ProviderResponseCacheBase and controller dispatch paths accordingly
- Improve SourceBuffer and SourceFile event tracking with delete-range support
- Update plugin system:
  - Centralize command/provider registration via helper methods
  - Add LSP commands: definition, references, implementation, toggle UI
- Enhance response handlers to support references and implementation hooks
- Improve Python LSP config (jedi completion, signatures, references enabled)
- Fix minor GTK lifecycle and buffer signal handling issues
- Clean up unused imports, dead code, and outdated JSON server configs
2026-04-11 15:36:59 -05:00
itdominator 0dc21cbb82 refactor: remove split_pane_manager plugin and harden view handling
- Delete deprecated split_pane_manager command plugin and all related commands
- Introduce new split_pane command structure (untracked replacement)
- Add guard in code_minimap to handle missing active view
- Prevent language detection for non-file buffers in command_helpers

This cleans up legacy split pane logic and improves stability for edge cases.
2026-04-04 23:21:03 -05:00
itdominator 890c6cdfcc refactor(editor): remove legacy split-view system and introduce source view lifecycle
* remove toggle_source_view plugin and related command implementations
* drop built-in sibling focus/move commands from core
* simplify EditorsContainer (Paned → Box) and editor initialization
* refactor source view creation to return (scrolled_window, source_view)
* add source view lifecycle events (remove/removed)
* rename GetNewCommandSystemEvent → CreateCommandSystemEvent
* update CommandsController to handle command system creation and cleanup
* ensure command systems are removed with their source views
* improve focus border handling across sibling chains
* clean up imports to use absolute core.* paths
* update keybindings to remove deprecated split navigation commands
2026-04-04 21:40:13 -05:00
itdominator b13d9c2397 refactor(command-system): replace legacy CommandSystem with SourceViewCommandSystem
- Remove CommandSystem and CommandSystemMixin
- Introduce SourceViewCommandSystem and shared libs/command_system
- Update CommandsController to use new command system
- Adjust package exports accordingly

feat(lsp): clean up and normalize LSP configurations

- Fix invalid JSON in Godot LSP config (processId -> null, formatting)
- Remove embedded jedi-language-server config from main Python LSP config
- Add separate jedi-lsp-server-config.json

refactor(markers): rename move_word -> move_along_word for clarity

- Update all usages in MarkerManager

chore: minor formatting and whitespace fixes
2026-04-04 14:21:58 -05:00
itdominator e367e31890 refactor(cursor): centralize movement logic and enhance word navigation
* Extract `_proc_move` to unify cursor/selection handling across markers
* Rework multi-insert cursor flow with `_do_cursor_moved` and improved key routing
* Add `ignore_leader` support for independent leader cursor movement
* Replace `move_word_snake_case` with `move_along_word` (better punctuation/special char handling)
* Add `is_super` modifier support in KeyMapper
2026-03-30 00:45:08 -05:00
itdominator bd277c0214 Refactor LSP manager and file handling
- Refactored LSPClient and LSPClientEvents to use workspace path and initialization options.
- Updated LSPManager to pass workspace path to client creation.
- Modified LSPManagerUI to handle user home paths and workspace configurations.
- Enhanced file handling to manage buffer and directory types.
- Introduced CreatedSourceViewEvent for source view creation event tracking.
- Added better error handling and logging for file operations.

Other minor UI and widget adjustments for improved layout and drag-and-drop handling.
2026-03-29 14:04:56 -05:00
itdominator 12a5e4935e Fix plugin lifecycle, tabs cleanup, and add auto-scroll to tabs
- Fix unload() method naming in nanoesq_temp_buffer plugin
- Wrap tabs_widget in ScrolledWindow/Viewport for proper scrolling
- Add auto-scroll to center when switching/adding tabs
- Return manifest_meta in manifest_manager for manual plugins
- Refactor remove_plugin() to properly clean up all manifest lists
- Fix widget references in plugins_ui for proper removal
2026-03-23 23:08:00 -05:00
itdominator 01ede1ac49 Fixed file_history plugin after breakage from testing a pattern 2026-03-23 21:41:50 -05:00
142 changed files with 3115 additions and 1131 deletions
+1
View File
@@ -1,4 +1,5 @@
database.db database.db
autoimport.db
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
+3
View File
@@ -23,3 +23,6 @@ There are a "\<change_me\>" strings and files that need to be set according to y
For the user_config, after changing names and files, copy all content to their respective destinations. For the user_config, after changing names and files, copy all content to their respective destinations.
The logic follows Debian Dpkg packaging and its placement logic. The logic follows Debian Dpkg packaging and its placement logic.
# Images
![1 Displaying template project and example widgets. ](images/pic1.png)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

+2 -2
View File
@@ -25,7 +25,7 @@ class Plugin(PluginCode):
if len(history) == history_size: if len(history) == history_size:
history.pop(0) history.pop(0)
history.append(event.file) history.append(event.file.fpath)
def load(self): def load(self):
self._manage_signals("register_command") self._manage_signals("register_command")
@@ -60,6 +60,6 @@ class Handler:
view._on_uri_data_received( view._on_uri_data_received(
[ [
history.pop().replace("file://", "") f"file://{history.pop()}"
] ]
) )
@@ -14,16 +14,24 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
class Handler: class Handler:
@staticmethod @staticmethod
def execute( def execute(view: GtkSource.View, *args, **kwargs):
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Cut to Temp Buffer") logger.debug("Command: Cut to Temp Buffer")
clear_temp_cut_buffer_delayed(view) clear_temp_cut_buffer_delayed(view)
buffer = view.get_buffer() buffer = view.get_buffer()
if buffer.get_has_selection():
start_itr, end_itr = buffer.get_selection_bounds()
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()) itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy() start_itr = itr.copy()
@@ -36,9 +44,14 @@ class Handler:
if not hasattr(view, "_cut_buffer"): if not hasattr(view, "_cut_buffer"):
view._cut_buffer = "" view._cut_buffer = ""
line_str = buffer.get_text(start_itr, end_itr, True) text = buffer.get_text(start_itr, end_itr, True)
view._cut_buffer += line_str
if not text.endswith("\n"):
text += "\n"
view._cut_buffer += text
buffer.delete(start_itr, end_itr) buffer.delete(start_itr, end_itr)
buffer.place_cursor(start_itr)
set_temp_cut_buffer_delayed(view) set_temp_cut_buffer_delayed(view)
@@ -23,7 +23,7 @@ class Plugin(PluginCode):
def load(self): def load(self):
self._manage_signals("register_command") self._manage_signals("register_command")
def load(self): def unload(self):
self._manage_signals("unregister_command") self._manage_signals("unregister_command")
def _manage_signals(self, action: str): def _manage_signals(self, action: str):
@@ -0,0 +1,3 @@
"""
Pligin Module
"""
@@ -0,0 +1,3 @@
"""
Pligin Package
"""
@@ -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()
@@ -0,0 +1,67 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
emit_to: callable = None
def execute(
source_view1,
char_str,
modkeys_states
):
logger.debug("Command: Split Pane")
scrolled_win1 = source_view1.get_parent()
container = scrolled_win1.get_parent()
pane = Gtk.Paned()
event = Event_Factory.create_event(
"create_source_view",
state = SourceViewStates.INSERT
)
emit_to("source_views", event)
scrolled_win2, \
source_view2 = event.response
old_sibling_right = None
if source_view1.sibling_right:
old_sibling_right = source_view1.sibling_right
source_view1.sibling_right = source_view2
if old_sibling_right:
old_sibling_right.sibling_left = source_view2
source_view2.sibling_right = old_sibling_right
source_view2.sibling_left = source_view1
pane.set_hexpand(True)
pane.set_vexpand(True)
scrolled_win1.set_hexpand(True)
scrolled_win2.set_vexpand(True)
pane.set_wide_handle(True)
container.remove(scrolled_win1)
pane.pack1( scrolled_win1, True, True )
pane.pack2( scrolled_win2, True, True )
container.add(pane)
if char_str == "|":
pane.set_orientation(Gtk.Orientation.VERTICAL)
elif char_str == "\\":
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
pane.show_all()
source_view2.command.exec("new_file")
source_view2.grab_focus()
@@ -7,6 +7,7 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
@@ -18,5 +19,4 @@ def execute(
): ):
logger.debug("Command: Focus Left Sibling") logger.debug("Command: Focus Left Sibling")
if not view.sibling_left: return if not view.sibling_left: return
view.sibling_left.get_parent().show()
view.sibling_left.grab_focus() view.sibling_left.grab_focus()
@@ -18,5 +18,4 @@ def execute(
): ):
logger.debug("Command: Focus Right Sibling") logger.debug("Command: Focus Right Sibling")
if not view.sibling_right: return if not view.sibling_right: return
view.sibling_right.get_parent().show()
view.sibling_right.grab_focus() view.sibling_right.grab_focus()
@@ -1,5 +1,5 @@
{ {
"name": "Toggle Source View", "name": "Split Pane",
"author": "ITDominator", "author": "ITDominator",
"version": "0.0.1", "version": "0.0.1",
"support": "", "support": "",
+100
View 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_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]: 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_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]: 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_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):
buffer = event.file.buffer buffer = event.buffer
self._clear_temp_delay() self._clear_temp_delay()
self._set_temp_delay(buffer) self._set_temp_delay(buffer)
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
...
def _clear_temp_delay(self): def _clear_temp_delay(self):
if self._temp_timeout_id: if self._temp_timeout_id:
GLib.source_remove(self._temp_timeout_id) GLib.source_remove(self._temp_timeout_id)
@@ -24,6 +24,7 @@ class Plugin(PluginCode):
self.emit_to("files", event) self.emit_to("files", event)
file = event.response file = event.response
if not file: return
if file.ftype == "buffer": return if file.ftype == "buffer": return
file.check_file_on_disk() file.check_file_on_disk()
@@ -1,13 +1,9 @@
# Python imports # Python imports
import threading
from os import path
import json
# Lib imports # Lib imports
import gi
from gi.repository import GLib
# Application imports # Application imports
from ..config import get_lsp_init_config
from ..dto.code.lsp.lsp_messages import get_message_str from ..dto.code.lsp.lsp_messages import get_message_str
from ..dto.code.lsp.lsp_message_structs import \ from ..dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification LSPResponseTypes, ClientRequest, ClientNotification
@@ -19,38 +15,30 @@ class LSPClient(LSPClientWebsocket):
def __init__(self): def __init__(self):
super(LSPClient, self).__init__() super(LSPClient, self).__init__()
# https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers self._socket: str = ""
# initialize-params-slim.json was created off of jedi_language_server one
# self._init_params = settings_manager.get_lsp_init_data()
self._language: str = "" self._language: str = ""
self._init_params: dict = {} self._workspace_path: str = ""
self._event_history: dict[int, str] = {}
try:
_USER_HOME = path.expanduser('~')
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
with open(_LSP_INIT_CONFIG) as file:
data = file.read().replace("{user.home}", _USER_HOME)
self._init_params = json.loads(data)
except Exception as e:
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
self._message_id: int = -1 self._message_id: int = -1
self._socket = None self._event_history: dict[int, str] = {}
self.read_lock = threading.Lock() self._init_params: dict = get_lsp_init_config()
self.write_lock = threading.Lock() self._init_opts: dict[str, str] = {}
self.doc_vers: dict[str, int] = {}
def set_language(self, language: str): def set_language(self, language: str):
self._language = language self._language = language
def set_workspace_path(self, workspace_path: str):
self._workspace_path = workspace_path
def set_init_opts(self, init_opts: dict[str, str]):
self._init_opts = init_opts
def set_socket(self, socket: str): def set_socket(self, socket: str):
self._socket = socket self._socket = socket
def unset_socket(self): def unset_socket(self):
self._socket = None self._socket = ""
def send_notification(self, method: str, params: dict = {}): def send_notification(self, method: str, params: dict = {}):
self._send_message( ClientNotification(method, params) ) self._send_message( ClientNotification(method, params) )
@@ -64,5 +52,5 @@ class LSPClient(LSPClientWebsocket):
if not message_id in self._event_history: return if not message_id in self._event_history: return
return self._event_history[message_id] return self._event_history[message_id]
def handle_lsp_response(self, lsp_response: LSPResponseTypes): def handle_lsp_response(self, lsp_response: LSPResponseTypes | dict):
raise NotImplementedError 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 didsave_notification
from ..dto.code.lsp.lsp_messages import didclose_notification from ..dto.code.lsp.lsp_messages import didclose_notification
from ..dto.code.lsp.lsp_messages import didchange_notification from ..dto.code.lsp.lsp_messages import didchange_notification
from ..dto.code.lsp.lsp_messages import didchange_notification_range
from ..dto.code.lsp.lsp_messages import completion_request from ..dto.code.lsp.lsp_messages import completion_request
from ..dto.code.lsp.lsp_messages import definition_request from ..dto.code.lsp.lsp_messages import definition_request
from ..dto.code.lsp.lsp_messages import implementation_request
from ..dto.code.lsp.lsp_messages import references_request from ..dto.code.lsp.lsp_messages import references_request
from ..dto.code.lsp.lsp_messages import symbols_request from ..dto.code.lsp.lsp_messages import symbols_request
from ..dto.code.lsp.lsp_messages import shutdown_request
from ..dto.code.lsp.lsp_messages import exit_request
class LSPClientEvents: class LSPClientEvents:
def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str): def send_initialize_message(self):
folder_name = os.path.basename(workspace_file) folder_name = os.path.basename(self._workspace_path)
workspace_uri = f"file://{self._workspace_path}"
self._init_params["processId"] = None self._init_params["processId"] = None
self._init_params["rootPath"] = workspace_file self._init_params["rootPath"] = self._workspace_path
self._init_params["rootUri"] = workspace_uri self._init_params["rootUri"] = workspace_uri
self._init_params["workspaceFolders"] = [ self._init_params["workspaceFolders"] = [
{ {
@@ -30,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) self.send_request("initialize", self._init_params)
def send_initialized_message(self): def send_initialized_notification(self):
self.send_notification("initialized") self.send_notification("initialized")
def send_shutdown_request(self):
self.send_request("shutdown")
def send_exit_notification(self):
self.send_notification("exit")
def _lsp_did_open(self, data: dict): def _lsp_did_open(self, data: dict):
method = "textDocument/didOpen" method = "textDocument/didOpen"
params = didopen_notification["params"] params = didopen_notification["params"]
self.doc_vers[ data["uri"] ] = -1
params["textDocument"]["uri"] = data["uri"] params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"] params["textDocument"]["languageId"] = data["language_id"]
@@ -76,24 +88,24 @@ class LSPClientEvents:
self.send_notification( method, params ) self.send_notification( method, params )
# def _lsp_did_change(self, data: dict): def _lsp_did_change_range(self, data: dict):
# method = "textDocument/didChange" method = "textDocument/didChange"
# params = didchange_notification_range["params"] params = didchange_notification_range["params"]
# params["textDocument"]["uri"] = data["uri"] params["textDocument"]["uri"] = data["uri"]
# params["textDocument"]["languageId"] = data["language_id"] params["textDocument"]["languageId"] = data["language_id"]
# params["textDocument"]["version"] = data["version"] params["textDocument"]["version"] = data["version"]
# contentChanges = params["contentChanges"][0] contentChanges = params["contentChanges"][0]
# start = contentChanges["range"]["start"] start = contentChanges["range"]["start"]
# end = contentChanges["range"]["end"] end = contentChanges["range"]["end"]
# contentChanges["text"] = data["text"] contentChanges["text"] = data["text"]
# start["line"] = data["line"] start["line"] = data["line"]
# start["character"] = 0 start["character"] = data["column"]
# end["line"] = data["line"] end["line"] = data["end_line"]
# end["character"] = data["column"] end["character"] = data["end_column"]
# self.send_notification( method, params ) self.send_notification( method, params )
def _lsp_definition(self, data: dict): def _lsp_definition(self, data: dict):
method = "textDocument/definition" method = "textDocument/definition"
@@ -107,13 +119,33 @@ class LSPClientEvents:
self.send_request( method, params ) self.send_request( method, params )
def _lsp_implementation(self, data: dict):
method = "textDocument/implementation"
params = implementation_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_references(self, data: dict):
method = "textDocument/references"
params = references_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_completion(self, data: dict): def _lsp_completion(self, data: dict):
method = "textDocument/completion" method = "textDocument/completion"
params = completion_request["params"] params = completion_request["params"]
params["textDocument"]["uri"] = data["uri"] params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
params["position"]["line"] = data["line"] params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"] params["position"]["character"] = data["column"]
@@ -11,7 +11,7 @@ from ..dto.code.lsp.lsp_message_structs import \
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
from .lsp_client_base import LSPClientBase from .lsp_client_base import LSPClientBase
from .websocket_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}" message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
logger.debug(f"Client: {message_str}") logger.debug(f"Client: {message_str}")
self.ws_client.send(message_str) self.websocket.send(message_str)
def start_client(self): def start_client(self):
self.ws_client = WebsocketClient() self.websocket = Websocket()
self.ws_client.set_socket(self._socket) self.websocket.set_socket(self._socket)
self.ws_client.set_callback(self._monitor_lsp_response) self.websocket.set_callback(self._monitor_lsp_response)
self.ws_client.start_client() self.websocket.start_client()
return self.ws_client return self.websocket
def stop_client(self): def stop_client(self):
if not hasattr(self, "ws_client"): return if not hasattr(self, "websocket"): return
self.ws_client.close_client() self.websocket.close_client()
def _monitor_lsp_response(self, data: dict | None): def _monitor_lsp_response(self, data: dict | None):
if not data: return if not data: return {}
message = get_message_obj(data) message = get_message_obj(data)
keys = message.keys() keys = message.keys()
lsp_response = None lsp_response = data
if "result" in keys: if "result" in keys:
lsp_response = LSPResponseRequest(**get_message_obj(data)) lsp_response = LSPResponseRequest(**get_message_obj(data))
@@ -51,6 +51,7 @@ class LSPClientWebsocket(LSPClientBase):
if "method" in keys: if "method" in keys:
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) ) lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
if 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): def __init__(self):
self.ws = None self.ws = None
self._socket = None self._socket = None
@@ -4,14 +4,15 @@ from concurrent.futures import ThreadPoolExecutor
# Lib imports # Lib imports
# Application 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 from .client.lsp_client import LSPClient
class LSPManagerClient(LSPClientEventsMixin): class ClientManager(ClientManagerEventsMixin):
def __init__(self): def __init__(self):
super(LSPManagerClient, self).__init__() super(ClientManager, self).__init__()
self._cache_refresh_timeout_id: int = None self._cache_refresh_timeout_id: int = None
@@ -22,22 +23,24 @@ class LSPManagerClient(LSPClientEventsMixin):
def create_client( def create_client(
self, self,
lang_id: str = "python", lang_id: str,
workspace_uri: str = "", workspace_path: str,
init_opts: dict = {} init_opts: dict[str, str],
address: str = "127.0.0.1",
port: str = "9999"
) -> LSPClient: ) -> LSPClient:
if lang_id in self.clients: return None if lang_id in self.clients: return None
address = "127.0.0.1" uri = f"ws://{address}:{port}/{lang_id}?workspace={workspace_path}"
port = 9999
uri = f"ws://{address}:{port}/{lang_id}"
client = LSPClient() client = LSPClient()
client.set_language(lang_id)
client.set_socket(uri) client.set_socket(uri)
client.set_language(lang_id)
client.set_workspace_path(workspace_path)
client.set_init_opts(init_opts)
client.start_client() client.start_client()
if not client.ws_client.wait_for_connection(timeout = 5.0): if not client.websocket.wait_for_connection(timeout = get_lsp_connect_timout()):
logger.error(f"Failed to connect to LSP server for {lang_id}") logger.error(f"Failed to connect to LSP server for {lang_id}")
return None return None
@@ -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
)
@@ -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://", "uri": "file://",
"languageId": "python", "languageId": "python",
"version": 1, "version": 1,
"text": ""
}, },
"contentChanges": [ "contentChanges": [
{ {
"text": "",
"range": { "range": {
"start": { "start": {
"line": 1, "line": 1,
@@ -108,9 +108,8 @@ didchange_notification_range = {
"end": { "end": {
"line": 1, "line": 1,
"character": 1, "character": 1,
},
"rangeLength": 0
} }
},
} }
] ]
} }
@@ -125,19 +124,11 @@ completion_request = {
"method": "textDocument/completion", "method": "textDocument/completion",
"params": { "params": {
"textDocument": { "textDocument": {
"uri": "file://", "uri": "file://"
"languageId": "python",
"version": 1,
"text": ""
}, },
"position": { "position": {
"line": 5, "line": 5,
"character": 12, "character": 12
"offset": 0
},
"contet": {
"triggerKind": 3,
"triggerCharacter": ""
} }
} }
} }
@@ -159,6 +150,19 @@ definition_request = {
} }
} }
implementation_request = {
"method": "textDocument/implementation",
"params": {
"textDocument": {
"uri": "file://"
},
"position": {
"line": 5,
"character": 12
}
}
}
references_request = { references_request = {
"method": "textDocument/references", "method": "textDocument/references",
"params": { "params": {
@@ -179,7 +183,6 @@ references_request = {
} }
} }
symbols_request = { symbols_request = {
"method": "textDocument/documentSymbol", "method": "textDocument/documentSymbol",
"params": { "params": {
@@ -191,3 +194,14 @@ symbols_request = {
} }
} }
} }
shutdown_request = {
"method": "shutdown",
"params": None
}
exit_request = {
"method": "exit",
"params": None
}
@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import GLib
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -11,10 +14,11 @@ from .dto.code.events import \
from .dto.code.lsp.lsp_message_structs import \ from .dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, LSPResponseRequest, LSPResponseNotification LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
from .ui_manager import UIManager
from .provider import Provider from .provider import Provider
from .provider_response_cache import ProviderResponseCache from .provider_response_cache import ProviderResponseCache
from .lsp_manager_ui import LSPManagerUI from .client_manager import ClientManager
from .lsp_manager_client import LSPManagerClient
from .response_handlers.response_registry import ResponseRegistry from .response_handlers.response_registry import ResponseRegistry
@@ -31,16 +35,16 @@ class LSPManager(ControllerBase):
def _init(self): def _init(self):
self.provider: Provider = Provider() self.provider: Provider = Provider()
self.response_cache: ProviderResponseCache = ProviderResponseCache() self.response_cache: ProviderResponseCache = ProviderResponseCache()
self.lsp_manager_client: LSPManagerClient = LSPManagerClient() self.client_manager: ClientManager = ClientManager()
self.response_registry: ResponseRegistry = ResponseRegistry() self.response_registry: ResponseRegistry = ResponseRegistry()
def _load_widgets(self): def _load_widgets(self):
self.lsp_manager_ui: LSPManagerUI = LSPManagerUI() self.ui_manager: LSPManagerUI = UIManager()
self.lsp_manager_ui.connect('create-client', self._on_create_client) self.ui_manager.connect('create-client', self._on_create_client)
self.lsp_manager_ui.connect('close-client', self._on_close_client) self.ui_manager.connect('close-client', self._on_close_client)
def _do_bind_mapping(self): def _do_bind_mapping(self):
self.response_cache.set_lsp_manager_client(self.lsp_manager_client) self.response_cache.set_lsp_manager_client(self.client_manager)
self.provider.response_cache = self.response_cache self.provider.response_cache = self.response_cache
self.response_registry.set_event_hub( self.response_registry.set_event_hub(
self.emit, self.emit_to, self.provider self.emit, self.emit_to, self.provider
@@ -49,39 +53,51 @@ class LSPManager(ControllerBase):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.RegisterLspClientEvent): if isinstance(event, Code_Event_Types.RegisterLspClientEvent):
self.response_registry.register_handler(event.lang_id, event.handler) self.response_registry.register_handler(event.lang_id, event.handler)
self.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): elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent):
self.response_registry.unregister_handler(event.lang_id) self.response_registry.unregister_handler(event.lang_id)
self.lsp_manager_ui.remove_client_listing(event.lang_id) self.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) 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: if result:
ui.toggle_client_buttons(show_close = True) ui.toggle_client_buttons(show_close = True)
return result return result
def _on_close_client(self, ui, lang_id: str) -> bool: def _on_close_client(self, ui, lang_id: str) -> bool:
result = self.close_client(lang_id) result = self.close_client(lang_id)
if result: if result:
ui.toggle_client_buttons(show_close = False) ui.toggle_client_buttons(show_close = False)
return result return result
def handle_destroy(self): def handle_destroy(self):
self.lsp_manager_ui.disconnect_by_func(self._on_create_client) self.ui_manager.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_close_client)
def create_client( def create_client(
self, self,
lang_id: str = "python", lang_id: str,
workspace_uri: str = "", workspace_path: str,
init_opts: dict = {} init_opts: dict[str, str],
address: str,
port: str
) -> bool: ) -> bool:
client = self.lsp_manager_client.create_client( client = self.client_manager.create_client(
lang_id, workspace_uri, init_opts lang_id, workspace_path, init_opts, address, port
) )
handler = self.response_registry.get_handler(lang_id) handler = self.response_registry.get_handler(lang_id)
self.lsp_manager_client.active_language_id = lang_id self.client_manager.active_language_id = lang_id
if not client or not handler: if not client or not handler:
logger.error(f"LSP Manager: Either 'client' or 'handler' didn't get created...'") logger.error(f"LSP Manager: Either 'client' or 'handler' didn't get created...'")
@@ -92,28 +108,46 @@ class LSPManager(ControllerBase):
handler.set_response_cache(self.response_cache) handler.set_response_cache(self.response_cache)
client.handle_lsp_response = self.server_response client.handle_lsp_response = self.server_response
client.send_initialize_message(init_opts, "", f"file://{workspace_uri}")
return True return True
def close_client(self, lang_id: str) -> bool: def close_client(self, lang_id: str) -> bool:
self.lsp_manager_client.close_client(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) self.response_registry.close_handler(lang_id)
return False
GLib.timeout_add(5000, _close)
return True return True
def server_response(self, lsp_response: LSPResponseTypes): def server_response(self, lsp_response: LSPResponseTypes | dict):
logger.debug(f"LSP Response: { lsp_response }") logger.debug(f"LSP Response: { lsp_response }")
if isinstance(lsp_response, LSPResponseRequest): if isinstance(lsp_response, dict):
if not self.lsp_manager_client.active_language_id in self.lsp_manager_client.clients: if not self.client_manager.active_language_id in self.client_manager.clients:
logger.debug(f"No LSP client for '{self.lsp_manager_client.active_language_id}', skipping 'server_response'") logger.debug(f"No LSP client for '{self.client_manager.active_language_id}', skipping 'server_response'")
return 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) event = controller.get_event_by_id(lsp_response.id)
handler = self.response_registry.get_handler( 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 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 plugins.plugin_types import PluginCode
from .dto.code import events as lsp_events from .dto.code import events as lsp_events
from .commands import Commands
from .lsp_manager import LSPManager from .lsp_manager import LSPManager
@@ -35,23 +36,11 @@ class Plugin(PluginCode):
window = self.request_ui_element("main-window") 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", self._manage_signals("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)
event = Event_Factory.create_event( self._manage_provider("register_provider")
"register_provider",
provider_name = "LSP Completer",
provider = lsp_manager.provider,
language_ids = []
)
self.emit_to("completion", event)
event = Event_Factory.create_event( event = Event_Factory.create_event(
"create_source_view", "create_source_view",
@@ -59,8 +48,8 @@ class Plugin(PluginCode):
) )
self.emit_to("source_views", event) self.emit_to("source_views", event)
source_view = event.response scrolled_win, source_view = event.response
lsp_manager.lsp_manager_ui.set_source_view(source_view) lsp_manager.ui_manager.set_source_view(scrolled_win, source_view)
def unload(self): def unload(self):
Event_Factory.unregister_events( lsp_events.__dict__.items() ) Event_Factory.unregister_events( lsp_events.__dict__.items() )
@@ -69,56 +58,61 @@ class Plugin(PluginCode):
window = self.request_ui_element("main-window") 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", self._manage_signals("unregister_command")
command_name = "LSP Manager",
command = Handler, 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_mode = "released",
binding = ["<Shift><Control>l", "<Control>g", "<Control>i"] binding = "<Shift><Control>l"
) )
self.emit_to("source_views", event) self.emit_to("source_views", event)
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( event = Event_Factory.create_event(
"unregister_provider", action,
provider_name = "LSP Completer" provider_name = "LSP Completer",
provider = lsp_manager.provider,
language_ids = []
) )
self.emit_to("completion", event) self.emit_to("completion", event)
lsp_manager.handle_destroy()
def run(self): def run(self):
... ...
def generate_plugin_element(self): def generate_plugin_element(self):
... ...
class Handler:
@staticmethod
def execute(
view: any,
*args,
**kwargs
):
logger.debug("Command: LSP Manager")
char_str = args[0]
if char_str in ["g", "i"]:
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
if char_str == "g":
lsp_manager.lsp_manager_client.process_goto_definition(
file.ftype, file.fpath, line, column
)
return
if char_str == "i":
return
lsp_manager.lsp_manager_ui.hide() if lsp_manager.lsp_manager_ui.is_visible() else lsp_manager.lsp_manager_ui.show()
@@ -32,20 +32,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
return "LSP Code Completion" return "LSP Code Completion"
def do_match(self, context): def do_match(self, context):
# Note: If provider is in interactive activation then need to check
# view focus as otherwise non focus views start trying to grab it.
# completion = context.get_property("completion")
# if not completion.get_view().has_focus(): return
iter = self.response_cache.get_iter_correctly(context) iter = self.response_cache.get_iter_correctly(context)
iter.backward_char()
ch = iter.get_char()
# NOTE: Look to re-add or apply supporting logic to use spaces
# As is it slows down the editor in certain contexts...
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
if not (ch in ('_', '.') or ch.isalnum()):
return False
buffer = iter.get_buffer() buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']: if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
@@ -68,6 +55,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
# return GtkSource.CompletionActivation.NONE # return GtkSource.CompletionActivation.NONE
return GtkSource.CompletionActivation.USER_REQUESTED return GtkSource.CompletionActivation.USER_REQUESTED
# return GtkSource.CompletionActivation.INTERACTIVE # return GtkSource.CompletionActivation.INTERACTIVE
# return GtkSource.CompletionActivation.USER_REQUESTED | GtkSource.CompletionActivation.INTERACTIVE
def do_populate(self, context): def do_populate(self, context):
results = self.response_cache.filter_with_context(context) results = self.response_cache.filter_with_context(context)
@@ -33,9 +33,13 @@ class ProviderResponseCache(ProviderResponseCacheBase):
if self.lsp_manager_client: if self.lsp_manager_client:
self.lsp_manager_client.process_file_save(event) self.lsp_manager_client.process_file_save(event)
def process_file_change(self, event): def process_file_text_inserted(self, event):
if self.lsp_manager_client: 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]: def filter(self, word: str) -> list[dict]:
return [] return []
@@ -17,10 +17,18 @@ class DefaultHandler(BaseHandler):
def handle(self, method: str, response, controller): def handle(self, method: str, response, controller):
match method: match method:
case "initialize":
controller.send_initialized_notification()
case "shutdown":
controller.send_exit_notification()
case "textDocument/completion": case "textDocument/completion":
self._handle_completion(response) self._handle_completion(response)
case "textDocument/definition": case "textDocument/definition":
self._handle_definition(response, controller) self._handle_definition(response, controller)
case "textDocument/references":
...
case "textDocument/implementation":
...
case "textDocument/publishDiagnostics": case "textDocument/publishDiagnostics":
self._handle_diagnostics(response) self._handle_diagnostics(response)
@@ -130,5 +138,5 @@ class DefaultHandler(BaseHandler):
view = view, view = view,
provider = self.context._provider provider = self.context._provider
) )
self.emit_to("completion", event)
self.emit_to("completion", event)
@@ -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": { "jedi_completion": {
"enabled": true, "enabled": true,
"fuzzy": true,
"include_params": false,
"include_class_objects": true, "include_class_objects": true,
"include_function_objects": true, "include_function_objects": true
"fuzzy": false },
"jedi_signature_help": {
"enabled": true
},
"jedi_references": {
"enabled": true
}, },
"jedi": { "jedi": {
"root_dir": "file://{workspace.folder}", "root_dir": "file://{workspace.folder}",
@@ -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
}
}
}
} }
+1
View File
@@ -30,6 +30,7 @@ class Plugin(PluginCode):
event = Event_Factory.create_event("get_active_view") event = Event_Factory.create_event("get_active_view")
self.emit_to("source_views", event) self.emit_to("source_views", event)
if not event.response: return
code_minimap.set_smini_view(event.response) code_minimap.set_smini_view(event.response)
def unload(self): def unload(self):
+24 -3
View File
@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import Gtk
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
@@ -25,8 +28,19 @@ class Plugin(PluginCode):
self.register_controller("tabs", self.tabs_controller) self.register_controller("tabs", self.tabs_controller)
code_container.add( self.tabs_controller.tabs_widget ) scrolled_win = Gtk.ScrolledWindow()
code_container.reorder_child(self.tabs_controller.tabs_widget, 0) 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") event = Event_Factory.create_event("get_files")
self.emit_to("files", event) self.emit_to("files", event)
@@ -36,7 +50,14 @@ class Plugin(PluginCode):
def unload(self): def unload(self):
self.unregister_controller("tabs") self.unregister_controller("tabs")
self.tabs_controller.unload_tabs() self.tabs_controller.unload_tabs()
self.tabs_controller.tabs_widget.destroy()
tabs_widget = self.tabs_controller.tabs_widget
viewport = tabs_widget.get_parent()
scrolled_win = viewport.get_parent()
tabs_widget.destroy()
viewport.destroy()
scrolled_win.destroy()
self.tabs_controller.tabs_widget = None self.tabs_controller.tabs_widget = None
self.tabs_controller = None self.tabs_controller = None
+18 -1
View File
@@ -27,7 +27,7 @@ class TabsWidget(Gtk.Notebook):
def _setup_styling(self): def _setup_styling(self):
self.set_scrollable(True) ...
def _setup_signals(self): def _setup_signals(self):
self.connect("page-added", self._page_added) self.connect("page-added", self._page_added)
@@ -67,6 +67,7 @@ class TabsWidget(Gtk.Notebook):
) )
self.emit(event) self.emit(event)
self._scroll_to_center(tab)
def _bind_tab_menu(self, tab, page_widget): def _bind_tab_menu(self, tab, page_widget):
def do_context_menu(tab, eve, page_widget): def do_context_menu(tab, eve, page_widget):
@@ -81,6 +82,21 @@ class TabsWidget(Gtk.Notebook):
page_widget page_widget
) )
def _scroll_to_center(self, tab):
scrolled_win = self.get_parent().get_parent()
alloc = tab.get_allocation()
tab_x = alloc.x
tab_width = alloc.width
view_width = scrolled_win.get_allocated_width()
target = tab_x + tab_width / 2 - view_width / 2
adj = scrolled_win.get_hadjustment()
lower = adj.get_lower()
upper = adj.get_upper()
page_size = adj.get_page_size()
target = max(lower, min(target, upper - page_size))
adj.set_value(target)
def create_menu(self, page_widget) -> Gtk.Menu: def create_menu(self, page_widget) -> Gtk.Menu:
context_menu = Gtk.Menu() context_menu = Gtk.Menu()
close_submenu = Gtk.Menu() close_submenu = Gtk.Menu()
@@ -130,6 +146,7 @@ class TabsWidget(Gtk.Notebook):
self.page_num(page_widget) self.page_num(page_widget)
) )
self.handler_unblock(self.switch_page_id) self.handler_unblock(self.switch_page_id)
self._scroll_to_center(tab)
break break
+1 -1
View File
@@ -4,12 +4,12 @@
import argparse import argparse
import faulthandler import faulthandler
import traceback import traceback
from setproctitle import setproctitle
import tracemalloc import tracemalloc
tracemalloc.start() tracemalloc.start()
# Lib imports # Lib imports
from setproctitle import setproctitle
# Application imports # Application imports
from __builtins__ import * from __builtins__ import *
+22 -7
View File
@@ -6,7 +6,9 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.webkit.webkit_ui import WebkitUI from .quad.quad_container import QuadContainer
from core.widgets.webkit.webkit_ui import WebkitUI
from core.widgets.files.files_base import FilesBase
@@ -28,6 +30,7 @@ class CenterContainer(Gtk.Box):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
self.set_hexpand(True) self.set_hexpand(True)
self.set_vexpand(True) self.set_vexpand(True)
self.set_size_request(320, -1)
def _setup_signals(self): def _setup_signals(self):
self.connect("show", self._handle_show) self.connect("show", self._handle_show)
@@ -42,20 +45,32 @@ class CenterContainer(Gtk.Box):
def _load_widgets(self): def _load_widgets(self):
widget_registery.expose_object("center-container", self) widget_registery.expose_object("center-container", self)
glade_box = widget_registery.get_object("glade_box") quad_container = QuadContainer()
button = Gtk.Button(label = "Click Me!") files_base = FilesBase()
webkit_ui = WebkitUI() webkit_ui = WebkitUI()
webkit_ui.load_context_base_path() button = Gtk.Button(label = "Click Me!")
glade_box = widget_registery.get_object("glade_box")
files_widget = files_base.create_tabs_view()
button.connect("clicked", self._hello_world) button.connect("clicked", self._hello_world)
webkit_ui.load_context_base_path()
files_widget.create_new_tab("test")
files_widget.create_new_tab("test2")
files_widget.create_new_tab("test3")
files_widget.create_new_tab("test4")
files_widget.create_new_tab("test5")
button.show() button.show()
glade_box.show() glade_box.show()
self.add(button) quad_container.top_container.pack1(button, True, True)
self.add(glade_box) quad_container.top_container.pack2(glade_box, True, True)
self.add(webkit_ui) quad_container.bottom_container.pack1(webkit_ui, True, True)
quad_container.bottom_container.pack2(files_widget, True, True)
self.add(quad_container)
def _hello_world(self, widget = None, eve = None): def _hello_world(self, widget = None, eve = None):
logger.debug("Hello, World!") logger.debug("Hello, World!")
+4 -19
View File
@@ -6,10 +6,6 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ...widgets.code.code_base import CodeBase
from ...widgets.separator_widget import Separator
from .editors_container import EditorsContainer from .editors_container import EditorsContainer
@@ -27,6 +23,9 @@ class CodeContainer(Gtk.Box):
def _setup_styling(self): def _setup_styling(self):
self.ctx = self.get_style_context()
self.ctx.add_class("code-container")
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
def _setup_signals(self): def _setup_signals(self):
@@ -37,18 +36,4 @@ class CodeContainer(Gtk.Box):
def _load_widgets(self): def _load_widgets(self):
widget_registery.expose_object("code-container", self) widget_registery.expose_object("code-container", self)
self.add( EditorsContainer() )
code_base = CodeBase()
self.add( self._create_editor_widget(code_base) )
def _create_editor_widget(self, code_base: CodeBase):
editors_container = Gtk.Box()
widget_registery.expose_object("editors-container", editors_container)
editors_container.add( Separator("separator_left") )
editors_container.add( EditorsContainer(code_base) )
editors_container.add( Separator("separator_right") )
return editors_container
+15 -26
View File
@@ -4,18 +4,17 @@
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
#from gi.repository import GLib
# Application imports # Application imports
from core.widgets.code.code_base import CodeBase
from core.widgets.separator_widget import Separator
class EditorsContainer(Gtk.Paned): class EditorsContainer(Gtk.Box):
def __init__(self, code_base: any): def __init__(self):
super(EditorsContainer, self).__init__() super(EditorsContainer, self).__init__()
self.code_base = code_base
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
@@ -24,11 +23,11 @@ class EditorsContainer(Gtk.Paned):
def _setup_styling(self): def _setup_styling(self):
self.ctx = self.get_style_context() 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_hexpand(True)
self.set_vexpand(True) self.set_vexpand(True)
self.set_wide_handle(True) self.set_size_request(320, -1)
def _setup_signals(self): def _setup_signals(self):
self.connect("map", self._init_map) self.connect("map", self._init_map)
@@ -37,26 +36,16 @@ class EditorsContainer(Gtk.Paned):
... ...
def _load_widgets(self): def _load_widgets(self):
self.scrolled_win1, \ box = Gtk.Box()
self.scrolled_win2 = self._create_views() 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 ) box.add( scrolled_win )
self.pack2( self.scrolled_win2, True, True ) self.add( Separator("separator_left") )
self.add( box )
def _create_views(self): self.add( Separator("separator_right") )
scrolled_win1 = Gtk.ScrolledWindow()
scrolled_win2 = Gtk.ScrolledWindow()
source_view1 = self.code_base.create_source_view()
source_view2 = self.code_base.create_source_view()
source_view1.sibling_right = source_view2
source_view2.sibling_left = source_view1
scrolled_win1.add( source_view1 )
scrolled_win2.add( source_view2 )
return scrolled_win1, scrolled_win2
def _init_map(self, view): def _init_map(self, view):
self.disconnect_by_func( self._init_map ) self.disconnect_by_func( self._init_map )
+1 -1
View File
@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator
+3 -3
View File
@@ -6,9 +6,9 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator
from ..widgets.controls.open_files_button import OpenFilesButton from core.widgets.controls.open_files_button import OpenFilesButton
from ..widgets.controls.transparency_scale import TransparencyScale from core.widgets.controls.transparency_scale import TransparencyScale
+2 -1
View File
@@ -6,7 +6,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator
from .code.code_container import CodeContainer from .code.code_container import CodeContainer
+3
View File
@@ -0,0 +1,3 @@
"""
Containers Package
"""
@@ -0,0 +1,44 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class BottomContainer(Gtk.Paned):
def __init__(self):
super(BottomContainer, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self.show()
def _setup_styling(self):
self.ctx = self.get_style_context()
self.ctx.add_class("quad-bottom-container")
self.set_hexpand(True)
self.set_vexpand(True)
self.set_wide_handle(True)
self.set_orientation(Gtk.Orientation.HORIZONTAL)
def _setup_signals(self):
self.connect("show", self._handle_show)
def _subscribe_to_events(self):
pass
def _handle_show(self, widget):
self.disconnect_by_func( self._handle_show )
self._load_widgets()
self.show_all()
def _load_widgets(self):
widget_registery.expose_object("quad-bottom-container", self)
@@ -0,0 +1,59 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .top_container import TopContainer
from .bottom_container import BottomContainer
class QuadContainer(Gtk.Paned):
def __init__(self):
super(QuadContainer, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self.show()
def _setup_styling(self):
self.ctx = self.get_style_context()
self.ctx.add_class("quad-container")
self.set_hexpand(True)
self.set_vexpand(True)
self.set_wide_handle(True)
self.set_orientation(Gtk.Orientation.VERTICAL)
def _setup_signals(self):
self.connect("show", self._handle_show)
self.connect("size-allocate", self._size_allocate)
def _subscribe_to_events(self):
pass
def _handle_show(self, widget):
self.disconnect_by_func( self._handle_show )
self._load_widgets()
def _size_allocate(self, widget, allocation):
self.disconnect_by_func( self._size_allocate )
self.top_container.set_position( allocation.width / 2 )
self.bottom_container.set_position( allocation.width / 2 )
self.set_position( allocation.height / 2 )
def _load_widgets(self):
widget_registery.expose_object("quad-container", self)
self.top_container = TopContainer()
self.bottom_container = BottomContainer()
self.pack1(self.top_container, True, True)
self.pack2(self.bottom_container, True, True)
+44
View File
@@ -0,0 +1,44 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class TopContainer(Gtk.Paned):
def __init__(self):
super(TopContainer, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self.show()
def _setup_styling(self):
self.ctx = self.get_style_context()
self.ctx.add_class("quad-top-container")
self.set_hexpand(True)
self.set_vexpand(True)
self.set_wide_handle(True)
self.set_orientation(Gtk.Orientation.HORIZONTAL)
def _setup_signals(self):
self.connect("show", self._handle_show)
def _subscribe_to_events(self):
pass
def _handle_show(self, widget):
self.disconnect_by_func( self._handle_show )
self._load_widgets()
self.show_all()
def _load_widgets(self):
widget_registery.expose_object("quad-top-container", self)
+2 -2
View File
@@ -6,8 +6,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator
from ..widgets.vte_widget import VteWidget from core.widgets.vte_widget import VteWidget
+1 -1
View File
@@ -81,7 +81,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
def _load_files(self): def _load_files(self):
for file in settings_manager.get_starting_files(): for file in settings_manager.get_starting_files():
event_system.emit("post-file-to-ipc", file) event_system.emit("post-file-to-ipc", f"FILE|{file}")
def _tggl_top_main_menubar(self): def _tggl_top_main_menubar(self):
logger.debug("_tggl_top_main_menubar > stub...") logger.debug("_tggl_top_main_menubar > stub...")
@@ -18,16 +18,12 @@ class BaseControllerMixin:
files = [] files = []
for arg in unknownargs + [args.new_tab,]: for arg in unknownargs + [args.new_tab,]:
if os.path.isdir( arg.replace("file://", "") ): if os.path.isfile(arg):
files.append( f"DIR|{arg.replace('file://', '')}" ) files.append(f"{arg}")
continue
# NOTE: If passing line number with file split against : if os.path.isdir(arg):
if os.path.isfile( arg.replace("file://", "").split(":")[0] ): message = f"DIR|{arg}"
files.append( f"FILE|{arg.replace('file://', '')}" ) ipc_server.send_ipc_message(message)
continue
logger.info(f"Not a File: {arg}")
if not files: return if not files: return
+17 -5
View File
@@ -1,9 +1,11 @@
# Python imports # Python imports
import base64 import base64
import json
# Lib imports # Lib imports
# Application imports # Application imports
from libs.event_factory import Event_Factory, App_Event_Types
@@ -21,8 +23,21 @@ class BridgeController:
event_system.subscribe("handle-bridge-event", self.handle_bridge_event) event_system.subscribe("handle-bridge-event", self.handle_bridge_event)
def handle_bridge_event(self, event): def handle_bridge_event(self, event_str: str):
event = Event_Factory.create_event( **json.loads(event_str) )
if isinstance(event, App_Event_Types.DisplayMessageEvent):
match event.topic: match event.topic:
case "alert":
message = base64.b64decode( event.message.encode() ).decode("utf-8")
logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {message}")
case "error":
message = base64.b64decode( event.message.encode() ).decode("utf-8")
logger.info(message)
case _:
...
elif isinstance(event, App_Event_Types.GenericEvent):
match event.action:
case "save": case "save":
event_system.emit(f"handle-file-event-{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "close": case "close":
@@ -33,9 +48,6 @@ class BridgeController:
event_system.emit(f"handle-file-event-{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "alert": case "alert":
content = base64.b64decode( event.content.encode() ).decode("utf-8") content = base64.b64decode( event.content.encode() ).decode("utf-8")
logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {content}") logger.info(f"\nEvent Action: {event.action}\nEvent Content: {content}")
case "error":
content = base64.b64decode( event.content.encode() ).decode("utf-8")
logger.info(content)
case _: case _:
... ...
+2 -2
View File
@@ -34,7 +34,6 @@ class CodeBase:
completion_controller = CompletionController() completion_controller = CompletionController()
source_views_controller = SourceViewsController() source_views_controller = SourceViewsController()
# self.controller_manager.register_controller("base", self)
self.controller_manager.register_controller("files", files_controller) self.controller_manager.register_controller("files", files_controller)
self.controller_manager.register_controller("commands", commands_controller) self.controller_manager.register_controller("commands", commands_controller)
self.controller_manager.register_controller("completion", completion_controller) self.controller_manager.register_controller("completion", completion_controller)
@@ -43,12 +42,13 @@ class CodeBase:
self.controller_manager.register_controller("widgets", widget_registery) self.controller_manager.register_controller("widgets", widget_registery)
def create_source_view(self): def create_source_view(self):
scrolled_win, \
source_view = self.controller_manager["source_views"].create_source_view() source_view = self.controller_manager["source_views"].create_source_view()
self.controller_manager["completion"].register_completer( self.controller_manager["completion"].register_completer(
source_view.get_completion() source_view.get_completion()
) )
return source_view return scrolled_win, source_view
def first_map_load(self): def first_map_load(self):
self.controller_manager["source_views"].first_map_load() self.controller_manager["source_views"].first_map_load()
@@ -2,4 +2,4 @@
Code Command System Package 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): def set_language_and_style(view, file):
language = None
if not file.fname == "buffer":
language = view.language_manager.guess_language(file.fname, None) language = view.language_manager.guess_language(file.fname, None)
file.buffer.set_language(language) file.buffer.set_language(language)
file.buffer.set_style_scheme(view.syntax_theme) file.buffer.set_style_scheme(view.syntax_theme)
@@ -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( def execute(view: GtkSource.View, *args, **kwargs):
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Duplicate Line") logger.debug("Command: Duplicate Line")
buffer = view.get_buffer() buffer = view.get_buffer()
if not buffer.get_has_selection(): if buffer.get_has_selection():
had_selection = False 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:
itr = buffer.get_iter_at_mark(buffer.get_insert()) itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy() start_line = end_line = itr.get_line()
end_itr = itr.copy() col = itr.get_line_offset()
start_line = itr.get_line() + 1
start_char = 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
start_itr.backward_visible_line() start_itr = buffer.get_iter_at_line(start_line)
start_itr.forward_line() end_itr = buffer.get_iter_at_line(end_line)
end_itr.forward_line()
end_itr.backward_char()
line_str = buffer.get_slice(start_itr, end_itr, True) if not end_itr.ends_line():
end_itr.forward_to_line_end()
if not end_itr.is_end():
end_itr.forward_char() end_itr.forward_char()
buffer.insert(end_itr, f"{line_str}\n", -1)
if not had_selection: text = buffer.get_text(start_itr, end_itr, True)
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) insert_itr = buffer.get_iter_at_line(end_line)
buffer.place_cursor(new_itr)
insert_itr.forward_to_line_end()
if not insert_itr.is_end():
insert_itr.forward_char()
buffer.insert(insert_itr, text)
if buffer.get_has_selection():
new_start = buffer.get_iter_at_line_offset(end_line + 1, scol)
new_end = buffer.get_iter_at_line_offset(end_line + 1 + (end_line - start_line), ecol)
buffer.select_range(new_start, new_end)
else: else:
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) new_start = buffer.get_iter_at_line_offset(end_line + 1, col)
new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char) buffer.place_cursor(new_start)
buffer.select_range(new_itr, new_end_itr)
@@ -20,9 +20,14 @@ def execute(
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()
ctx.add_class("source-view-focused") ctx.add_class("source-view-focused")
if view.sibling_right: lview = view.sibling_left
ctx = view.sibling_right.get_parent().get_style_context() while lview is not None:
elif view.sibling_left: ctx = lview.get_parent().get_style_context()
ctx = view.sibling_left.get_parent().get_style_context()
ctx.remove_class("source-view-focused") ctx.remove_class("source-view-focused")
lview = lview.sibling_left
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 # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
from libs.command_system import CommandSystem
from ..source_view import SourceView from ..source_view import SourceView
from . import commands
class SourceViewCommandSystem(CommandSystem):
def __init__(self):
super(SourceViewCommandSystem, self).__init__(commands)
class CommandSystemMixin:
def toggle_plugins_ui(self): def toggle_plugins_ui(self):
event = Event_Factory.create_event( "toggle_plugins_ui" ) event = Event_Factory.create_event( "toggle_plugins_ui" )
@@ -81,3 +88,11 @@ class CommandSystemMixin:
) )
self.emit_to("completion", event) self.emit_to("completion", event)
def emit(self, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit' from command controller... """
...
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit_to' from command controller... """
...
@@ -36,8 +36,11 @@ class ProviderResponseCacheBase:
def process_file_save(self, event: Code_Event_Types.SavedFileEvent): def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...") raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...")
def process_file_change(self, event: Code_Event_Types.TextChangedEvent): def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_change' not implemented...") 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]: def filter(self, word: str) -> list[dict]:
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...") 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 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): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent): if isinstance(event, Code_Event_Types.CreateCommandSystemEvent):
event.response = self.get_new_command_system() event.response = self.create_command_system()
elif isinstance(event, Code_Event_Types.RemovedSourceViewEvent):
self.remove_command_system(event)
def get_new_command_system(self): def create_command_system(self):
command_system = CommandSystem() command_system = SourceViewCommandSystem()
command_system.emit = self.emit command_system.emit = self.emit
command_system.emit_to = self.emit_to command_system.emit_to = self.emit_to
self.append(command_system) self.append(command_system)
return command_system return command_system
def remove_command_system(self, event: Code_Event_Types.RemovedSourceViewEvent):
self.remove(event.view.command)
@@ -4,7 +4,6 @@
import gi import gi
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
@@ -41,8 +40,10 @@ class CompletionController(ControllerBase):
self.provider_process_file_close(event) self.provider_process_file_close(event)
elif isinstance(event, Code_Event_Types.SavedFileEvent): elif isinstance(event, Code_Event_Types.SavedFileEvent):
self.provider_process_file_save(event) self.provider_process_file_save(event)
elif isinstance(event, Code_Event_Types.TextChangedEvent): elif isinstance(event, Code_Event_Types.TextInsertedEvent):
self.provider_process_file_change(event) 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): elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
self.request_unbound_completion(event) self.request_unbound_completion(event)
@@ -88,9 +89,13 @@ class CompletionController(ControllerBase):
for provider in self._providers.values(): for provider in self._providers.values():
provider.response_cache.process_file_save(event) provider.response_cache.process_file_save(event)
def provider_process_file_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(): 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): def request_unbound_completion(self, event: Code_Event_Types.RequestCompletionEvent):
completer = event.view.get_completion() completer = event.view.get_completion()
@@ -46,14 +46,24 @@ class MarkerManager(MarkSupportMixin):
start_itr = buffer.get_iter_at_mark(start_mark) start_itr = buffer.get_iter_at_mark(start_mark)
end_itr = buffer.get_iter_at_mark(end_mark) end_itr = buffer.get_iter_at_mark(end_mark)
self._proc_move(
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 is_selection:
if mark_hash:
self.buffer_markers[mark_hash]["is_selection"] = True self.buffer_markers[mark_hash]["is_selection"] = True
self._move_iter(buffer, end_itr, mode, is_forward) self._move_iter(buffer, end_itr, mode, is_forward)
buffer.move_mark(end_mark, end_itr) buffer.move_mark(end_mark, end_itr)
self._apply_selection(buffer, start_itr, end_itr) self._apply_selection(buffer, start_itr, end_itr)
continue return
if has_selection: if has_selection:
caret_itr = buffer.get_iter_at_mark(end_mark) caret_itr = buffer.get_iter_at_mark(end_mark)
@@ -67,15 +77,14 @@ class MarkerManager(MarkSupportMixin):
self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward) self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward)
if mode == "word": if mode == "word":
if not can_move: continue if not can_move: return
itr = caret_itr itr = caret_itr
self._move_iter(buffer, itr, mode, is_forward) self._move_iter(buffer, itr, mode, is_forward)
buffer.move_mark(start_mark, itr) buffer.move_mark(start_mark, itr)
buffer.move_mark(end_mark, itr) buffer.move_mark(end_mark, itr)
continue return
# No selection - move both anchor and caret together # No selection - move both anchor and caret together
self._move_iter(buffer, end_itr, mode, is_forward) self._move_iter(buffer, end_itr, mode, is_forward)
@@ -86,6 +95,7 @@ class MarkerManager(MarkSupportMixin):
def collapse_selection(self, def collapse_selection(self,
buffer, mark_hash, start_mark, end_mark, is_forward: bool buffer, mark_hash, start_mark, end_mark, is_forward: bool
): ):
if mark_hash:
self.buffer_markers[mark_hash]["is_selection"] = False self.buffer_markers[mark_hash]["is_selection"] = False
start_itr = buffer.get_iter_at_mark(start_mark) start_itr = buffer.get_iter_at_mark(start_mark)
@@ -105,19 +115,28 @@ class MarkerManager(MarkSupportMixin):
buffer.move_mark(start_mark, collapse_itr) buffer.move_mark(start_mark, collapse_itr)
buffer.move_mark(end_mark, collapse_itr) buffer.move_mark(end_mark, collapse_itr)
def move_word_snake_case(self, itr: Gtk.TextIter, count: int): def move_along_word(self, itr: Gtk.TextIter, count: int):
def is_word(ch): def not_is_word(ch: str):
return not is_word(ch)
def is_word(ch: str):
return ch and (ch.isalnum() or ch == "_") return ch and (ch.isalnum() or ch == "_")
def 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() 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() if fwd: return itr.get_char()
tmp = itr.copy() tmp = itr.copy()
return tmp.backward_cursor_position() and tmp.get_char() return tmp.backward_cursor_position() and tmp.get_char()
def walk(fwd, cond): def walk(fwd, cond: callable):
while True: while True:
ch = peek(fwd) ch = peek(fwd)
if not cond(ch): break if not cond(ch): break
@@ -126,23 +145,22 @@ class MarkerManager(MarkSupportMixin):
return True return True
fwd = count > 0 fwd = count > 0
for _ in range( abs(count) ): for _ in range( abs(count) ):
ch = itr.get_char() if fwd else peek(False) ch = peek(fwd)
if is_word(ch): if is_special(ch) or is_punct(ch):
# inside word step(fwd)
elif is_word(ch):
if not walk(fwd, is_word): return if not walk(fwd, is_word): return
else: else:
# in separators -> skip them, then the word if not walk(fwd, not_is_word): return
if not walk(fwd, lambda c: not is_word(c)): return
if not walk(fwd, is_word): return if not walk(fwd, is_word): return
def _move_iter(self, buffer, itr_, mode: str, is_forward: bool): def _move_iter(self, buffer, itr_, mode: str, is_forward: bool):
if mode == "char": if mode == "char":
itr_.forward_char() if is_forward else itr_.backward_char() itr_.forward_char() if is_forward else itr_.backward_char()
elif mode == "word": elif mode == "word":
self.move_word_snake_case(itr_, 1 if is_forward else -1) self.move_along_word(itr_, 1 if is_forward else -1)
elif mode == "line": elif mode == "line":
line = itr_.get_line() line = itr_.get_line()
offset = itr_.get_line_offset() offset = itr_.get_line_offset()
@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -29,6 +32,8 @@ class SourceViewsController(ControllerBase, list):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.CreateSourceViewEvent): if isinstance(event, Code_Event_Types.CreateSourceViewEvent):
event.response = self.create_source_view(event.state) event.response = self.create_source_view(event.state)
elif isinstance(event, Code_Event_Types.RemoveSourceViewEvent):
self._remove_source_view(event)
elif isinstance(event, Code_Event_Types.RemovedFileEvent): elif isinstance(event, Code_Event_Types.RemovedFileEvent):
self._remove_file(event) self._remove_file(event)
elif isinstance(event, Code_Event_Types.RegisterCommandEvent): elif isinstance(event, Code_Event_Types.RegisterCommandEvent):
@@ -86,7 +91,7 @@ class SourceViewsController(ControllerBase, list):
) )
def _get_command_system(self): 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) self.message_to("commands", event)
command = event.response command = event.response
@@ -103,15 +108,34 @@ class SourceViewsController(ControllerBase, list):
source_view.set_buffer(event.next_file.buffer) source_view.set_buffer(event.next_file.buffer)
def _remove_source_view(self, event: Code_Event_Types.RemovedFileEvent):
event = Event_Factory.create_event("removed_source_view", view = event.view)
self.message(event)
self.remove(event.view)
self.signal_mapper.disconnect_signals(event.view)
def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT): def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT):
scrolled_win: Gtk.ScrolledWindow = Gtk.ScrolledWindow()
source_view: SourceView = SourceView(state) source_view: SourceView = SourceView(state)
source_view.command = self._get_command_system() source_view.command = self._get_command_system()
source_view.command.set_data(source_view)
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.signal_mapper.connect_signals(source_view)
self.append(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): def first_map_load(self):
for source_view in self: for source_view in self:
@@ -59,30 +59,56 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
buffer.insert(start_itr, text, -1) buffer.insert(start_itr, text, -1)
self.marker_manager.apply_to_marks(buffer, replace_word) self.marker_manager.apply_to_marks(buffer, replace_word)
return True return True
def move_cursor(self, source_view, step, count, is_selection, emit): def move_cursor(
self, source_view, step, count, is_selection,
emit = None, ignore_leader: bool = False
):
is_forward = count > 0 is_forward = count > 0
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
if step in [ start_mark = buffer.get_insert()
Gtk.MovementStep.LOGICAL_POSITIONS, end_mark = buffer.get_selection_bound()
Gtk.MovementStep.VISUAL_POSITIONS start_itr = buffer.get_iter_at_mark(start_mark)
]: end_itr = buffer.get_iter_at_mark(end_mark)
self.marker_manager.move_by_char(buffer, is_forward, is_selection) has_selection = not start_itr.equal(end_itr)
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)
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): def key_press_event(self, source_view, event, key_mapper):
char = key_mapper.get_raw_keyname(event).upper() char = key_mapper.get_raw_keyname(event).upper()
self.is_control = key_mapper.is_control(event) self.is_control = key_mapper.is_control(event)
self.is_shift = key_mapper.is_shift(event) self.is_shift = key_mapper.is_shift(event)
self.is_super = key_mapper.is_super(event)
if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]: if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]:
self.marker_manager.process_cursor_action( self.marker_manager.process_cursor_action(
@@ -91,6 +117,9 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
) )
return False return False
if self._do_cursor_moved(source_view, char):
return True
return super().key_press_event(source_view, event, key_mapper) return super().key_press_event(source_view, event, key_mapper)
def button_press_event(self, source_view, event): def button_press_event(self, source_view, event):
@@ -99,6 +128,56 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
def button_release_event(self, source_view, event): def button_release_event(self, source_view, event):
self.marker_manager.button_release_event(source_view, event) self.marker_manager.button_release_event(source_view, event)
def _do_cursor_moved(self, source_view, char: str):
key = char.upper()
if key not in {"LEFT", "RIGHT", "UP", "DOWN"}: return False
direction = {
"LEFT": -1,
"RIGHT": 1,
"UP": -1,
"DOWN": 1,
}[key]
is_horizontal = key in {"LEFT", "RIGHT"}
step = \
Gtk.MovementStep.VISUAL_POSITIONS if is_horizontal else Gtk.MovementStep.DISPLAY_LINES
count = direction
is_selection = self.is_shift
if is_horizontal:
if self.is_control:
step = Gtk.MovementStep.WORDS
if self.is_control and self.is_shift:
is_selection = True
if self.is_super:
return self.move_cursor(
source_view,
step,
count,
is_selection = False,
emit = None,
ignore_leader = True,
)
else:
if self.is_super:
return self.move_cursor(
source_view,
step,
count,
is_selection,
emit = None,
ignore_leader = True,
)
return self.move_cursor(
source_view,
step = step,
count = count,
is_selection = is_selection,
)
def _signal_cursor_moved(self, source_view, emit): def _signal_cursor_moved(self, source_view, emit):
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() ) itr = buffer.get_iter_at_mark( buffer.get_insert() )
+4
View File
@@ -161,6 +161,10 @@ class KeyMapper:
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return modifiers & Gdk.ModifierType.SHIFT_MASK return modifiers & Gdk.ModifierType.SHIFT_MASK
def is_super(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return modifiers & Gdk.ModifierType.SUPER_MASK
def get_raw_keyname(self, eve) -> str: def get_raw_keyname(self, eve) -> str:
return Gdk.keyval_name(eve.keyval) return Gdk.keyval_name(eve.keyval)
@@ -3,7 +3,10 @@
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
#gi.require_version('Gdk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
#from gi.repository import Gdk
# Application imports # Application imports
@@ -12,25 +15,41 @@ from gi.repository import Gtk
class SourceViewDnDMixin: class SourceViewDnDMixin:
def _set_up_dnd(self): def _set_up_dnd(self):
PLAIN_TEXT_TARGET_TYPE = 70 URI_TARGET_TYPE = 10
URI_TARGET_TYPE = 80 PLAIN_TEXT_TARGET_TYPE = 50
text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE)
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) uri_target = Gtk.TargetEntry.new(
targets = [ text_target, uri_target ] 'text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE
)
text_target = Gtk.TargetEntry.new(
'text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE
)
targets = Gtk.TargetList.new([ uri_target, text_target ])
self.drag_dest_set_target_list(targets) self.drag_dest_set_target_list(targets)
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time): def _on_drag_data_received(
if info == 70: return self, widget, drag_context, x, y, data, info, time
):
target = data.get_target().name()
if info == 80: if (info == 10) or (target == "text/uri-list"):
uris = data.get_uris() uris = data.get_uris()
if not uris: if not uris:
uris = data.get_text().split("\n") uris = data.get_text().split("\n")
self._on_uri_data_received(uris) self._on_uri_data_received(uris)
drag_context.finish(True, False, time)
return
elif (info == 50) or (target == "text/plain"):
...
else:
logger.info(f"DnD Dropped File Type: {target}")
drag_context.finish(False, False, time)
def _on_uri_data_received(self, uris: list[str]): def _on_uri_data_received(self, uris: list[str]):
uris = self.command.filter_out_loaded_files(uris) uris = self.command.filter_out_loaded_files(uris)
if not uris: return if not uris: return
+9 -1
View File
@@ -31,6 +31,7 @@ class SourceBuffer(GtkSource.Buffer):
_insert_text, _insert_text,
_after_insert_text, _after_insert_text,
_modified_changed, _modified_changed,
_delete_range,
): ):
self._handler_ids = [ self._handler_ids = [
@@ -39,7 +40,8 @@ class SourceBuffer(GtkSource.Buffer):
self.connect("mark-set", _mark_set), self.connect("mark-set", _mark_set),
self.connect("insert-text", _insert_text), self.connect("insert-text", _insert_text),
self.connect_after("insert-text", _after_insert_text), self.connect_after("insert-text", _after_insert_text),
self.connect("modified-changed", _modified_changed) self.connect("modified-changed", _modified_changed),
self.connect("delete-range", _delete_range)
] ]
def block_changed_signal(self): def block_changed_signal(self):
@@ -54,6 +56,9 @@ class SourceBuffer(GtkSource.Buffer):
def block_modified_changed_signal(self): def block_modified_changed_signal(self):
self.handler_block(self._handler_ids[5]) self.handler_block(self._handler_ids[5])
def block_delete_range(self):
self.handler_block(self._handler_ids[6])
def unblock_changed_signal(self): def unblock_changed_signal(self):
self.handler_unblock(self._handler_ids[0]) self.handler_unblock(self._handler_ids[0])
@@ -66,6 +71,9 @@ class SourceBuffer(GtkSource.Buffer):
def unblock_modified_changed_signal(self): def unblock_modified_changed_signal(self):
self.handler_unblock(self._handler_ids[5]) self.handler_unblock(self._handler_ids[5])
def unblock_delete_range(self):
self.handler_block(self._handler_ids[6])
def clear_signals(self): def clear_signals(self):
for handle_id in self._handler_ids: for handle_id in self._handler_ids:
self.disconnect(handle_id) self.disconnect(handle_id)
+38 -5
View File
@@ -40,7 +40,8 @@ class SourceFile(GtkSource.File):
self._mark_set, self._mark_set,
self._insert_text, self._insert_text,
self._after_insert_text, self._after_insert_text,
self._modified_changed self._modified_changed,
self._delete_range
) )
def _changed(self, buffer: SourceBuffer): def _changed(self, buffer: SourceBuffer):
@@ -60,7 +61,20 @@ class SourceFile(GtkSource.File):
location: Gtk.TextIter, location: Gtk.TextIter,
text: str, length: int text: str, length: int
): ):
... event = Event_Factory.create_event(
"text_insert",
file = self,
buffer = self.buffer,
location = location,
text = text,
length = length
)
# Note: 'idle_add' needed b/c markers don't get thir positions
# updated relative to the initial insert.
# If not used, seg faults galor during multi insert.
# GLib.idle_add(self.emit, event)
self.emit(event)
def _after_insert_text( def _after_insert_text(
self, self,
@@ -99,7 +113,19 @@ class SourceFile(GtkSource.File):
def _modified_changed(self, buffer: SourceBuffer): def _modified_changed(self, buffer: SourceBuffer):
event = Event_Factory.create_event( event = Event_Factory.create_event(
"modified_changed", "modified_changed",
file = self, 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) self.emit(event)
@@ -155,9 +181,10 @@ class SourceFile(GtkSource.File):
self.buffer.unblock_modified_changed_signal() self.buffer.unblock_modified_changed_signal()
def is_externally_modified(self) -> bool: def is_externally_modified(self) -> bool:
if self.fname == "buffer": return
stat = os.stat(self.fpath) stat = os.stat(self.fpath)
current = (stat.st_mtime_ns, stat.st_size) current = (stat.st_mtime_ns, stat.st_size)
is_modified = \ is_modified = \
hasattr(self, "last_state") and not current == self.last_state hasattr(self, "last_state") and not current == self.last_state
@@ -169,7 +196,12 @@ class SourceFile(GtkSource.File):
loaded, contents, etag_out = gfile.load_contents() loaded, contents, etag_out = gfile.load_contents()
if not loaded: raise Exception("File couldn't be loaded...'") if not loaded: raise Exception("File couldn't be loaded...'")
text = contents.decode("UTF-8") # Note:
# "strict" (default) -> raises an error on invalid bytes
# "ignore" -> skips invalid bytes entirely
# "replace" -> replaces invalid bytes with ďż˝
# "backslashreplace" -> uses escape sequences like \xFF
text = contents.decode("UTF-8", errors = "replace")
info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None) info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None)
content_type = info.get_content_type() content_type = info.get_content_type()
self.ftype = Gio.content_type_get_mime_type(content_type) \ self.ftype = Gio.content_type_get_mime_type(content_type) \
@@ -177,6 +209,7 @@ class SourceFile(GtkSource.File):
.replace("text/", "") \ .replace("text/", "") \
.replace("x-", "") .replace("x-", "")
del contents
self.set_path(gfile) self.set_path(gfile)
logger.debug(f"File content type: {self.ftype}") logger.debug(f"File content type: {self.ftype}")
self._load_data(text) self._load_data(text)
+3
View File
@@ -0,0 +1,3 @@
"""
Files Package
"""
@@ -0,0 +1,5 @@
"""
Code Command System Package
"""
from .files_view_command_system import FilesViewCommandSystem
@@ -0,0 +1,16 @@
"""
Code Commands Package
"""
import pkgutil
import importlib
__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
module = importlib.import_module(f"{__name__}.{module_name}")
# globals()[module_name] = module # Add module to package namespace
__all__.append(module_name)
del pkgutil
del importlib
@@ -0,0 +1,19 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def execute(
view: Gtk.Notebook,
*args,
**kwargs
):
logger.debug("Command: Get File Type")
@@ -0,0 +1,20 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def execute(
view: Gtk.Notebook,
*args,
**kwargs
):
logger.debug("Command: New File")
@@ -0,0 +1,19 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def execute(
view: Gtk.Notebook,
*args,
**kwargs
):
logger.debug("Command: Open File(s)")
@@ -0,0 +1,19 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def execute(
view: Gtk.Notebook,
*args,
**kwargs
):
logger.debug("Command: Save File")
@@ -0,0 +1,19 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def execute(
view: Gtk.Notebook,
*args,
**kwargs
):
logger.debug("Command: Save File As")
@@ -0,0 +1,19 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def execute(
view: Gtk.Notebook,
*args,
**kwargs
):
logger.debug("Command: Update Info Bar")
@@ -0,0 +1,32 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Files_Event_Types
from libs.command_system import CommandSystem
from ..tabs_view import TabsView
from . import commands
class FilesViewCommandSystem(CommandSystem):
def __init__(self):
super(FilesViewCommandSystem, self).__init__(commands)
def toggle_plugins_ui(self):
event = Event_Factory.create_event( "toggle_plugins_ui" )
self.emit_to("plugins", event)
def emit(self, event: Files_Event_Types.FilesEvent):
""" Monkey patch 'emit' from command controller... """
...
def emit_to(self, controller: str, event: Files_Event_Types.FilesEvent):
""" Monkey patch 'emit_to' from command controller... """
...
@@ -0,0 +1,3 @@
"""
Code Controllers Package
"""
@@ -0,0 +1,33 @@
# Python imports
# Lib imports
# Application imports
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Files_Event_Types
from ..command_system import FilesViewCommandSystem
class CommandsController(ControllerBase, list):
def __init__(self):
super(CommandsController, self).__init__()
def _controller_message(self, event: Files_Event_Types.FilesEvent):
if isinstance(event, Files_Event_Types.CreateCommandSystemEvent):
event.response = self.create_command_system()
def create_command_system(self):
command_system = FilesViewCommandSystem()
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: Files_Event_Types.RemovedFilesViewEvent):
self.remove(event.view.command)
@@ -0,0 +1,27 @@
# Python imports
# Lib imports
# Application imports
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Files_Event_Types
class FilesSessionController(ControllerBase, list):
def __init__(self):
super(FilesSessionController, self).__init__()
def _controller_message(self, event: Files_Event_Types.FilesEvent):
if isinstance(event, Files_Event_Types.AddNewFileEvent):
...
def create_tabs_view(self) -> TabsView:
tabs_view = TabsView()
self.add(tabs_view)
self.active_view = self.length
return tabs_view
@@ -0,0 +1,70 @@
# Python imports
# Lib imports
# Application imports
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Files_Event_Types
from ..tabs_view import TabsView
from ..tab_dto import TabDTO
class TabViewsController(ControllerBase, list):
def __init__(self):
super(TabViewsController, self).__init__()
self.active_view: int = 0
def _controller_message(self, event: Files_Event_Types.FilesEvent):
if isinstance(event, Files_Event_Types.CreateFilesViewEvent):
...
def _get_command_system(self):
event = Event_Factory.create_event("create_command_system")
self.message_to("commands", event)
command = event.response
del event
return command
def create_tabs_view(self) -> TabsView:
tabs_view = TabsView()
tabs_view.command = self._get_command_system()
self.append(tabs_view)
self.active_view = len(self)
return tabs_view
def set_active_tabs_view(self, i: int):
if i < 0:
i = len(self)
elif i > len(self):
i = 0
self.active_view = i
def get_active_tabs_view(self) -> TabsView:
return self.get(i)
def get_tabs_view(self, i: int = 0) -> TabsView:
if i < 0:
i = len(self)
elif i > len(self):
i = 0
return self.get(i)
def create_new_tab(self, name: str, tabs_view: TabsView = None) -> TabDTO:
if not name:
logger.error("Must give a Tab 'name' to create new tab...")
return
if not tabs_view:
tabs_view = self.get_active_tabs_view()
return tabs_view.create_new_tab(name)
+54
View File
@@ -0,0 +1,54 @@
# Python imports
# Lib imports
# Application imports
from plugins import plugins_controller
from libs.controllers.controller_manager import ControllerManager
from .controllers.files_session_controller import FilesSessionController
from .controllers.commands_controller import CommandsController
from .controllers.tab_views_controller import TabViewsController
class FilesBase:
def __init__(self):
super(FilesBase, self).__init__()
self.controller_manager: ControllerManager = ControllerManager()
self._subscribe_to_events()
self._load_controllers()
def _subscribe_to_events(self):
event_system.subscribe("handle-file", self._load_ipc_file)
event_system.subscribe("handle-files", self._load_ipc_files)
def _load_controllers(self):
files_session_controller = FilesSessionController()
# commands_controller = CommandsController() # Uncomment if not used with 'code' widget
tab_views_controller = TabViewsController()
self.controller_manager.register_controller("files_session", files_session_controller)
# self.controller_manager.register_controller("commands", commands_controller)
self.controller_manager.register_controller("tab_views", tab_views_controller)
# self.controller_manager.register_controller("widgets", widget_registery)
def create_sessioned_tabs_view(self):
return self.controller_manager["files_session"].create_tabs_view()
def create_tabs_view(self):
return self.controller_manager["tab_views"].create_tabs_view()
def _load_ipc_file(self, fpath: str):
active_view = self.controller_manager["tab_views"].get_active_tabs_view()
uris = [ f"file://{fpath}" ]
# active_view._on_uri_data_received(uris)
pass
def _load_ipc_files(self, uris: list):
active_view = self.controller_manager["tab_views"].get_active_tabs_view()
# active_view._on_uri_data_received(uris)
+46
View File
@@ -0,0 +1,46 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class FilesView(Gtk.ScrolledWindow):
def __init__(self):
super(FilesView, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show()
def _setup_styling(self):
...
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
...
def get_icon_grid(self) -> Gtk.IconView:
return self.get_children()[0].get_children()[0]
def bind_icon_grid(self, icon_grid: Gtk.IconView):
self.add(icon_grid)
# viewport = Gtk.Viewport(icon_grid)
# viewport.show()
# self.add(viewport)
+139
View File
@@ -0,0 +1,139 @@
# Python imports
import gc
# 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 GLib
from gi.repository import Gdk
from gi.repository import GdkPixbuf
# Application imports
# from utils.cbindings import gtkmemreaper
class IconGrid(Gtk.IconView):
"""docstring for IconGridWidget"""
def __init__(self):
super(IconGrid, self).__init__()
self._handler_ids = []
self._setup_styling()
self._setup_signals()
self._set_up_dnd()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_pixbuf_column(0)
self.set_text_column(1)
self.set_item_orientation(1)
self.set_selection_mode(3)
self.set_item_width(96)
self.set_item_padding(8)
self.set_margin(12)
self.set_row_spacing(18)
self.set_columns(-1)
self.set_spacing(12)
self.set_column_spacing(18)
def _setup_signals(self):
...
def _setup_additional_signals(self,
grid_icon_single_click,
grid_icon_double_click,
grid_set_selected_items,
grid_on_drag_set,
grid_on_drag_data_received,
grid_on_drag_motion
):
self._handler_ids = [
self.connect("button-release-event", grid_icon_single_click),
self.connect("item-activated", grid_icon_double_click),
self.connect("selection-changed", grid_set_selected_items),
self.connect("drag-data-get", grid_on_drag_set),
self.connect("drag-data-received", grid_on_drag_data_received),
self.connect("drag-motion", grid_on_drag_motion)
]
def _destroy(grid):
self.clear_signals_and_data()
self._handler_ids.append(self.connect("destroy", _destroy))
def _load_widgets(self):
self.clear_and_set_new_store()
def _set_up_dnd(self):
URI_TARGET_TYPE = 80
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
targets = [ uri_target ]
action = Gdk.DragAction.COPY
self.enable_model_drag_dest(targets, action)
self.enable_model_drag_source(0, targets, action)
def get_store(self):
return self.get_model()
def clear_and_set_new_store(self):
self._clear_store()
self.set_model(
Gtk.ListStore(
GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None
)
)
def clear_signals_and_data(self):
self.unset_model_drag_dest()
self.unset_model_drag_source()
for handle_id in self._handler_ids:
self.disconnect(handle_id)
self._handler_ids.clear()
self._clear_store()
def _clear_store(self):
store = self.get_model()
self.set_model(None)
if not store: return
iter = store.get_iter_first()
while iter:
icon = store.get_value(iter, 0)
store.set_value(iter, 0, None)
store.set_value(iter, 1, None)
iter = store.iter_next(iter)
if icon:
logger.debug(f"Reference count for icon is: {icon.__grefcount__}")
icon.run_dispose()
# icon_ptr = int(hash(icon)) # WARNING: not stable across runs
# gtkmemreaper.free_pixbuf(icon_ptr)
del icon
store.clear()
store.run_dispose()
# GLib.idle_add(store.clear)
# GLib.idle_add(store.run_dispose)
# store.run_dispose()
# store_ptr = int(hash(store)) # WARNING: not stable across runs
# gtkmemreaper.free_list_store(store)
del store
gc.collect()
+20
View File
@@ -0,0 +1,20 @@
# Python imports
# Lib imports
# Application imports
from .tab_header import TabHeader
from .files_view import FilesView
from .icon_grid import IconGrid
class TabDTO:
def __init__(self):
super(TabDTO, self).__init__()
self.tab_header: TabHeader = TabHeader()
self.files_view: FilesView = FilesView()
self.icon_grid: IconGrid = IconGrid()
self.files_view.bind_icon_grid(self.icon_grid)
+58
View File
@@ -0,0 +1,58 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class TabHeader(Gtk.Box):
"""docstring for TabHeader"""
def __init__(self):
super(TabHeader, self).__init__()
self._setup_styling()
self._setup_signals()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_orientation(0)
self.set_hexpand(False)
def _setup_signals(self):
...
def _load_widgets(self):
self.label = Gtk.Label()
self.close_bttn = Gtk.Button()
icon = Gtk.Image(stock = Gtk.STOCK_CLOSE)
self.label.set_xalign(0.0)
self.label.set_margin_left(25)
self.label.set_margin_right(25)
self.label.set_hexpand(True)
self.close_bttn.add(icon)
self.add(self.label)
self.add(self.close_bttn)
def bind_close_signal(self, close_method: callable):
self._close_handler_id = self.close_bttn.connect("released", close_method)
def clear_signals_and_data(self):
self.close_bttn.disconnect(self._close_handler_id)
self._close_tab = None
self._close_handler_id = None
for child in self.get_children():
child.unparent()
child.run_dispose()
child.destroy()

Some files were not shown because too many files have changed in this diff Show More