feat(editor): introduce split pane manager plugin and refactor source view system

* address TODO item for split_pane_manager plugin and bound keys
* add split_pane_manager plugin with create/close split view commands
* move sibling focus/move commands from core into plugin system
* remove legacy toggle_source_view plugin
* redesign EditorsContainer to use simpler Box-based layout
* refactor source view API to return (scrolled_window, source_view)
* add source view lifecycle events (create/remove/removed)
* rename GetNewCommandSystemEvent → CreateCommandSystemEvent
* update CommandsController to support command system creation and cleanup
* ensure command systems are removed with their source views
* improve focus border handling across sibling chains
* update telescope integration for new source view structure
* clean up container imports and initialization logic
* remove old keybindings and align with new split pane workflow
This commit is contained in:
2026-04-04 21:33:09 -05:00
parent 5911f37449
commit 69e766dc28
32 changed files with 344 additions and 165 deletions

View File

@@ -2,7 +2,6 @@ ___
### Add
1. Add Godot LSP Client
1. Add Terminal plugin
1. Add Plugin to <Shift\><Ctrl\>| and <Ctrl\>| to split views up, down, left, right
1. Add <Ctrl\>i to **lsp_manager** to list who implements xyz
___

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
# 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 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 = remaining.get_child()
left = closing_view.sibling_left
right = closing_view.sibling_right
if left:
left.sibling_right = right
if right:
right.sibling_left = left
pane.remove(source_view1)
pane.remove(source_view2)
container.remove(pane)
container.add(remaining)
event = Event_Factory.create_event(
"remove_source_view",
view = closing_view
)
emit_to("source_views", event)
remaining_view.grab_focus()

View File

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

View File

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

View File

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

View File

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

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 = "<Shift><Control>w"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_left_sibling",
command = focus_left_sibling,
binding_mode = "released",
binding = "<Control>Page_Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_right_sibling",
command = focus_right_sibling,
binding_mode = "released",
binding = "<Control>Page_Down"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_left_sibling",
command = move_to_left_sibling,
binding_mode = "released",
binding = "<Control><Shift>Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_right_sibling",
command = move_to_right_sibling,
binding_mode = "released",
binding = "<Control><Shift>Down"
)
self.emit_to("source_views", event)
def run(self):
...

View File

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

View File

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

View File

@@ -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()

View File

@@ -52,8 +52,8 @@ class Plugin(PluginCode):
)
self.emit_to("source_views", event)
source_view = event.response
telescope.set_source_view(source_view)
scrolled_win, source_view = event.response
telescope.set_source_view(scrolled_win, source_view)
event = Event_Factory.create_event(
"register_completer",

View File

@@ -99,13 +99,9 @@ class Telescope(Gtk.Dialog):
def unmap_parent_resize_event(self, parent):
parent.disconnect(self.size_allocate_id)
def set_source_view(self, source_view):
scrolled_win = Gtk.ScrolledWindow()
def set_source_view(self, scrolled_win, source_view):
self.source_view = source_view
scrolled_win.add(self.source_view)
self.main_box.pack_end(scrolled_win, True, True, 0)
scrolled_win.show_all()
def _handle_destroy(self, widget):

View File

@@ -6,9 +6,10 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from core.widgets.save_file_dialog import SaveFileDialog
from core.widgets.controls.open_files_button import OpenFilesButton
from .code.code_container import CodeContainer
from ..widgets.save_file_dialog import SaveFileDialog
from ..widgets.controls.open_files_button import OpenFilesButton

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,10 +17,12 @@ class CommandsController(ControllerBase, list):
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent):
event.response = self.get_new_command_system()
if isinstance(event, Code_Event_Types.CreateCommandSystemEvent):
event.response = self.create_command_system()
elif isinstance(event, Code_Event_Types.RemovedSourceViewEvent):
self.remove_command_system(event)
def get_new_command_system(self):
def create_command_system(self):
command_system = SourceViewCommandSystem()
command_system.emit = self.emit
command_system.emit_to = self.emit_to
@@ -28,3 +30,6 @@ class CommandsController(ControllerBase, list):
self.append(command_system)
return command_system
def remove_command_system(self, event: Code_Event_Types.RemovedSourceViewEvent):
self.remove(event.view.command)

View File

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

View File

@@ -7,6 +7,8 @@ from .code_event import CodeEvent
from .toggle_plugins_ui_event import TogglePluginsUiEvent
from .create_source_view_event import CreateSourceViewEvent
from .created_source_view_event import CreatedSourceViewEvent
from .remove_source_view_event import RemoveSourceViewEvent
from .removed_source_view_event import RemovedSourceViewEvent
from .register_completer_event import RegisterCompleterEvent
from .unregister_completer_event import UnregisterCompleterEvent
from .register_provider_event import RegisterProviderEvent
@@ -21,7 +23,7 @@ from .filter_out_loaded_files_event import FilterOutLoadedFilesEvent
from .get_active_view_event import GetActiveViewEvent
from .get_source_views_event import GetSourceViewsEvent
from .get_new_command_system_event import GetNewCommandSystemEvent
from .create_command_system_event import CreateCommandSystemEvent
from .request_completion_event import RequestCompletionEvent
from .cursor_moved_event import CursorMovedEvent
from .modified_changed_event import ModifiedChangedEvent

View File

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

View File

@@ -0,0 +1,14 @@
# Python imports
from dataclasses import dataclass
# Lib imports
# Application imports
from .code_event import CodeEvent
from libs.dto.states.source_view_states import SourceViewStates
@dataclass
class RemoveSourceViewEvent(CodeEvent):
...

View File

@@ -0,0 +1,14 @@
# Python imports
from dataclasses import dataclass
# Lib imports
# Application imports
from .code_event import CodeEvent
from libs.dto.states.source_view_states import SourceViewStates
@dataclass
class RemovedSourceViewEvent(CodeEvent):
...

View File

@@ -44,18 +44,6 @@
},
"toggle_plugins_ui": {
"released": "<Control><Shift>p"
},
"focus_left_sibling": {
"released": "<Control>Page_Up"
},
"focus_right_sibling": {
"released": "<Control>Page_Down"
},
"move_to_left_sibling": {
"released": "<Control><Shift>Up"
},
"move_to_right_sibling": {
"released": "<Control><Shift>Down"
}
}
}