diff --git a/plugins/code/commands/autopairs/plugin.py b/plugins/code/commands/autopairs/plugin.py index d034cea..1da1ad0 100644 --- a/plugins/code/commands/autopairs/plugin.py +++ b/plugins/code/commands/autopairs/plugin.py @@ -39,6 +39,24 @@ class Plugin(PluginCode): self.emit_to("source_views", event) + def unload(self): + event = Event_Factory.create_event("unregister_command", + command_name = "autopairs", + command = Handler, + binding_mode = "held", + binding = [ + "'", "`", "[", "]", + '"', + '(', + ')', + '{', + '}' + ] + ) + + self.emit_to("source_views", event) + autopairs = None + def run(self): ... diff --git a/plugins/code/commands/file_history/__init__.py b/plugins/code/commands/file_history/__init__.py new file mode 100644 index 0000000..e6b1b36 --- /dev/null +++ b/plugins/code/commands/file_history/__init__.py @@ -0,0 +1,3 @@ +""" + Plugin Module +""" diff --git a/plugins/code/commands/file_history/__main__.py b/plugins/code/commands/file_history/__main__.py new file mode 100644 index 0000000..c2e27a7 --- /dev/null +++ b/plugins/code/commands/file_history/__main__.py @@ -0,0 +1,3 @@ +""" + Plugin Package +""" diff --git a/plugins/code/commands/file_history/autopairs.py b/plugins/code/commands/file_history/autopairs.py new file mode 100644 index 0000000..221393a --- /dev/null +++ b/plugins/code/commands/file_history/autopairs.py @@ -0,0 +1,97 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class Autopairs: + def __init__(self): + ... + + def handle_word_wrap(self, buffer, char_str: str): + wrap_block = self.get_wrap_block(char_str) + if not wrap_block: return + + selection = buffer.get_selection_bounds() + if not selection: + self.insert_pair(buffer, char_str, wrap_block) + return True + + self.wrap_selection(buffer, char_str, wrap_block, selection) + + return True + + def insert_pair( + self, buffer, char_str: str, wrap_block: tuple + ): + buffer.begin_user_action() + + left_block, right_block = wrap_block + insert_mark = buffer.get_insert() + + insert_itr = buffer.get_iter_at_mark(insert_mark) + buffer.insert(insert_itr, f"{left_block}{right_block}") + insert_itr = buffer.get_iter_at_mark( insert_mark ) + insert_itr.backward_char() + + buffer.place_cursor(insert_itr) + + buffer.end_user_action() + + def wrap_selection( + self, buffer, char_str: str, wrap_block: tuple, selection + ): + left_block, \ + right_block = wrap_block + start_itr, \ + end_itr = selection + data = buffer.get_text( + start_itr, end_itr, include_hidden_chars = False + ) + start_mark = buffer.create_mark("startclose", start_itr, False) + end_mark = buffer.create_mark("endclose", end_itr, True) + + buffer.begin_user_action() + + buffer.insert(start_itr, left_block) + end_itr = buffer.get_iter_at_mark(end_mark) + buffer.insert(end_itr, right_block) + + start = buffer.get_iter_at_mark(start_mark) + end = buffer.get_iter_at_mark(end_mark) + + buffer.select_range(start, end) + buffer.delete_mark_by_name("startclose") + buffer.delete_mark_by_name("endclose") + + buffer.end_user_action() + + def get_wrap_block(self, char_str) -> tuple: + left_block = "" + right_block = "" + + match char_str: + case "(" | ")": + left_block = "(" + right_block = ")" + case "[" | "]": + left_block = "[" + right_block = "]" + case "{" | "}": + left_block = "{" + right_block = "}" + case '"': + left_block = '"' + right_block = '"' + case "'": + left_block = "'" + right_block = "'" + case "`": + left_block = "`" + right_block = "`" + case _: + return () + + return left_block, right_block diff --git a/plugins/code/commands/file_history/manifest.json b/plugins/code/commands/file_history/manifest.json new file mode 100644 index 0000000..8561d56 --- /dev/null +++ b/plugins/code/commands/file_history/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "File History", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/code/commands/file_history/plugin.py b/plugins/code/commands/file_history/plugin.py new file mode 100644 index 0000000..18086c6 --- /dev/null +++ b/plugins/code/commands/file_history/plugin.py @@ -0,0 +1,65 @@ +# Python imports + +# Lib imports + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + +from plugins.plugin_types import PluginCode + + + +history: list = [] +history_size: int = 30 + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + + def _controller_message(self, event: Code_Event_Types.CodeEvent): + if isinstance(event, Code_Event_Types.RemovedFileEvent): + if event.file.ftype == "buffer": return + + if len(history) == history_size: + history.pop(0) + + history.append(event.file) + + def load(self): + self._manage_signals("register_command") + + def unload(self): + self._manage_signals("unregister_command") + + def _manage_signals(self, action: str): + event = Event_Factory.create_event(action, + command_name = "file_history_pop", + command = Handler, + binding_mode = "released", + binding = "t" + ) + + self.emit_to("source_views", event) + + def run(self): + ... + + +class Handler: + @staticmethod + def execute( + view: any, + char_str: str, + *args, + **kwargs + ): + logger.debug("Command: File History") + if len(history) == 0: return + + view._on_uri_data_received( + [ + history.pop().replace("file://", "") + ] + ) diff --git a/plugins/code/commands/nanoesq_temp_buffer/plugin.py b/plugins/code/commands/nanoesq_temp_buffer/plugin.py index 392caf0..028c460 100644 --- a/plugins/code/commands/nanoesq_temp_buffer/plugin.py +++ b/plugins/code/commands/nanoesq_temp_buffer/plugin.py @@ -21,7 +21,13 @@ class Plugin(PluginCode): ... def load(self): - event = Event_Factory.create_event("register_command", + self._manage_signals("register_command") + + def load(self): + self._manage_signals("unregister_command") + + def _manage_signals(self, action: str): + event = Event_Factory.create_event(action, command_name = "cut_to_temp_buffer", command = Handler, binding_mode = "held", @@ -30,7 +36,7 @@ class Plugin(PluginCode): self.emit_to("source_views", event) - event = Event_Factory.create_event("register_command", + event = Event_Factory.create_event(action, command_name = "paste_temp_buffer", command = Handler2, binding_mode = "held", diff --git a/plugins/code/commands/toggle_source_view/plugin.py b/plugins/code/commands/toggle_source_view/plugin.py index ff50e86..d8c55d2 100644 --- a/plugins/code/commands/toggle_source_view/plugin.py +++ b/plugins/code/commands/toggle_source_view/plugin.py @@ -26,6 +26,16 @@ class Plugin(PluginCode): 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 = "h" + ) + + self.emit_to("source_views", event) + def run(self): ... diff --git a/plugins/code/completers/example_completer/plugin.py b/plugins/code/completers/example_completer/plugin.py index d95e8a4..4e5b6e3 100644 --- a/plugins/code/completers/example_completer/plugin.py +++ b/plugins/code/completers/example_completer/plugin.py @@ -35,5 +35,15 @@ class Plugin(PluginCode): ) self.emit_to("completion", event) + def unload(self): + event = Event_Factory.create_event( + "unregister_provider", + provider_name = "Example Completer" + ) + self.emit_to("completion", event) + + self.provider = None + del self.provider + def run(self): ... diff --git a/plugins/code/completers/snippets_completer/plugin.py b/plugins/code/completers/snippets_completer/plugin.py index b303184..138571d 100644 --- a/plugins/code/completers/snippets_completer/plugin.py +++ b/plugins/code/completers/snippets_completer/plugin.py @@ -35,5 +35,15 @@ class Plugin(PluginCode): ) self.emit_to("completion", event) + def unload(self): + event = Event_Factory.create_event( + "unregister_provider", + provider_name = "Snippets Completer" + ) + self.emit_to("completion", event) + + self.provider = None + del self.provider + def run(self): ... diff --git a/plugins/code/completers/words_completer/plugin.py b/plugins/code/completers/words_completer/plugin.py index 2db9079..a1ab8a0 100644 --- a/plugins/code/completers/words_completer/plugin.py +++ b/plugins/code/completers/words_completer/plugin.py @@ -35,5 +35,15 @@ class Plugin(PluginCode): ) self.emit_to("completion", event) + def unload(self): + event = Event_Factory.create_event( + "unregister_provider", + provider_name = "Words Completer" + ) + self.emit_to("completion", event) + + self.provider = None + del self.provider + def run(self): ... diff --git a/plugins/code/event-watchers/extend_source_view_menu/__init__.py b/plugins/code/event-watchers/extend_source_view_menu/__init__.py new file mode 100644 index 0000000..e6b1b36 --- /dev/null +++ b/plugins/code/event-watchers/extend_source_view_menu/__init__.py @@ -0,0 +1,3 @@ +""" + Plugin Module +""" diff --git a/plugins/code/event-watchers/extend_source_view_menu/__main__.py b/plugins/code/event-watchers/extend_source_view_menu/__main__.py new file mode 100644 index 0000000..c2e27a7 --- /dev/null +++ b/plugins/code/event-watchers/extend_source_view_menu/__main__.py @@ -0,0 +1,3 @@ +""" + Plugin Package +""" diff --git a/plugins/code/event-watchers/extend_source_view_menu/manifest.json b/plugins/code/event-watchers/extend_source_view_menu/manifest.json new file mode 100644 index 0000000..eb66255 --- /dev/null +++ b/plugins/code/event-watchers/extend_source_view_menu/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Extend Source View Menu", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/code/event-watchers/extend_source_view_menu/plugin.py b/plugins/code/event-watchers/extend_source_view_menu/plugin.py new file mode 100644 index 0000000..4907807 --- /dev/null +++ b/plugins/code/event-watchers/extend_source_view_menu/plugin.py @@ -0,0 +1,29 @@ +# Python imports + +# Lib imports + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + +from plugins.plugin_types import PluginCode + +from .source_view_menu import extend_source_view_menu + + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + def _controller_message(self, event: Code_Event_Types.CodeEvent): + if isinstance(event, Code_Event_Types.PopulateSourceViewPopupEvent): + extend_source_view_menu(event.buffer, event.menu) + + def load(self): + ... + + def unload(self): + ... + + def run(self): + ... diff --git a/plugins/code/event-watchers/extend_source_view_menu/source_view_menu.py b/plugins/code/event-watchers/extend_source_view_menu/source_view_menu.py new file mode 100644 index 0000000..af84daf --- /dev/null +++ b/plugins/code/event-watchers/extend_source_view_menu/source_view_menu.py @@ -0,0 +1,68 @@ +# Python imports +import json + +# Lib imports +import gi + +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk + +# Application imports + + + +def on_case_handle(menuitem, buffer, action): + start_itr, \ + end_itr = buffer.get_selection_bounds() + data = buffer.get_text(start_itr, end_itr, False) + text = data + + if action == "on_all_upper": + text = data.upper() + elif action == "on_all_lower": + text = data.lower() + elif action == "on_invert": + text = data.swapcase() + elif action == "on_title": + text = data.title() + elif action == "on_title_strip": + text = data.title().replace("-", "").replace("_", "").replace(" ", "") + + buffer.begin_user_action() + buffer.delete(start_itr, end_itr) + buffer.insert(start_itr, text) + buffer.end_user_action() + + + +def extend_source_view_menu(buffer, menu): + if not buffer.get_selection_bounds(): return + + for child in menu.get_children(): + if not child.get_label() == "C_hange Case": continue + menu.remove(child) + + change_case_item = Gtk.MenuItem(label = "Change Case") + + case_menu = Gtk.Menu() + au_case_item = Gtk.MenuItem(label = "All Upper Case") + al_case_item = Gtk.MenuItem(label = "All Lower Case") + inver_case_item = Gtk.MenuItem(label = "Invert Case") + title_case_item = Gtk.MenuItem(label = "Title Case") + title_strip_case_item = Gtk.MenuItem(label = "Title Strip Case") + + au_case_item.connect("activate", on_case_handle, buffer, "on_all_upper") + al_case_item.connect("activate", on_case_handle, buffer, "on_all_lower") + inver_case_item.connect("activate", on_case_handle, buffer, "on_invert") + title_case_item.connect("activate", on_case_handle, buffer, "on_title") + title_strip_case_item.connect("activate", on_case_handle, buffer, "on_title_strip") + + case_menu.append(au_case_item) + case_menu.append(al_case_item) + case_menu.append(inver_case_item) + case_menu.append(title_case_item) + case_menu.append(title_strip_case_item) + change_case_item.set_submenu(case_menu) + + menu.append(change_case_item) diff --git a/plugins/code/event-watchers/file_state_watcher/plugin.py b/plugins/code/event-watchers/file_state_watcher/plugin.py index 43faf62..5603f2f 100644 --- a/plugins/code/event-watchers/file_state_watcher/plugin.py +++ b/plugins/code/event-watchers/file_state_watcher/plugin.py @@ -21,12 +21,15 @@ class Plugin(PluginCode): event.file.check_file_on_disk() if event.file.is_deleted(): - file_is_deleted(event) + file_is_deleted(event, self.emit) elif event.file.is_externally_modified(): - file_is_externally_modified(event) + file_is_externally_modified(event, self.emit) def load(self): ... + def unload(self): + ... + def run(self): ... diff --git a/plugins/code/event-watchers/file_state_watcher/watcher_checks.py b/plugins/code/event-watchers/file_state_watcher/watcher_checks.py index 6ec9fea..51dfa78 100644 --- a/plugins/code/event-watchers/file_state_watcher/watcher_checks.py +++ b/plugins/code/event-watchers/file_state_watcher/watcher_checks.py @@ -10,23 +10,23 @@ from libs.event_factory import Event_Factory, Code_Event_Types -def file_is_deleted(event): +def file_is_deleted(event, emit): event.file.was_deleted = True event = Event_Factory.create_event( "file_externally_deleted", file = event.file, buffer = event.buffer ) - self.emit(event) + emit(event) -def file_is_externally_modified(event): +def file_is_externally_modified(event, emit): # event = Event_Factory.create_event( # "file_externally_modified", # file = event.file, # buffer = event.buffer # ) -# self.emit(event) +# emit(event) ... diff --git a/plugins/code/event-watchers/prettify_json/plugin.py b/plugins/code/event-watchers/prettify_json/plugin.py index 4938f24..99afe8d 100644 --- a/plugins/code/event-watchers/prettify_json/plugin.py +++ b/plugins/code/event-watchers/prettify_json/plugin.py @@ -32,5 +32,8 @@ class Plugin(PluginCode): def load(self): ... + def unload(self): + ... + def run(self): ... diff --git a/plugins/code/event-watchers/prettify_json/prettify_json.py b/plugins/code/event-watchers/prettify_json/prettify_json.py index 0e62dc6..ad03e62 100644 --- a/plugins/code/event-watchers/prettify_json/prettify_json.py +++ b/plugins/code/event-watchers/prettify_json/prettify_json.py @@ -13,7 +13,7 @@ from gi.repository import Gtk def add_prettify_json(buffer, menu): - menu.append( Gtk.SeparatorMenuItem() ) + menu.append(separator) def on_prettify_json(menuitem, buffer): start_itr, \ diff --git a/plugins/code/language_server_clients/java_lsp_client/manifest.json b/plugins/code/language_server_clients/java_lsp_client/manifest.json index 93f7049..76cfaf8 100644 --- a/plugins/code/language_server_clients/java_lsp_client/manifest.json +++ b/plugins/code/language_server_clients/java_lsp_client/manifest.json @@ -3,5 +3,6 @@ "author": "ITDominator", "version": "0.0.1", "support": "", + "autoload": false, "requests": {} } diff --git a/plugins/code/language_server_clients/java_lsp_client/plugin.py b/plugins/code/language_server_clients/java_lsp_client/plugin.py index e7dd8c2..2320324 100644 --- a/plugins/code/language_server_clients/java_lsp_client/plugin.py +++ b/plugins/code/language_server_clients/java_lsp_client/plugin.py @@ -33,5 +33,11 @@ class Plugin(PluginCode): ) self.emit_to("lsp_manager", event) + def unload(self): + event = Event_Factory.create_event("unregister_lsp_client", + lang_id = "java" + ) + self.emit_to("lsp_manager", event) + def run(self): ... diff --git a/plugins/code/language_server_clients/lsp_manager/lsp_manager.py b/plugins/code/language_server_clients/lsp_manager/lsp_manager.py index effa67e..cbfce28 100644 --- a/plugins/code/language_server_clients/lsp_manager/lsp_manager.py +++ b/plugins/code/language_server_clients/lsp_manager/lsp_manager.py @@ -40,7 +40,7 @@ class LSPManager(ControllerBase): self.lsp_manager_ui.connect('close-client', self._on_close_client) def _do_bind_mapping(self): - self.response_cache.set_lsp_client(self.lsp_manager_client) + self.response_cache.set_lsp_manager_client(self.lsp_manager_client) self.provider.response_cache = self.response_cache self.response_registry.set_event_hub( self.emit, self.emit_to, self.provider @@ -52,6 +52,7 @@ class LSPManager(ControllerBase): self.lsp_manager_ui.add_client_listing(event.lang_id, event.lang_config) elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent): self.response_registry.unregister_handler(event.lang_id) + self.lsp_manager_ui.remove_client_listing(event.lang_id) def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool: init_opts = ui.get_init_opts(lang_id) @@ -66,6 +67,10 @@ class LSPManager(ControllerBase): ui.toggle_client_buttons(show_close=False) return result + def handle_destroy(self): + self.lsp_manager_ui.disconnect_by_func(self._on_create_client) + self.lsp_manager_ui.disconnect_by_func(self._on_close_client) + def create_client( self, lang_id: str = "python", diff --git a/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py b/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py index 79f6db5..740dd0d 100644 --- a/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py +++ b/plugins/code/language_server_clients/lsp_manager/lsp_manager_ui.py @@ -41,7 +41,8 @@ class LSPManagerUI(Gtk.Dialog): self.set_hexpand(True) def _setup_signals(self): - self.connect("show", self._show) + self.connect("show", self._handle_show) + self.connect("destroy", self._handle_destroy) def _subscribe_to_events(self): ... @@ -68,7 +69,7 @@ class LSPManagerUI(Gtk.Dialog): self.path_bttn.connect("file-set", self._file_set) self.combo_box.connect("changed", self._on_combo_changed) - self.hide_bttn.connect("clicked", lambda widget: self.hide()) + 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) @@ -92,9 +93,18 @@ class LSPManagerUI(Gtk.Dialog): self.close_client_bttn.hide() bttn_box.hide() - def _show(self, widget): + 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() @@ -163,7 +173,10 @@ class LSPManagerUI(Gtk.Dialog): buffer.set_text(json_str, -1) def map_parent_resize_event(self, parent): - parent.connect("size-allocate", lambda w, r: self._map_resize(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() @@ -187,6 +200,17 @@ class LSPManagerUI(Gtk.Dialog): 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 {} diff --git a/plugins/code/language_server_clients/lsp_manager/manifest.json b/plugins/code/language_server_clients/lsp_manager/manifest.json index 830009d..5536e7f 100644 --- a/plugins/code/language_server_clients/lsp_manager/manifest.json +++ b/plugins/code/language_server_clients/lsp_manager/manifest.json @@ -3,6 +3,6 @@ "author": "ITDominator", "version": "0.0.1", "support": "", - "pre_launch": true, + "autoload": false, "requests": {} } diff --git a/plugins/code/language_server_clients/lsp_manager/plugin.py b/plugins/code/language_server_clients/lsp_manager/plugin.py index 75f9c50..4f8612b 100644 --- a/plugins/code/language_server_clients/lsp_manager/plugin.py +++ b/plugins/code/language_server_clients/lsp_manager/plugin.py @@ -62,6 +62,31 @@ class Plugin(PluginCode): source_view = event.response lsp_manager.lsp_manager_ui.set_source_view(source_view) + def unload(self): + Event_Factory.unregister_events( lsp_events.__dict__.items() ) + + self.unregister_controller("lsp_manager") + + window = self.request_ui_element("main-window") + + lsp_manager.lsp_manager_ui.unmap_parent_resize_event(window) + + event = Event_Factory.create_event("unregister_command", + command_name = "LSP Manager", + command = Handler, + binding_mode = "released", + binding = ["l", "g", "i"] + ) + self.emit_to("source_views", event) + + event = Event_Factory.create_event( + "unregister_provider", + provider_name = "LSP Completer" + ) + self.emit_to("completion", event) + + lsp_manager.handle_destroy() + def run(self): ... diff --git a/plugins/code/language_server_clients/lsp_manager/provider/provider_response_cache.py b/plugins/code/language_server_clients/lsp_manager/provider/provider_response_cache.py index 0ef02fa..2686d91 100644 --- a/plugins/code/language_server_clients/lsp_manager/provider/provider_response_cache.py +++ b/plugins/code/language_server_clients/lsp_manager/provider/provider_response_cache.py @@ -15,27 +15,27 @@ class ProviderResponseCache(ProviderResponseCacheBase): def __init__(self): super(ProviderResponseCache, self).__init__() - self.matchers: dict = {} - self._lsp_client = None + self.matchers: dict = {} + self.lsp_manager_client = None - def set_lsp_client(self, lsp_client): - self._lsp_client = lsp_client + def set_lsp_manager_client(self, lsp_client): + self.lsp_manager_client = lsp_client def process_file_load(self, event): - if self._lsp_client: - self._lsp_client.process_file_load(event) + if self.lsp_manager_client: + self.lsp_manager_client.process_file_load(event) def process_file_close(self, event): - if self._lsp_client: - self._lsp_client.process_file_close(event) + if self.lsp_manager_client: + self.lsp_manager_client.process_file_close(event) def process_file_save(self, event): - if self._lsp_client: - self._lsp_client.process_file_save(event) + if self.lsp_manager_client: + self.lsp_manager_client.process_file_save(event) def process_file_change(self, event): - if self._lsp_client: - self._lsp_client.process_file_change(event) + if self.lsp_manager_client: + self.lsp_manager_client.process_file_change(event) def filter(self, word: str) -> list[dict]: return [] diff --git a/plugins/code/language_server_clients/lsp_manager/response_handlers/response_registry.py b/plugins/code/language_server_clients/lsp_manager/response_handlers/response_registry.py index c871486..09403fa 100644 --- a/plugins/code/language_server_clients/lsp_manager/response_handlers/response_registry.py +++ b/plugins/code/language_server_clients/lsp_manager/response_handlers/response_registry.py @@ -33,7 +33,7 @@ class ResponseRegistry: def register_handler(self, lang_id: str, handler_cls: type[BaseHandler]): self._lang_handlers[lang_id] = handler_cls - def unregister_handler(self, lang_id: str, handler_cls: type[BaseHandler]): + def unregister_handler(self, lang_id: str): del self._lang_handlers[lang_id] def get_handler(self, lang_id: str = "", method: str = ""): diff --git a/plugins/code/language_server_clients/python_lsp_client/manifest.json b/plugins/code/language_server_clients/python_lsp_client/manifest.json index beccbb5..dff6d04 100644 --- a/plugins/code/language_server_clients/python_lsp_client/manifest.json +++ b/plugins/code/language_server_clients/python_lsp_client/manifest.json @@ -3,5 +3,6 @@ "author": "ITDominator", "version": "0.0.1", "support": "", + "autoload": false, "requests": {} } diff --git a/plugins/code/language_server_clients/python_lsp_client/plugin.py b/plugins/code/language_server_clients/python_lsp_client/plugin.py index a579da6..5d4f6cf 100644 --- a/plugins/code/language_server_clients/python_lsp_client/plugin.py +++ b/plugins/code/language_server_clients/python_lsp_client/plugin.py @@ -33,5 +33,11 @@ class Plugin(PluginCode): ) self.emit_to("lsp_manager", event) + def unload(self): + event = Event_Factory.create_event("unregister_lsp_client", + lang_id = "python" + ) + self.emit_to("lsp_manager", event) + def run(self): ... diff --git a/plugins/code/ui/code_minimap/plugin.py b/plugins/code/ui/code_minimap/plugin.py index 2b13d74..5c110ef 100644 --- a/plugins/code/ui/code_minimap/plugin.py +++ b/plugins/code/ui/code_minimap/plugin.py @@ -28,5 +28,12 @@ class Plugin(PluginCode): editors_container = self.request_ui_element("editors-container") editors_container.add( code_minimap ) + event = Event_Factory.create_event("get_active_view") + self.emit_to("source_views", event) + code_minimap.set_smini_view(event.response) + + def unload(self): + code_minimap.destroy() + def run(self): ... diff --git a/plugins/code/ui/info_bar/info_bar_widget.py b/plugins/code/ui/info_bar/info_bar_widget.py index 40c48ed..a5d88e2 100644 --- a/plugins/code/ui/info_bar/info_bar_widget.py +++ b/plugins/code/ui/info_bar/info_bar_widget.py @@ -35,7 +35,6 @@ class InfoBarWidget(Gtk.Box): def _subscribe_to_events(self): ... - def _load_widgets(self): self.path_label = Gtk.Label(label = "...") self.line_char_label = Gtk.Label(label = "1:0") @@ -92,5 +91,3 @@ class InfoBarWidget(Gtk.Box): encoding_type = "utf-8" if not encoding_type else encoding_type self.encoding_label.set_text(encoding_type) - - diff --git a/plugins/code/ui/info_bar/plugin.py b/plugins/code/ui/info_bar/plugin.py index 5fe0b15..f1c08e3 100644 --- a/plugins/code/ui/info_bar/plugin.py +++ b/plugins/code/ui/info_bar/plugin.py @@ -28,5 +28,8 @@ class Plugin(PluginCode): header = self.request_ui_element("header-container") header.add( info_bar_widget ) + def unload(self): + info_bar_widget.destroy() + def run(self): ... diff --git a/plugins/code/ui/tabs_bar/plugin.py b/plugins/code/ui/tabs_bar/plugin.py index 2cec4d4..1911b6b 100644 --- a/plugins/code/ui/tabs_bar/plugin.py +++ b/plugins/code/ui/tabs_bar/plugin.py @@ -20,13 +20,27 @@ class Plugin(PluginCode): ... def load(self): - tabs_controller = TabsController() + self.tabs_controller = TabsController() code_container = self.request_ui_element("code-container") - self.register_controller("tabs", tabs_controller) + self.register_controller("tabs", self.tabs_controller) - code_container.add( tabs_controller.tabs_widget ) - code_container.reorder_child(tabs_controller.tabs_widget, 0) + code_container.add( self.tabs_controller.tabs_widget ) + code_container.reorder_child(self.tabs_controller.tabs_widget, 0) + + event = Event_Factory.create_event("get_files") + self.emit_to("files", event) + for file in event.response: + self.tabs_controller.add_tab(file) + + def unload(self): + self.unregister_controller("tabs") + self.tabs_controller.unload_tabs() + self.tabs_controller.tabs_widget.destroy() + + self.tabs_controller.tabs_widget = None + self.tabs_controller = None + del self.tabs_controller def run(self): ... diff --git a/plugins/code/ui/tabs_bar/tab_widget.py b/plugins/code/ui/tabs_bar/tab_widget.py index 4796548..729cc29 100644 --- a/plugins/code/ui/tabs_bar/tab_widget.py +++ b/plugins/code/ui/tabs_bar/tab_widget.py @@ -32,7 +32,6 @@ class TabWidget(Gtk.Box): self.set_orientation(0) self.set_hexpand(False) self.set_vexpand(False) - self.set_can_focus(False) self.set_size_request(-1, 12) def _setup_signals(self): @@ -44,10 +43,6 @@ class TabWidget(Gtk.Box): self.close_bttn = Gtk.Button() icon = Gtk.Image(stock = Gtk.STOCK_CLOSE) - self.event_box.set_can_focus(False) - self.label.set_can_focus(False) - self.close_bttn.set_can_focus(False) - self.event_box.set_above_child(True) ctx = self.label.get_style_context() ctx.add_class("tab-label") diff --git a/plugins/code/ui/tabs_bar/tabs_controller.py b/plugins/code/ui/tabs_bar/tabs_controller.py index a30ac64..d5cb48f 100644 --- a/plugins/code/ui/tabs_bar/tabs_controller.py +++ b/plugins/code/ui/tabs_bar/tabs_controller.py @@ -16,7 +16,6 @@ from .tab_widget import TabWidget - class TabsController(ControllerBase): def __init__(self): super(TabsController, self).__init__() @@ -35,7 +34,7 @@ class TabsController(ControllerBase): 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) + self.add_tab(event.file) elif isinstance(event, Code_Event_Types.PoppedFileEvent): ... elif isinstance(event, Code_Event_Types.RemovedFileEvent): @@ -53,11 +52,11 @@ class TabsController(ControllerBase): break - def add_tab(self, event: Code_Event_Types.AddedNewFileEvent): + def add_tab(self, file): tab = TabWidget() - tab.file = event.file + tab.file = file - tab.label.set_label(event.file.fname) + tab.label.set_label(file.fname) self.tabs_widget.append_page(Gtk.Separator(), tab) tab.show_all() @@ -73,3 +72,13 @@ class TabsController(ControllerBase): ) break + + def unload_tabs(self): + for page_widget in self.tabs_widget.get_children(): + tab = self.tabs_widget.get_tab_label(page_widget) + + tab.clear_signals_and_data() + self.tabs_widget.remove_page( + self.tabs_widget.page_num(page_widget) + ) + diff --git a/plugins/code/ui/tabs_bar/tabs_widget.py b/plugins/code/ui/tabs_bar/tabs_widget.py index d2cee4a..9a8b18d 100644 --- a/plugins/code/ui/tabs_bar/tabs_widget.py +++ b/plugins/code/ui/tabs_bar/tabs_widget.py @@ -33,6 +33,7 @@ class TabsWidget(Gtk.Notebook): self.connect("page-added", self._page_added) self.switch_page_id = \ self.connect_after("switch-page", self._switch_page) + self.connect("destroy", self._handle_destroy) def _subscribe_to_events(self): ... @@ -81,25 +82,38 @@ class TabsWidget(Gtk.Notebook): ) def create_menu(self, page_widget) -> Gtk.Menu: - context_menu = Gtk.Menu() + context_menu = Gtk.Menu() + close_submenu = Gtk.Menu() + save_item = Gtk.MenuItem(label = "Save") + save_as_item = Gtk.MenuItem(label = "Save As") - 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_actions_menu = Gtk.MenuItem(label = "Close Actions") + 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) + save_item.connect("activate", self.save_item, page_widget) + save_as_item.connect("activate", self.save_as_item, page_widget) + + 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) + 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) + close_submenu.append(close_item) + close_submenu.append(close_left_item) + close_submenu.append(close_right_item) + close_submenu.append(close_other_item) + close_submenu.append(close_all_item) + + close_actions_menu.set_submenu(close_submenu) + + context_menu.append(save_item) + context_menu.append(save_as_item) + context_menu.append(close_actions_menu) context_menu.show_all() @@ -115,7 +129,6 @@ class TabsWidget(Gtk.Notebook): self.set_current_page( self.page_num(page_widget) ) - self.handler_unblock(self.switch_page_id) break @@ -143,6 +156,14 @@ class TabsWidget(Gtk.Notebook): break + def save_item(self, menu_item, page_widget): + tab = self.get_tab_label(page_widget) + tab.file.save() + + def save_as_item(self, menu_item, page_widget): + tab = self.get_tab_label(page_widget) + tab.file.save_as() + def close_item(self, menu_item, page_widget): tab = self.get_tab_label(page_widget) tab.close_bttn.clicked() @@ -177,3 +198,9 @@ class TabsWidget(Gtk.Notebook): for widget in children[ : ]: tab = self.get_tab_label(widget) tab.close_bttn.clicked() + + def _handle_destroy(self, widget): + self.disconnect_by_func(self._page_added) + self.disconnect_by_func(self._switch_page) + self.disconnect_by_func(self._handle_destroy) + diff --git a/plugins/ui/template/plugin.py b/plugins/ui/template/plugin.py index bd7fcb6..41b3e54 100644 --- a/plugins/ui/template/plugin.py +++ b/plugins/ui/template/plugin.py @@ -24,8 +24,16 @@ class Plugin(PluginUI): ui_element = self.request_ui_element("header-container") ui_element.add( self.generate_plugin_element() ) + def unload(self): + ui_element = self.request_ui_element("header-container") + self.button = self.generate_plugin_element() + + ui_element.add( self.button ) + def run(self): - ... + self.button.disconnect_by_func(self.send_message) + self.button.destroy() + del button def generate_plugin_element(self): button = Gtk.Button(label = "Hello, World!") diff --git a/src/core/containers/base_container.py b/src/core/containers/base_container.py index 56525d7..cada0ec 100644 --- a/src/core/containers/base_container.py +++ b/src/core/containers/base_container.py @@ -19,7 +19,6 @@ class BaseContainer(Gtk.Box): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -32,12 +31,16 @@ class BaseContainer(Gtk.Box): self._update_transparency() def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): event_system.subscribe("update-transparency", self._update_transparency) event_system.subscribe("remove-transparency", self._remove_transparency) + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("base-container", self) diff --git a/src/core/containers/body_container.py b/src/core/containers/body_container.py index 8d56db5..1a1f3d2 100644 --- a/src/core/containers/body_container.py +++ b/src/core/containers/body_container.py @@ -19,7 +19,6 @@ class BodyContainer(Gtk.Box): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -29,14 +28,17 @@ class BodyContainer(Gtk.Box): self.ctx.add_class("body-container") self.set_orientation(Gtk.Orientation.HORIZONTAL) - self.set_homogeneous(True) def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): ... + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("body-container", self) diff --git a/src/core/containers/center_container.py b/src/core/containers/center_container.py index 7900535..5fcd3bf 100644 --- a/src/core/containers/center_container.py +++ b/src/core/containers/center_container.py @@ -14,11 +14,9 @@ class CenterContainer(Gtk.Box): def __init__(self): super(CenterContainer, self).__init__() - self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -32,11 +30,15 @@ class CenterContainer(Gtk.Box): self.set_vexpand(True) def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): ... + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("center-container", self) diff --git a/src/core/containers/code/editors_container.py b/src/core/containers/code/editors_container.py index 06409a7..675e9ad 100644 --- a/src/core/containers/code/editors_container.py +++ b/src/core/containers/code/editors_container.py @@ -4,7 +4,7 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from gi.repository import GLib +#from gi.repository import GLib # Application imports @@ -31,7 +31,7 @@ class EditorsContainer(Gtk.Paned): self.set_wide_handle(True) def _setup_signals(self): - self.map_id = self.connect("map", self._init_map) + self.connect("map", self._init_map) def _subscribe_to_events(self): ... @@ -59,13 +59,6 @@ class EditorsContainer(Gtk.Paned): return scrolled_win1, scrolled_win2 def _init_map(self, view): - def _first_show_init(): - self.disconnect(self.map_id) - del self.map_id - - self.code_base.first_map_load() - - del self.code_base - return False - - GLib.timeout_add(100, _first_show_init) + self.disconnect_by_func( self._init_map ) + self.code_base.first_map_load() + del self.code_base diff --git a/src/core/containers/footer_container.py b/src/core/containers/footer_container.py index e737114..5ec537b 100644 --- a/src/core/containers/footer_container.py +++ b/src/core/containers/footer_container.py @@ -17,7 +17,6 @@ class FooterContainer(Gtk.Box): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -30,11 +29,15 @@ class FooterContainer(Gtk.Box): self.set_hexpand(True) def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): ... + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("footer-container", self) diff --git a/src/core/containers/header_container.py b/src/core/containers/header_container.py index 4e4902c..3d3a19d 100644 --- a/src/core/containers/header_container.py +++ b/src/core/containers/header_container.py @@ -19,7 +19,6 @@ class HeaderContainer(Gtk.Box): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -32,11 +31,15 @@ class HeaderContainer(Gtk.Box): self.set_hexpand(True) def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar) + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("header-container", self) diff --git a/src/core/containers/left_container.py b/src/core/containers/left_container.py index 1865a68..78190b7 100644 --- a/src/core/containers/left_container.py +++ b/src/core/containers/left_container.py @@ -18,7 +18,6 @@ class LeftContainer(Gtk.Box): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -31,11 +30,15 @@ class LeftContainer(Gtk.Box): self.set_vexpand(True) def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): ... + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("left-container", self) diff --git a/src/core/containers/right_container.py b/src/core/containers/right_container.py index 090a924..eb003ad 100644 --- a/src/core/containers/right_container.py +++ b/src/core/containers/right_container.py @@ -18,7 +18,6 @@ class RightContainer(Gtk.Box): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self.show() @@ -31,11 +30,15 @@ class RightContainer(Gtk.Box): self.set_vexpand(True) def _setup_signals(self): - ... + self.connect("show", self._handle_show) def _subscribe_to_events(self): ... + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("right-container", self) diff --git a/src/core/controllers/base_controller.py b/src/core/controllers/base_controller.py index 5e95dd0..28e985e 100644 --- a/src/core/controllers/base_controller.py +++ b/src/core/controllers/base_controller.py @@ -24,6 +24,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin) def __init__(self): self._setup_controller_data() + self.plugins_controller.manual_launch_plugins() self._load_plugins(is_pre = True) self._setup_styling() @@ -31,6 +32,7 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin) self._subscribe_to_events() self._load_controllers() self._load_plugins(is_pre = False) + self._load_files() logger.info(f"Made it past {self.__class__} loading...") @@ -43,7 +45,6 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin) self.base_container = BaseContainer() self.plugins_controller = plugins_controller - widget_registery.expose_object("main_window", self.window) settings_manager.register_signals_to_builder([self, self.base_container]) self._collect_files_dirs() diff --git a/src/core/widgets/code/command_system/command_system.py b/src/core/widgets/code/command_system/command_system.py index 7e5d8c4..a723f46 100644 --- a/src/core/widgets/code/command_system/command_system.py +++ b/src/core/widgets/code/command_system/command_system.py @@ -5,13 +5,14 @@ # 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: +class CommandSystem(CommandSystemMixin): def __init__(self): super(CommandSystem, self).__init__() @@ -37,6 +38,10 @@ class CommandSystem: 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... """ @@ -47,69 +52,69 @@ class CommandSystem: ... - 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) +# 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) \ No newline at end of file diff --git a/src/core/widgets/code/command_system/commands/toggle_plugins_ui.py b/src/core/widgets/code/command_system/commands/toggle_plugins_ui.py new file mode 100644 index 0000000..21af296 --- /dev/null +++ b/src/core/widgets/code/command_system/commands/toggle_plugins_ui.py @@ -0,0 +1,21 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + + + +def execute( + view: GtkSource.View, + *args, + **kwargs +): + logger.debug("Command: Toggle Plugins UI") + view.command.toggle_plugins_ui() diff --git a/src/core/widgets/code/controllers/files_controller.py b/src/core/widgets/code/controllers/files_controller.py index 6f7fa00..da73b5e 100644 --- a/src/core/widgets/code/controllers/files_controller.py +++ b/src/core/widgets/code/controllers/files_controller.py @@ -28,6 +28,8 @@ class FilesController(ControllerBase, list): self.remove_file(event) elif isinstance(event, Code_Event_Types.GetFileEvent): self.get_file(event) + elif isinstance(event, Code_Event_Types.GetFilesEvent): + event.response = self elif isinstance(event, Code_Event_Types.GetSwapFileEvent): self.get_swap_file(event) diff --git a/src/core/widgets/code/controllers/views/signal_mapper.py b/src/core/widgets/code/controllers/views/signal_mapper.py index 915acf9..da702a4 100644 --- a/src/core/widgets/code/controllers/views/signal_mapper.py +++ b/src/core/widgets/code/controllers/views/signal_mapper.py @@ -29,7 +29,11 @@ class SourceViewSignalMapper: def connect_signals(self, source_view: SourceView): signal_mappings = self._get_signal_mappings() for signal, handler in signal_mappings.items(): - source_view.connect(signal, handler) + if not signal == "populate-popup": + source_view.connect(signal, handler) + continue + + source_view.connect_after(signal, handler) def disconnect_signals(self, source_view: SourceView): signal_mappings = self._get_signal_mappings() diff --git a/src/core/widgets/code/controllers/views/source_views_controller.py b/src/core/widgets/code/controllers/views/source_views_controller.py index 059f917..cb66763 100644 --- a/src/core/widgets/code/controllers/views/source_views_controller.py +++ b/src/core/widgets/code/controllers/views/source_views_controller.py @@ -33,11 +33,15 @@ class SourceViewsController(ControllerBase, list): self._remove_file(event) elif isinstance(event, Code_Event_Types.RegisterCommandEvent): self._register_command(event) + elif isinstance(event, Code_Event_Types.UnregisterCommandEvent): + self._unregister_command(event) if not self.signal_mapper.active_view: return if isinstance(event, Code_Event_Types.GetActiveViewEvent): event.response = self.signal_mapper.active_view + elif isinstance(event, Code_Event_Types.GetSourceViewsEvent): + event.response = self elif isinstance(event, Code_Event_Types.TextChangedEvent): self.signal_mapper.active_view.command.exec("update_info_bar") elif isinstance(event, Code_Event_Types.SetActiveFileEvent): @@ -63,6 +67,24 @@ class SourceViewsController(ControllerBase, list): event.command ) + def _unregister_command(self, event: Code_Event_Types.UnregisterCommandEvent): + if not isinstance(event.binding, list): + event.binding = [ event.binding ] + + for binding in event.binding: + self.state_manager.key_mapper.unmap_command( + event.command_name, + { + f"{event.binding_mode}": binding + } + ) + + for view in self: + view.command.remove_command( + event.command_name, + event.command + ) + def _get_command_system(self): event = Event_Factory.create_event("get_new_command_system") self.message_to("commands", event) diff --git a/src/core/widgets/code/key_mapper.py b/src/core/widgets/code/key_mapper.py index eb1b473..6f788da 100644 --- a/src/core/widgets/code/key_mapper.py +++ b/src/core/widgets/code/key_mapper.py @@ -95,6 +95,28 @@ class KeyMapper: getattr(self.states[state], press_state)[keyname] = command + def unmap_command(self, command, entry): + press_state = "held" if "held" in entry else "released" + keyname = entry[press_state] + + state = NoKeyState + if "" in keyname: + state = state | CtrlKeyState + if "" in keyname: + state = state | ShiftKeyState + if "" in keyname: + state = state | AltKeyState + + keyname = keyname.replace("", "") \ + .replace("", "") \ + .replace("", "") \ + .lower() + + mapping = getattr(self.states[state], press_state) + + if keyname in mapping and mapping[keyname] == command: + del mapping[keyname] + def _key_press_event(self, eve): keyname = self.get_keyname(eve) char_str = self.get_char(eve) diff --git a/src/core/widgets/code/mixins/command_system_mixin.py b/src/core/widgets/code/mixins/command_system_mixin.py new file mode 100644 index 0000000..6dd7804 --- /dev/null +++ b/src/core/widgets/code/mixins/command_system_mixin.py @@ -0,0 +1,83 @@ +# Python imports + +# Lib imports + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + +from ..source_view import SourceView + + + +class CommandSystemMixin: + def toggle_plugins_ui(self): + event = Event_Factory.create_event( "toggle_plugins_ui" ) + + self.emit_to("plugins", event) + + 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) diff --git a/src/core/window.py b/src/core/window.py index fc03ad8..ca907b3 100644 --- a/src/core/window.py +++ b/src/core/window.py @@ -42,7 +42,6 @@ class Window(Gtk.ApplicationWindow): self._setup_styling() self._setup_signals() self._subscribe_to_events() - self._load_widgets() self._set_window_data() self._set_size_constraints() @@ -67,6 +66,7 @@ class Window(Gtk.ApplicationWindow): def _setup_signals(self): self.connect("focus-in-event", self._on_focus_in_event) self.connect("focus-out-event", self._on_focus_out_event) + self.connect("show", self._handle_show) self.connect("delete-event", self.stop) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.stop) @@ -75,6 +75,10 @@ class Window(Gtk.ApplicationWindow): event_system.subscribe("tear-down", self.stop) event_system.subscribe("load-interactive-debug", self._load_interactive_debug) + def _handle_show(self, widget): + self.disconnect_by_func( self._handle_show ) + self._load_widgets() + def _load_widgets(self): widget_registery.expose_object("main-window", self) diff --git a/src/libs/controllers/controller_base.py b/src/libs/controllers/controller_base.py index 480c406..3997ba6 100644 --- a/src/libs/controllers/controller_base.py +++ b/src/libs/controllers/controller_base.py @@ -3,7 +3,7 @@ # Lib imports # Application imports -from ..singleton_raised import SingletonRaised +from ..singleton import Singleton from ..dto.base_event import BaseEvent @@ -17,7 +17,7 @@ class ControllerBaseException(Exception): -class ControllerBase(SingletonRaised, EmitDispatcher): +class ControllerBase(Singleton, EmitDispatcher): def __init__(self): super(ControllerBase, self).__init__() @@ -42,3 +42,6 @@ class ControllerBase(SingletonRaised, EmitDispatcher): def register_controller(self, name: str, controller): self.controller_message_bus.register_controller(name, controller) + + def unregister_controller(self, name: str): + self.controller_message_bus.unregister_controller(name) diff --git a/src/libs/controllers/controller_manager.py b/src/libs/controllers/controller_manager.py index c80229f..aa5afb3 100644 --- a/src/libs/controllers/controller_manager.py +++ b/src/libs/controllers/controller_manager.py @@ -31,10 +31,11 @@ class ControllerManager(Singleton, dict): def _crete_controller_message_bus(self) -> ControllerMessageBus: - controller_message_bus = ControllerMessageBus() - controller_message_bus.message_to = self.message_to - controller_message_bus.message = self.message - controller_message_bus.register_controller = self.register_controller + controller_message_bus = ControllerMessageBus() + controller_message_bus.message_to = self.message_to + controller_message_bus.message = self.message + controller_message_bus.register_controller = self.register_controller + controller_message_bus.unregister_controller = self.unregister_controller return controller_message_bus @@ -51,6 +52,17 @@ class ControllerManager(Singleton, dict): self[name] = controller + def unregister_controller(self, name: str): + if not name: + raise ControllerManagerException("Must pass in a 'name'...") + + if not name in self.keys(): + raise ControllerManagerException( + f"Can't find controller registered with name of '{name}'..." + ) + + self.pop(name, None) + def get_controllers_key_list(self) -> list[str]: return self.keys() diff --git a/src/libs/controllers/controller_message_bus.py b/src/libs/controllers/controller_message_bus.py index a7a3e45..29e82a8 100644 --- a/src/libs/controllers/controller_message_bus.py +++ b/src/libs/controllers/controller_message_bus.py @@ -28,3 +28,6 @@ class ControllerMessageBus: def register_controller(self, name: str, controller): raise ControllerMessageBusException("Controller Message Bus 'register_controller' must be overriden by Controller Manager...") + + def unregister_controller(self, name: str): + raise ControllerMessageBusException("Controller Message Bus 'unregister_controller' must be overriden by Controller Manager...") diff --git a/src/libs/dto/code/events/__init__.py b/src/libs/dto/code/events/__init__.py index c473d0f..cfe8223 100644 --- a/src/libs/dto/code/events/__init__.py +++ b/src/libs/dto/code/events/__init__.py @@ -4,18 +4,21 @@ from .code_event import CodeEvent +from .toggle_plugins_ui_event import TogglePluginsUiEvent from .create_source_view_event import CreateSourceViewEvent from .register_completer_event import RegisterCompleterEvent from .unregister_completer_event import UnregisterCompleterEvent from .register_provider_event import RegisterProviderEvent from .unregister_provider_event import UnregisterProviderEvent from .register_command_event import RegisterCommandEvent +from .unregister_command_event import UnregisterCommandEvent from .file_externally_modified_event import FileExternallyModifiedEvent from .file_externally_deleted_event import FileExternallyDeletedEvent from .set_info_labels_event import SetInfoLabelsEvent from .populate_source_view_popup_event import PopulateSourceViewPopupEvent 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 .request_completion_event import RequestCompletionEvent @@ -34,6 +37,7 @@ from .removed_file_event import RemovedFileEvent from .saved_file_event import SavedFileEvent from .get_file_event import GetFileEvent +from .get_files_event import GetFilesEvent from .get_swap_file_event import GetSwapFileEvent from .add_new_file_event import AddNewFileEvent from .pop_file_event import PopFileEvent diff --git a/src/libs/dto/code/events/get_files_event.py b/src/libs/dto/code/events/get_files_event.py new file mode 100644 index 0000000..6ac2d3f --- /dev/null +++ b/src/libs/dto/code/events/get_files_event.py @@ -0,0 +1,13 @@ +# Python imports +from dataclasses import dataclass, field + +# Lib imports + +# Application imports +from .code_event import CodeEvent + + + +@dataclass +class GetFilesEvent(CodeEvent): + ... diff --git a/src/libs/dto/code/events/get_source_views_event.py b/src/libs/dto/code/events/get_source_views_event.py new file mode 100644 index 0000000..471e3eb --- /dev/null +++ b/src/libs/dto/code/events/get_source_views_event.py @@ -0,0 +1,13 @@ +# Python imports +from dataclasses import dataclass, field + +# Lib imports + +# Application imports +from .code_event import CodeEvent + + + +@dataclass +class GetSourceViewsEvent(CodeEvent): + ... diff --git a/src/libs/dto/code/events/toggle_plugins_ui_event.py b/src/libs/dto/code/events/toggle_plugins_ui_event.py new file mode 100644 index 0000000..095a015 --- /dev/null +++ b/src/libs/dto/code/events/toggle_plugins_ui_event.py @@ -0,0 +1,17 @@ +# Python imports +from dataclasses import dataclass, field + +# Lib imports +import gi +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports +from .code_event import CodeEvent + + + +@dataclass +class TogglePluginsUiEvent(CodeEvent): + ... diff --git a/src/libs/dto/code/events/unregister_command_event.py b/src/libs/dto/code/events/unregister_command_event.py new file mode 100644 index 0000000..0662dd0 --- /dev/null +++ b/src/libs/dto/code/events/unregister_command_event.py @@ -0,0 +1,20 @@ +# Python imports +from dataclasses import dataclass, field + +# Lib imports +import gi +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports +from .code_event import CodeEvent + + + +@dataclass +class UnregisterCommandEvent(CodeEvent): + command_name: str = "" + command: callable = None + binding_mode: str = "" + binding: str or list = "" diff --git a/src/libs/dto/plugins/manifest.py b/src/libs/dto/plugins/manifest.py index 54c7f26..03c1a9c 100644 --- a/src/libs/dto/plugins/manifest.py +++ b/src/libs/dto/plugins/manifest.py @@ -17,6 +17,7 @@ class Manifest: version: str = "0.0.1" support: str = "support@mail.com" pre_launch: bool = False + autoload: bool = True requests: Requests = field(default_factory = lambda: Requests()) def __post_init__(self): diff --git a/src/libs/event_factory.py b/src/libs/event_factory.py index 102822b..e8ccc43 100644 --- a/src/libs/event_factory.py +++ b/src/libs/event_factory.py @@ -36,6 +36,19 @@ class EventFactory(Singleton): logger.debug(f"Registered {i} event types:") + def unregister_events(self, events: dict): + i = 0 + for name, obj in events: + if not self._is_valid_event_class(obj): continue + + event_type = self._class_name_to_event_type(name) + + del self._event_classes[event_type] + Code_Event_Types.remove_event_class(name) + i += 1 + + logger.debug(f"Unregistered {i} event types:") + def create_event(self, event_type: str, **kwargs) -> BaseEvent: if event_type not in self._event_classes: raise ValueError(f"Unknown event type: {event_type}") @@ -80,6 +93,9 @@ class EventNamespace: def add_event_class(self, name: str, event_class: Type[BaseEvent]): setattr(self, name, event_class) + def remove_event_class(self, name: str): + delattr(self, name) + Code_Event_Types = EventNamespace() diff --git a/src/libs/singleton.py b/src/libs/singleton.py index 4fa22e2..cfe6534 100644 --- a/src/libs/singleton.py +++ b/src/libs/singleton.py @@ -12,21 +12,21 @@ class SingletonError(Exception): -T = TypeVar('T', bound='Singleton') +T = TypeVar('T', bound = 'Singleton') + + class Singleton: - __instance = None + _instances = {} def __new__(cls: Type[T], *args: Any, **kwargs: Any) -> T: - if cls.__instance is not None: - logger.debug(f"'{cls.__name__}' is a Singleton. Returning instance...") - return cls.__instance + if cls in cls._instances: return cls._instances[cls] - cls.__instance = super(Singleton, cls).__new__(cls) - return cls.__instance + instance = super().__new__(cls) + cls._instances[cls] = instance + return instance - def __init__(self) -> None: - if self.__instance is not None: - return - - super(Singleton, self).__init__() + @classmethod + def destroy(cls): + if cls in cls._instances: + del cls._instances[cls] diff --git a/src/plugins/controller.py b/src/plugins/controller.py index d09431b..6b327fc 100644 --- a/src/plugins/controller.py +++ b/src/plugins/controller.py @@ -4,25 +4,25 @@ import sys import importlib import traceback -from concurrent.futures import ThreadPoolExecutor from os.path import join from os.path import isdir # Lib imports import gi +from gi.repository import Gtk from gi.repository import GLib # Application imports +from libs.event_factory import Event_Factory, Code_Event_Types from libs.controllers.controller_base import ControllerBase - from libs.dto.plugins.manifest_meta import ManifestMeta - from libs.dto.base_event import BaseEvent from .manifest_manager import ManifestManager from .plugins_controller_mixin import PluginsControllerMixin from .plugin_reload_mixin import PluginReloadMixin from .plugin_context import PluginContext +from .plugins_ui import PluginsUI @@ -40,11 +40,12 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi # path = os.path.dirname(os.path.realpath(__file__)) # sys.path.insert(0, path) # NOTE: I think I'm not using this correctly... - self._plugin_collection: list = [] - - self._plugins_path: str = settings_manager.path_manager.get_plugins_path() + self.plugins_ui: PluginsUI = PluginsUI() self._manifest_manager: ManifestManager = ManifestManager() + self._plugin_collection: list = [] + self._plugins_path: str = settings_manager.path_manager.get_plugins_path() + self._set_plugins_watcher() @@ -52,6 +53,14 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi for manifest_meta in self._plugin_collection: manifest_meta.instance._controller_message(event) + if isinstance(event, Code_Event_Types.PopulateSourceViewPopupEvent): + event.menu.append( Gtk.SeparatorMenuItem() ) + item = Gtk.MenuItem(label = "Plugins") + item.connect("activate", self.toggle_plugins_ui) + event.menu.append(item) + elif isinstance(event, Code_Event_Types.TogglePluginsUiEvent): + self.toggle_plugins_ui() + def _collect_search_locations(self, path: str, locations: list): locations.append(path) for file in os.listdir(path): @@ -105,33 +114,46 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi ): if not is_pre_launch: GLib.idle_add( - self._run_with_pool, module, manifest_meta + self.execute_plugin, module, manifest_meta ) return - self._run_with_pool(module, manifest_meta) + self.execute_plugin(module, manifest_meta) - 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: + def pre_launch_plugins(self): logger.info(f"Loading pre-launch plugins...") manifest_metas: list = self._manifest_manager.get_pre_launch_plugins() self._load_plugins(manifest_metas, is_pre_launch = True) - def post_launch_plugins(self) -> None: + for manifest_meta in manifest_metas: + self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state) + + def post_launch_plugins(self): logger.info(f"Loading post-launch plugins...") manifest_metas: list = self._manifest_manager.get_post_launch_plugins() self._load_plugins(manifest_metas) + for manifest_meta in manifest_metas: + self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state) + + def manual_launch_plugins(self): + logger.info(f"Collecting manual-launch plugins...") + manifest_metas: list = self._manifest_manager.get_manual_launch_plugins() + + for manifest_meta in manifest_metas: + self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state) + + def toggle_plugin_load_state(self, widget, manifest_meta): + if manifest_meta.instance: + self._plugin_collection.remove(manifest_meta) + manifest_meta.instance.unload() + manifest_meta.instance = None + widget.set_label("Load") + return + + self._load_plugins( [manifest_meta] ) + widget.set_label("Unload") + def execute_plugin(self, module: type, manifest_meta: ManifestMeta): plugin = module.Plugin() plugin.plugin_context: PluginContext = self.create_plugin_context() @@ -148,15 +170,18 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi self._plugin_collection.append(manifest_meta) def create_plugin_context(self): - plugin_context: PluginContext = PluginContext() + plugin_context: PluginContext = PluginContext() - plugin_context.request_ui_element: callable = self.request_ui_element - plugin_context.emit: callable = self.emit - plugin_context.emit_to: callable = self.emit_to - plugin_context.emit_to_selected: callable = self.emit_to_selected - plugin_context.register_controller: callable = self.register_controller + plugin_context.request_ui_element: callable = self.request_ui_element + plugin_context.emit: callable = self.emit + plugin_context.emit_to: callable = self.emit_to + plugin_context.emit_to_selected: callable = self.emit_to_selected + plugin_context.register_controller: callable = self.register_controller + plugin_context.unregister_controller: callable = self.unregister_controller return plugin_context + def toggle_plugins_ui(self, widget = None): + self.plugins_ui.hide() if self.plugins_ui.is_visible() else self.plugins_ui.show() plugins_controller = PluginsController() diff --git a/src/plugins/manifest_manager.py b/src/plugins/manifest_manager.py index e41b92d..0f7add3 100644 --- a/src/plugins/manifest_manager.py +++ b/src/plugins/manifest_manager.py @@ -19,10 +19,12 @@ class ManifestMapperException(Exception): class ManifestManager: def __init__(self): - self._plugins_path = settings_manager.path_manager.get_plugins_path() + self._plugins_path: str = \ + settings_manager.path_manager.get_plugins_path() - self.pre_launch_manifests: list = [] - self.post_launch_manifests: list = [] + self.pre_launch_manifests: list = [] + self.post_launch_manifests: list = [] + self.manual_launch_manifests: list = [] self.load_manifests() @@ -37,7 +39,7 @@ class ManifestManager: ]: self.load(folder, path) - def load(self, folder, path): + def load(self, folder, path) -> ManifestMeta: manifest_pth = join(path, "manifest.json") if not os.path.exists(manifest_pth): @@ -52,14 +54,22 @@ class ManifestManager: manifest_meta.path = path manifest_meta.manifest = manifest + if not manifest.autoload: + self.manual_launch_manifests.append(manifest_meta) + return + if manifest.pre_launch: self.pre_launch_manifests.append(manifest_meta) else: self.post_launch_manifests.append(manifest_meta) - def get_pre_launch_plugins(self) -> dict: + return manifest_meta + + def get_pre_launch_plugins(self) -> list: return self.pre_launch_manifests - def get_post_launch_plugins(self) -> None: + def get_post_launch_plugins(self) -> list: return self.post_launch_manifests + def get_manual_launch_plugins(self) -> list: + return self.manual_launch_manifests diff --git a/src/plugins/plugin_context.py b/src/plugins/plugin_context.py index 3957c97..eb093e0 100644 --- a/src/plugins/plugin_context.py +++ b/src/plugins/plugin_context.py @@ -37,3 +37,6 @@ class PluginContext: def register_controller(self, name: str, controller): raise PluginContextException("Plugin Context 'register_controller' must be overridden...") + def unregister_controller(self, name: str): + raise PluginContextException("Plugin Context 'unregister_controller' must be overridden...") + diff --git a/src/plugins/plugin_reload_mixin.py b/src/plugins/plugin_reload_mixin.py index 2b256ff..eea9a9e 100644 --- a/src/plugins/plugin_reload_mixin.py +++ b/src/plugins/plugin_reload_mixin.py @@ -12,12 +12,12 @@ class PluginReloadMixin: _plugins_dir_watcher = None def _set_plugins_watcher(self) -> None: - self._plugins_dir_watcher = Gio.File.new_for_path( - self._plugins_path - ).monitor_directory( - Gio.FileMonitorFlags.WATCH_MOVES, - Gio.Cancellable() - ) + self._plugins_dir_watcher = \ + Gio.File.new_for_path( self._plugins_path ) \ + .monitor_directory( + Gio.FileMonitorFlags.WATCH_MOVES, + Gio.Cancellable() + ) self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) @@ -27,10 +27,40 @@ class PluginReloadMixin: eve_type = None, data = None ): - if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, - Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, - Gio.FileMonitorEvent.MOVED_OUT]: - self.reload_plugins(file) + if eve_type is Gio.FileMonitorEvent.RENAMED: + ... - def reload_plugins(self, file: str = None) -> None: - logger.info(f"Reloading plugins... stub.") + if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.MOVED_IN]: + self.add_plugin(file) + + if eve_type in [Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.MOVED_OUT]: + self.remove_plugin(file) + + def add_plugin(self, file: str) -> None: + logger.info(f"Adding plugin: {file.get_uri()}") + uri = file.get_uri() + path = uri.replace("file://", "") + folder = path.split("/")[-1] + manifest_meta = self._manifest_manager.load(folder, path) + + self._load_plugins( [manifest_meta] ) + self.plugins_ui.add_row(manifest_meta, self.toggle_plugin_load_state) + + def remove_plugin(self, file: str) -> None: + logger.info(f"Removing plugin: {file.get_uri()}") + for manifest_meta in self._plugin_collection[:]: + if not manifest_meta.folder in file.get_uri(): continue + + manifest_meta.instance.unload() + manifest_meta.instance = None + self._plugin_collection.remove(manifest_meta) + self.plugins_ui.remove_row(manifest_meta) + + if manifest_meta in self._manifest_manager.pre_launch_manifests: + self._manifest_manager.pre_launch_manifests.remove(manifest_meta) + elif manifest_meta in self._manifest_manager.post_launch_manifests: + self._manifest_manager.post_launch_manifests.remove(manifest_meta) + elif manifest_meta in self._manifest_manager.manual_launch_manifests: + self._manifest_manager.manual_launch_manifests.remove(manifest_meta) + + break diff --git a/src/plugins/plugin_types/plugin_base.py b/src/plugins/plugin_types/plugin_base.py index 92a819c..221643f 100644 --- a/src/plugins/plugin_types/plugin_base.py +++ b/src/plugins/plugin_types/plugin_base.py @@ -27,6 +27,9 @@ class PluginBase: def load(self): raise PluginBaseException("Plugin Base 'load' must be overriden by Plugin") + def unload(self): + raise PluginBaseException("Plugin Base 'unload' must be overriden by Plugin") + def run(self): raise PluginBaseException("Plugin Base 'run' must be overriden by Plugin") diff --git a/src/plugins/plugin_types/plugin_code.py b/src/plugins/plugin_types/plugin_code.py index 95aa9e9..3c6188d 100644 --- a/src/plugins/plugin_types/plugin_code.py +++ b/src/plugins/plugin_types/plugin_code.py @@ -34,6 +34,9 @@ class PluginCode(PluginBase): def register_controller(self, name: str, controller): return self.plugin_context.register_controller(name, controller) + def unregister_controller(self, name: str): + return self.plugin_context.unregister_controller(name) + def request_ui_element(self, element_id: str): return self.plugin_context.request_ui_element(element_id) diff --git a/src/plugins/plugins_ui.py b/src/plugins/plugins_ui.py new file mode 100644 index 0000000..34efa56 --- /dev/null +++ b/src/plugins/plugins_ui.py @@ -0,0 +1,100 @@ +# Python imports + +# Lib imports +import gi +from gi.repository import Gtk + +# Application imports + + + +class PluginsUI(Gtk.Dialog): + def __init__(self): + super(PluginsUI, self).__init__() + + self._setup_styling() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + + def _setup_styling(self): + header = Gtk.HeaderBar() + self.ctx = self.get_style_context() + self.ctx.add_class("plugin-ui") + + self.set_title("Plugins") + self.set_size_request(450, 530) + self.set_deletable(False) + self.set_skip_pager_hint(True) + self.set_skip_taskbar_hint(True) + + header.set_title("Plugins") + self.set_titlebar(header) + header.show() + + window = widget_registery.get_object("main-window") + self.set_transient_for(window) + + def _setup_signals(self): + ... + + def _subscribe_to_events(self): + ... + + def _load_widgets(self): + widget_registery.expose_object("plugin-ui", self) + + content_area = self.get_content_area() + scrolled_win = Gtk.ScrolledWindow() + viewport = Gtk.Viewport() + self.list_box = Gtk.ListBox() + + self.list_box.set_selection_mode( Gtk.SelectionMode.NONE ) + scrolled_win.set_vexpand(True) + + viewport.add(self.list_box) + scrolled_win.add(viewport) + content_area.add(scrolled_win) + + scrolled_win.show_all() + + def add_row(self, manifest_meta, callback: callable): + box = Gtk.Box() + plugin_lbl = Gtk.Label(label = manifest_meta.manifest.name) + author_lbl = Gtk.Label(label = manifest_meta.manifest.author) + version_lbl = Gtk.Label(label = manifest_meta.manifest.version) + is_autoload = manifest_meta.manifest.autoload + toggle_bttn = Gtk.ToggleButton(label = "Unload" if is_autoload else "Load") + + toggle_bttn.set_active(is_autoload) + plugin_lbl.set_hexpand(True) + box.set_hexpand(True) + version_lbl.set_margin_left(15) + version_lbl.set_margin_right(15) + toggle_bttn.set_size_request(120, -1) + + toggle_bttn.toggle_id = \ + toggle_bttn.connect("toggled", callback, manifest_meta) + + box.add(plugin_lbl) + box.add(author_lbl) + box.add(version_lbl) + box.add(toggle_bttn) + box.manifest_meta = manifest_meta + + box.show_all() + self.list_box.add(box) + + def remove_row(self, manifest_meta): + for row in self.list_box.get_children(): + child = row.get_children()[0] + if not child.manifest_meta == manifest_meta: continue + + child.manifest_meta = None + toggle_bttn = getattr(child, "toggle_bttn", None) + toggle_bttn.disconnect(toggle_bttn.toggle_id) + + self.list_box.remove(row) + box.destroy() + break