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:
@@ -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") )
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
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 ..source_view import SourceView
|
||||
|
||||
|
||||
|
||||
class TabsController(ControllerBase):
|
||||
def __init__(self):
|
||||
super(TabsController, self).__init__()
|
||||
|
||||
self.tabs_widget: TabsWidget = TabsWidget()
|
||||
self.tabs_widget.message = self.message
|
||||
|
||||
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.FocusedViewEvent):
|
||||
self.tabs_widget.view_changed( event.view.get_buffer() )
|
||||
elif isinstance(event, Code_Event_Types.FilePathSetEvent):
|
||||
self.update_tab_label(event)
|
||||
elif isinstance(event, Code_Event_Types.ModifiedChangedEvent):
|
||||
self.tabs_widget.modified_changed( event.buffer )
|
||||
elif isinstance(event, Code_Event_Types.FileExternallyDeletedEvent):
|
||||
self.tabs_widget.externally_deleted( event.buffer )
|
||||
elif isinstance(event, Code_Event_Types.AddedNewFileEvent):
|
||||
self.add_tab(event)
|
||||
elif isinstance(event, Code_Event_Types.PoppedFileEvent):
|
||||
...
|
||||
elif isinstance(event, Code_Event_Types.RemovedFileEvent):
|
||||
self.remove_tab(event)
|
||||
|
||||
def get_tabs_widget(self):
|
||||
return self.tabs_widget
|
||||
|
||||
def update_tab_label(self, event: Code_Event_Types.FilePathSetEvent):
|
||||
for page_widget in self.tabs_widget.get_children():
|
||||
tab = self.tabs_widget.get_tab_label(page_widget)
|
||||
if not event.file == tab.file: continue
|
||||
|
||||
tab.label.set_label(event.file.fname)
|
||||
|
||||
break
|
||||
|
||||
def add_tab(self, event: Code_Event_Types.AddedNewFileEvent):
|
||||
tab = TabWidget()
|
||||
tab.file = event.file
|
||||
|
||||
tab.label.set_label(event.file.fname)
|
||||
|
||||
self.tabs_widget.append_page(Gtk.Separator(), tab)
|
||||
tab.show_all()
|
||||
|
||||
def remove_tab(self, event: Code_Event_Types.RemovedFileEvent):
|
||||
for page_widget in self.tabs_widget.get_children():
|
||||
tab = self.tabs_widget.get_tab_label(page_widget)
|
||||
if not event.file == tab.file: continue
|
||||
|
||||
tab.clear_signals_and_data()
|
||||
self.tabs_widget.remove_page(
|
||||
self.tabs_widget.page_num(page_widget)
|
||||
)
|
||||
|
||||
break
|
||||
@@ -1,79 +0,0 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class TabWidget(Gtk.Box):
|
||||
"""docstring for TabWidget"""
|
||||
|
||||
def __init__(self):
|
||||
super(TabWidget, self).__init__()
|
||||
|
||||
self.file = None
|
||||
|
||||
self._handler_id = None
|
||||
self._eve_handler_id = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("tab-widget")
|
||||
|
||||
self.set_orientation(0)
|
||||
self.set_hexpand(False)
|
||||
self.set_vexpand(False)
|
||||
self.set_size_request(-1, 12)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.event_box = Gtk.EventBox()
|
||||
self.label = Gtk.Label()
|
||||
self.close_bttn = Gtk.Button()
|
||||
icon = Gtk.Image(stock = Gtk.STOCK_CLOSE)
|
||||
|
||||
self.event_box.set_above_child(True)
|
||||
ctx = self.label.get_style_context()
|
||||
ctx.add_class("tab-label")
|
||||
ctx = self.close_bttn.get_style_context()
|
||||
ctx.add_class("tab-close-bttn")
|
||||
|
||||
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.event_box.add(self.label)
|
||||
self.add(self.event_box)
|
||||
self.add(self.close_bttn)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def clear_signals_and_data(self):
|
||||
self.close_bttn.disconnect(self._handler_id)
|
||||
self.event_box.disconnect(self._eve_handler_id)
|
||||
self._handler_id = None
|
||||
|
||||
for child in self.get_children():
|
||||
child.unparent()
|
||||
child.run_dispose()
|
||||
child.destroy()
|
||||
|
||||
def set_close_signal(self, callback):
|
||||
self._handler_id = self.close_bttn.connect(
|
||||
'clicked',
|
||||
callback,
|
||||
self.file
|
||||
)
|
||||
@@ -1,177 +0,0 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from .tab_widget import TabWidget
|
||||
|
||||
|
||||
|
||||
class TabsWidget(Gtk.Notebook):
|
||||
def __init__(self):
|
||||
super(TabsWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_scrollable(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("page-added", self._page_added)
|
||||
self.switch_page_id = \
|
||||
self.connect_after("switch-page", self._switch_page)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def _page_added(self, notebook, page_widget, page_num):
|
||||
tab = self.get_tab_label(page_widget)
|
||||
tab.set_close_signal(self._close_tab)
|
||||
|
||||
self._bind_tab_menu(tab, page_widget)
|
||||
|
||||
page_widget.show()
|
||||
self.set_tab_detachable(page_widget, True)
|
||||
self.set_tab_reorderable(page_widget, True)
|
||||
|
||||
def _close_tab(self, button, file):
|
||||
event = Event_Factory.create_event(
|
||||
"remove_file",
|
||||
buffer = file.buffer
|
||||
)
|
||||
|
||||
self.message(event)
|
||||
|
||||
def _switch_page(self, notebook, page_widget, page_num):
|
||||
tab = self.get_tab_label(page_widget)
|
||||
event = Event_Factory.create_event(
|
||||
"set_active_file",
|
||||
buffer = tab.file.buffer
|
||||
)
|
||||
|
||||
self.message(event)
|
||||
|
||||
def _bind_tab_menu(self, tab, page_widget):
|
||||
def do_context_menu(tab, eve, page_widget):
|
||||
if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
|
||||
menu = self.create_menu(page_widget)
|
||||
menu.popup_at_pointer(eve)
|
||||
|
||||
tab._eve_handler_id = \
|
||||
tab.event_box.connect(
|
||||
"button-release-event",
|
||||
do_context_menu,
|
||||
page_widget
|
||||
)
|
||||
|
||||
def create_menu(self, page_widget) -> Gtk.Menu:
|
||||
context_menu = Gtk.Menu()
|
||||
|
||||
close_item = Gtk.MenuItem(label = "Close Tab")
|
||||
close_left_item = Gtk.MenuItem(label = "Close Tabs Left")
|
||||
close_right_item = Gtk.MenuItem(label = "Close Tabs Right")
|
||||
close_other_item = Gtk.MenuItem(label = "Close Other Tabs")
|
||||
close_all_item = Gtk.MenuItem(label = "Close All Tabs")
|
||||
|
||||
close_item.connect("activate", self.close_item, page_widget)
|
||||
close_left_item.connect("activate", self.close_left_items, page_widget)
|
||||
close_right_item.connect("activate", self.close_right_items, page_widget)
|
||||
close_other_item.connect("activate", self.close_other_items, page_widget)
|
||||
close_all_item.connect("activate", self.close_all_items, page_widget)
|
||||
|
||||
context_menu.append(close_item)
|
||||
context_menu.append(close_left_item)
|
||||
context_menu.append(close_right_item)
|
||||
context_menu.append(close_other_item)
|
||||
context_menu.append(close_all_item)
|
||||
|
||||
context_menu.show_all()
|
||||
|
||||
return context_menu
|
||||
|
||||
def view_changed(self, buffer):
|
||||
for page_widget in self.get_children():
|
||||
tab = self.get_tab_label(page_widget)
|
||||
if not buffer == tab.file.buffer: continue
|
||||
|
||||
self.handler_block(self.switch_page_id)
|
||||
|
||||
self.set_current_page(
|
||||
self.page_num(page_widget)
|
||||
)
|
||||
|
||||
self.handler_unblock(self.switch_page_id)
|
||||
|
||||
break
|
||||
|
||||
def modified_changed(self, buffer):
|
||||
for page_widget in self.get_children():
|
||||
tab = self.get_tab_label(page_widget)
|
||||
if not buffer == tab.file.buffer: continue
|
||||
|
||||
ctx = tab.label.get_style_context()
|
||||
ctx.remove_class("file-deleted")
|
||||
if buffer.get_modified():
|
||||
ctx.add_class("file-changed")
|
||||
else:
|
||||
ctx.remove_class("file-changed")
|
||||
|
||||
break
|
||||
|
||||
def externally_deleted(self, buffer):
|
||||
for page_widget in self.get_children():
|
||||
tab = self.get_tab_label(page_widget)
|
||||
if not buffer == tab.file.buffer: continue
|
||||
ctx = tab.label.get_style_context()
|
||||
ctx.add_class("file-deleted")
|
||||
break
|
||||
|
||||
|
||||
def close_item(self, menu_item, page_widget):
|
||||
tab = self.get_tab_label(page_widget)
|
||||
tab.close_bttn.clicked()
|
||||
|
||||
def close_left_items(self, menu_item, page_widget):
|
||||
children = self.get_children()
|
||||
i = children.index(page_widget)
|
||||
|
||||
if i == 0: return
|
||||
|
||||
for widget in children[ : i]:
|
||||
tab = self.get_tab_label(widget)
|
||||
tab.close_bttn.clicked()
|
||||
|
||||
def close_right_items(self, menu_item, page_widget):
|
||||
children = self.get_children()
|
||||
i = children.index(page_widget) + 1
|
||||
|
||||
if i == len(children): return
|
||||
|
||||
for widget in children[i : ]:
|
||||
tab = self.get_tab_label(widget)
|
||||
tab.close_bttn.clicked()
|
||||
|
||||
def close_other_items(self, menu_item, page_widget):
|
||||
self.close_left_items(menu_item, page_widget)
|
||||
self.close_right_items(menu_item, page_widget)
|
||||
|
||||
def close_all_items(self, menu_item, page_widget):
|
||||
children = self.get_children()
|
||||
|
||||
for widget in children[ : ]:
|
||||
tab = self.get_tab_label(widget)
|
||||
tab.close_bttn.clicked()
|
||||
@@ -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)
|
||||
|
||||
@@ -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...")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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...")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user