Remove tabs UI from code editor and move to plugin. Enhance plugin system.

- Remove tabs controller, tab widget, and tabs widget files and move to plugin
- Delete plugins/README.txt
- Add register_controller method to controller system for plugin use
- Add error handling for plugin crashes via futures callback
This commit is contained in:
2026-02-26 21:09:00 -06:00
parent 597ac2b06a
commit b724d41f6c
16 changed files with 112 additions and 73 deletions

View File

@@ -1,31 +0,0 @@
### Note
Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' requests data but don't have to if not directly interacted with.
Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use.
### Manifest Example (All are required even if empty.)
```
class Manifest:
name: str = "Example Plugin"
author: str = "John Doe"
version: str = "0.0.1"
support: str = ""
pre_launch: bool = False
requests: {} = {
'pass_ui_objects': ["plugin_control_list"],
'pass_events': True,
'bind_keys': []
}
```
### Requests
```
requests: {} = {
'pass_events': true, # If empty or not present will be ignored.
"pass_ui_objects": [""], # Request reference to a UI component. Will be passed back as array to plugin.
'bind_keys': [f"{name}||send_message:<Control>f"],
f"{name}||do_save:<Control>s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right.
}
```

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
{
"name": "Tabs Bar",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -0,0 +1,32 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .tabs_controller import TabsController
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
tabs_controller = TabsController()
code_container = self.requests_ui_element("code-container")
self.register_controller("tabs", tabs_controller)
code_container.add( tabs_controller.tabs_widget )
code_container.reorder_child(tabs_controller.tabs_widget, 0)
def run(self):
...

View File

@@ -9,10 +9,11 @@ from gi.repository import Gtk
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Code_Event_Types
from ..tabs_widget import TabsWidget
from ..tab_widget import TabWidget
from core.widgets.code.source_view import SourceView
from .tabs_widget import TabsWidget
from .tab_widget import TabWidget
from ..source_view import SourceView

View File

@@ -23,6 +23,8 @@ class TabsWidget(Gtk.Notebook):
self._subscribe_to_events()
self._load_widgets()
self.show()
def _setup_styling(self):
self.set_scrollable(True)

View File

@@ -36,18 +36,15 @@ class CodeContainer(Gtk.Box):
...
def _load_widgets(self):
widget_registery.expose_object("code-container", self)
code_base = CodeBase()
self.add( self._create_tabs_widgets(code_base) )
self.add( self._create_editor_widget(code_base) )
def _create_tabs_widgets(self, code_base: CodeBase):
return code_base.get_tabs_widget()
def _create_editor_widget(self, code_base: CodeBase):
editors_container = Gtk.Box()
widget_registery.expose_object("code-container", self)
widget_registery.expose_object("editors-container", editors_container)
editors_container.add( Separator("separator_left") )

View File

@@ -8,7 +8,6 @@ from plugins import plugins_controller
from libs.controllers.controller_manager import ControllerManager
from .controllers.files_controller import FilesController
from .controllers.tabs_controller import TabsController
from .controllers.commands_controller import CommandsController
from .controllers.completion_controller import CompletionController
from .controllers.views.source_views_controller import SourceViewsController
@@ -31,23 +30,18 @@ class CodeBase:
def _load_controllers(self):
files_controller = FilesController()
tabs_controller = TabsController()
commands_controller = CommandsController()
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("tabs", tabs_controller)
self.controller_manager.register_controller("commands", commands_controller)
self.controller_manager.register_controller("completion", completion_controller)
self.controller_manager.register_controller("source_views", source_views_controller)
self.controller_manager.register_controller("plugins", plugins_controller)
self.controller_manager.register_controller("widgets", widget_registery)
def get_tabs_widget(self):
return self.controller_manager["tabs"].get_tabs_widget()
def create_source_view(self):
source_view = self.controller_manager["source_views"].create_source_view()
self.controller_manager["completion"].register_completer(

View File

@@ -39,3 +39,6 @@ class ControllerBase(Singleton, EmitDispatcher):
def message_to_selected(self, names: list[str], event: BaseEvent):
for name in names:
self.controller_context.message_to_selected(name, event)
def register_controller(self, name: str, controller):
self.controller_context.register_controller(name, controller)

View File

@@ -25,3 +25,6 @@ class ControllerContext:
def message_to_selected(self, name: list, event: BaseEvent):
raise ControllerContextException("Controller Context 'message_to_selected' must be overriden by Controller Manager...")
def register_controller(self, name: str, controller):
raise ControllerContextException("Controller Context 'register_controller' must be overriden by Controller Manager...")

View File

@@ -22,9 +22,10 @@ class ControllerManager(Singleton, dict):
def _crete_controller_context(self) -> ControllerContext:
controller_context = ControllerContext()
controller_context.message_to = self.message_to
controller_context.message = self.message
controller_context = ControllerContext()
controller_context.message_to = self.message_to
controller_context.message = self.message
controller_context.register_controller = self.register_controller
return controller_context

View File

@@ -67,31 +67,26 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
parent_path = os.getcwd()
for manifest_meta in manifest_metas:
path, folder, manifest = manifest_meta.path, manifest_meta.folder, manifest_meta.manifest
try:
target = join(path, "plugin.py")
path, \
folder, \
manifest = manifest_meta.path, manifest_meta.folder, manifest_meta.manifest
target = join(path, "plugin.py")
if not os.path.exists(target):
raise PluginsControllerException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
raise PluginsControllerException(
"Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load..."
)
module = self._load_plugin_module(path, folder, target)
if is_pre_launch:
self._run_with_pool(module, manifest_meta)
else:
GLib.idle_add(
self._run_with_pool, module, manifest_meta
)
except Exception as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
self._handle_plugin_execute(is_pre_launch, module, manifest_meta)
except PluginsControllerException as e:
logger.info(f"Malformed Plugin: Not loading -->: '{manifest_meta.folder}' !")
logger.debug(f"Trace: {traceback.print_exc()}")
os.chdir(parent_path)
def _run_with_pool(self, module: type, manifest_meta: ManifestMeta):
with ThreadPoolExecutor(max_workers = 1) as executor:
executor.submit(self.execute_plugin, module, manifest_meta)
def _load_plugin_module(self, path, folder, target):
os.chdir(path)
@@ -105,17 +100,27 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
return module
def create_plugin_context(self):
plugin_context: PluginContext = PluginContext()
def _handle_plugin_execute(
self, is_pre_launch: bool, module, manifest_meta
):
if not is_pre_launch:
GLib.idle_add(
self._run_with_pool, module, manifest_meta
)
return
plugin_context.requests_ui_element: callable = self.requests_ui_element
plugin_context.message: callable = self.message
plugin_context.message_to: callable = self.message_to
plugin_context.message_to_selected: callable = self.message_to_selected
plugin_context.emit: callable = event_system.emit
plugin_context.emit_and_await: callable = event_system.emit_and_await
self._run_with_pool(module, manifest_meta)
return plugin_context
def _run_with_pool(self, module: type, manifest_meta: ManifestMeta):
with ThreadPoolExecutor(max_workers = 1) as executor:
future = executor.submit(self.execute_plugin, module, manifest_meta)
future.add_done_callback(self._handle_future_exception)
def _handle_future_exception(self, future):
try:
future.result()
except Exception:
logger.exception("Plugin crashed during execution...")
def pre_launch_plugins(self) -> None:
logger.info(f"Loading pre-launch plugins...")
@@ -142,6 +147,18 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
self._plugin_collection.append(manifest_meta)
def create_plugin_context(self):
plugin_context: PluginContext = PluginContext()
plugin_context.requests_ui_element: callable = self.requests_ui_element
plugin_context.message: callable = self.message
plugin_context.message_to: callable = self.message_to
plugin_context.message_to_selected: callable = self.message_to_selected
plugin_context.emit: callable = event_system.emit
plugin_context.emit_and_await: callable = event_system.emit_and_await
plugin_context.register_controller: callable = self.register_controller
return plugin_context
plugins_controller = PluginsController()

View File

@@ -38,3 +38,7 @@ class PluginContext:
def emit_and_await(self, event_type: str, data: tuple = ()):
raise PluginContextException("Plugin Context 'emit_and_await' must be overridden...")
def register_controller(self, name: str, controller):
raise PluginContextException("Plugin Context 'register_controller' must be overridden...")

View File

@@ -42,3 +42,6 @@ class PluginCode(PluginBase):
def message_to_selected(self, names: list[str], event: BaseEvent):
return self.plugin_context.message_to_selected(names, event)
def register_controller(self, name: str, controller):
return self.plugin_context.register_controller(name, controller)