diff --git a/plugins/completers/example_completer/__init__.py b/plugins/code/commentzar/__init__.py similarity index 100% rename from plugins/completers/example_completer/__init__.py rename to plugins/code/commentzar/__init__.py diff --git a/plugins/completers/example_completer/__main__.py b/plugins/code/commentzar/__main__.py similarity index 100% rename from plugins/completers/example_completer/__main__.py rename to plugins/code/commentzar/__main__.py diff --git a/plugins/code/commentzar/commenter.py b/plugins/code/commentzar/commenter.py new file mode 100644 index 0000000..3724934 --- /dev/null +++ b/plugins/code/commentzar/commenter.py @@ -0,0 +1,66 @@ +# Python imports + +# Lib imports + +# Application imports +from .mixins.code_comment_tags_mixin import CodeCommentTagsMixin + + + +class Commenter(CodeCommentTagsMixin): + def __init__(self): + ... + + + def keyboard_tggl_comment(self, buffer): + language = buffer.get_language() + if language is None: return + + start_tag, end_tag = self.get_comment_tags(language) + # Note: Only handling line comment tag- no block comment option + if not start_tag and not end_tag: return + + bounds = buffer.get_selection_bounds() + if bounds: + self._bounds_comment( + start_tag, end_tag, bounds, buffer + ) + else: + self._line_comment(start_tag, end_tag, buffer) + + + def _line_comment(self, start_tag, end_tag, buffer): + start_itr = buffer.get_iter_at_mark( buffer.get_insert() ).copy() + end_itr = start_itr.copy() + if not start_itr.starts_line(): + start_itr.set_line_offset(0) + if not end_itr.ends_line(): + end_itr.forward_to_line_end() + + text = buffer.get_text(start_itr, end_itr, True) + text = text.replace(start_tag, "") if text.startswith(start_tag) else start_tag + text + + buffer.begin_user_action() + buffer.delete(start_itr, end_itr) + buffer.insert(start_itr, text) + buffer.end_user_action() + + + def _bounds_comment(self, start_tag, end_tag, bounds, buffer): + start_itr, end_itr = bounds + if not start_itr.starts_line(): + start_itr.set_line_offset(0) + if not end_itr.ends_line(): + end_itr.forward_to_line_end() + + text = buffer.get_text(start_itr, end_itr, True) + text = "\n".join( + line.replace(start_tag, "") if line.startswith(start_tag) else start_tag + line + for line in text.splitlines() + ) + + buffer.begin_user_action() + buffer.delete(start_itr, end_itr) + buffer.insert(start_itr, text) + buffer.end_user_action() + diff --git a/plugins/depricated/commentzar/manifest.json b/plugins/code/commentzar/manifest.json similarity index 51% rename from plugins/depricated/commentzar/manifest.json rename to plugins/code/commentzar/manifest.json index 8197cc3..4100da1 100644 --- a/plugins/depricated/commentzar/manifest.json +++ b/plugins/code/commentzar/manifest.json @@ -4,8 +4,5 @@ "credit": "Hamad Al Marri", "version": "0.0.1", "support": "", - "requests": { - "pass_events": true, - "bind_keys": ["Commentzar||keyboard_tggl_comment:slash"] - } + "requests": {} } diff --git a/plugins/code/commentzar/mixins/__init__.py b/plugins/code/commentzar/mixins/__init__.py new file mode 100644 index 0000000..7987b9a --- /dev/null +++ b/plugins/code/commentzar/mixins/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module Mixin +""" diff --git a/plugins/code/commentzar/mixins/code_comment_tags_mixin.py b/plugins/code/commentzar/mixins/code_comment_tags_mixin.py new file mode 100755 index 0000000..2f962d5 --- /dev/null +++ b/plugins/code/commentzar/mixins/code_comment_tags_mixin.py @@ -0,0 +1,30 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class CodeCommentTagsMixin: + def get_comment_tags(self, language): + start_tag, end_tag = self.get_line_comment_tags(language) + if (start_tag, end_tag) == (None, None): + start_tag, end_tag = self.get_block_comment_tags(language) + + return start_tag, end_tag + + def get_block_comment_tags(self, language): + start_tag = language.get_metadata('block-comment-start') + end_tag = language.get_metadata('block-comment-end') + + if start_tag and end_tag: return (start_tag, end_tag) + + return (None, None) + + def get_line_comment_tags(self, language): + start_tag = language.get_metadata('line-comment-start') + + if start_tag: return (start_tag, None) + + return (None, None) diff --git a/plugins/code/commentzar/plugin.py b/plugins/code/commentzar/plugin.py new file mode 100644 index 0000000..e3b1597 --- /dev/null +++ b/plugins/code/commentzar/plugin.py @@ -0,0 +1,49 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + +from plugins.plugin_types import PluginCode + +from .commenter import Commenter + + + +commenter = Commenter() + + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + + def _controller_message(self, event: Code_Event_Types.CodeEvent): + ... + + def load(self): + event = Event_Factory.create_event("register_command", + command_name = "keyboard_tggl_comment", + command = Handler, + binding_mode = "released", + binding = "slash" + ) + + self.message_to("source_views", event) + + def run(self): + ... + + +class Handler: + @staticmethod + def execute( + view: any + ): + logger.debug("Command: Toggle Comment") + commenter.keyboard_tggl_comment( view.get_buffer() ) \ No newline at end of file diff --git a/plugins/completers/lsp_completer/__init__.py b/plugins/code/completers/example_completer/__init__.py similarity index 100% rename from plugins/completers/lsp_completer/__init__.py rename to plugins/code/completers/example_completer/__init__.py diff --git a/plugins/completers/lsp_completer/__main__.py b/plugins/code/completers/example_completer/__main__.py similarity index 100% rename from plugins/completers/lsp_completer/__main__.py rename to plugins/code/completers/example_completer/__main__.py diff --git a/plugins/completers/example_completer/manifest.json b/plugins/code/completers/example_completer/manifest.json similarity index 100% rename from plugins/completers/example_completer/manifest.json rename to plugins/code/completers/example_completer/manifest.json diff --git a/plugins/completers/example_completer/plugin.py b/plugins/code/completers/example_completer/plugin.py similarity index 83% rename from plugins/completers/example_completer/plugin.py rename to plugins/code/completers/example_completer/plugin.py index f13331f..479fdd0 100644 --- a/plugins/completers/example_completer/plugin.py +++ b/plugins/code/completers/example_completer/plugin.py @@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from libs.dto.base_event import BaseEvent -from libs.event_factory import Event_Factory +from libs.event_factory import Event_Factory, Code_Event_Types from plugins.plugin_types import PluginCode @@ -22,7 +21,7 @@ class Plugin(PluginCode): self.provider: Provider = None - def _controller_message(self, event: BaseEvent): + def _controller_message(self, event: Code_Event_Types.CodeEvent): ... def load(self): diff --git a/plugins/completers/example_completer/provider.py b/plugins/code/completers/example_completer/provider.py similarity index 89% rename from plugins/completers/example_completer/provider.py rename to plugins/code/completers/example_completer/provider.py index a7d27bb..d736210 100644 --- a/plugins/completers/example_completer/provider.py +++ b/plugins/code/completers/example_completer/provider.py @@ -30,13 +30,13 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider): return 'Example Code Completion' def do_match(self, context): - # word = context.get_word() - # if not word or len(word) < 2: return False + # Note: If provider is in interactive activation then need to check + # view focus as otherwise non focus views start trying to grab it. + completion = context.get_property("completion") + if not completion.get_view().has_focus(): return - """ Get whether the provider match the context of completion detailed in context. """ word = self.response_cache.get_word(context) if not word or len(word) < 2: return False - return True def do_get_priority(self): diff --git a/plugins/completers/example_completer/provider_response_cache.py b/plugins/code/completers/example_completer/provider_response_cache.py similarity index 92% rename from plugins/completers/example_completer/provider_response_cache.py rename to plugins/code/completers/example_completer/provider_response_cache.py index 87af042..bc6d50b 100644 --- a/plugins/completers/example_completer/provider_response_cache.py +++ b/plugins/code/completers/example_completer/provider_response_cache.py @@ -1,4 +1,5 @@ # Python imports +from concurrent.futures import ThreadPoolExecutor import re # Lib imports @@ -21,6 +22,9 @@ class ProviderResponseCache(ProviderResponseCacheBase): def __init__(self): super(ProviderResponseCache, self).__init__() + # Note: Using asyncio.run causes a keyboard trap that prevents app + # closure from terminal. ThreadPoolExecutor seems to not have such issues... + self.executor = ThreadPoolExecutor(max_workers = 1) self.matchers: dict = { "hello": { "label": "Hello, World!", diff --git a/plugins/completers/python_completer/__init__.py b/plugins/code/completers/lsp_completer/__init__.py similarity index 100% rename from plugins/completers/python_completer/__init__.py rename to plugins/code/completers/lsp_completer/__init__.py diff --git a/plugins/completers/python_completer/__main__.py b/plugins/code/completers/lsp_completer/__main__.py similarity index 100% rename from plugins/completers/python_completer/__main__.py rename to plugins/code/completers/lsp_completer/__main__.py diff --git a/plugins/completers/lsp_completer/manifest.json b/plugins/code/completers/lsp_completer/manifest.json similarity index 100% rename from plugins/completers/lsp_completer/manifest.json rename to plugins/code/completers/lsp_completer/manifest.json diff --git a/plugins/completers/lsp_completer/plugin.py b/plugins/code/completers/lsp_completer/plugin.py similarity index 84% rename from plugins/completers/lsp_completer/plugin.py rename to plugins/code/completers/lsp_completer/plugin.py index 7de452e..b37a718 100644 --- a/plugins/completers/lsp_completer/plugin.py +++ b/plugins/code/completers/lsp_completer/plugin.py @@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from libs.dto.base_event import BaseEvent -from libs.event_factory import Event_Factory +from libs.event_factory import Event_Factory, Code_Event_Types from plugins.plugin_types import PluginCode @@ -22,7 +21,7 @@ class Plugin(PluginCode): self.provider: Provider = None - def _controller_message(self, event: BaseEvent): + def _controller_message(self, event: Code_Event_Types.CodeEvent): ... def load(self): diff --git a/plugins/completers/lsp_completer/provider.py b/plugins/code/completers/lsp_completer/provider.py similarity index 100% rename from plugins/completers/lsp_completer/provider.py rename to plugins/code/completers/lsp_completer/provider.py diff --git a/plugins/completers/lsp_completer/provider_response_cache.py b/plugins/code/completers/lsp_completer/provider_response_cache.py similarity index 75% rename from plugins/completers/lsp_completer/provider_response_cache.py rename to plugins/code/completers/lsp_completer/provider_response_cache.py index b1bf2d2..58288d1 100644 --- a/plugins/completers/lsp_completer/provider_response_cache.py +++ b/plugins/code/completers/lsp_completer/provider_response_cache.py @@ -1,4 +1,5 @@ # Python imports +from concurrent.futures import ThreadPoolExecutor # Lib imports import gi @@ -17,9 +18,13 @@ class ProviderResponseCache(ProviderResponseCacheBase): def __init__(self): super(ProviderResponseCache, self).__init__() + self.executor = ThreadPoolExecutor(max_workers = 1) + self.matchers: dict = {} + def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent): - ... + buffer = event.file.buffer + self.executor.submit(self._handle_change, buffer) def process_file_close(self, event: Code_Event_Types.RemovedFileEvent): ... @@ -28,8 +33,13 @@ class ProviderResponseCache(ProviderResponseCacheBase): ... def process_file_change(self, event: Code_Event_Types.TextChangedEvent): + buffer = event.file.buffer + self.executor.submit(self._handle_change, buffer) + + def _handle_change(self, buffer): ... + def filter(self, word: str) -> list[dict]: return [] diff --git a/plugins/completers/snippets_completer/__init__.py b/plugins/code/completers/python_completer/__init__.py similarity index 100% rename from plugins/completers/snippets_completer/__init__.py rename to plugins/code/completers/python_completer/__init__.py diff --git a/plugins/completers/snippets_completer/__main__.py b/plugins/code/completers/python_completer/__main__.py similarity index 100% rename from plugins/completers/snippets_completer/__main__.py rename to plugins/code/completers/python_completer/__main__.py diff --git a/plugins/completers/python_completer/manifest.json b/plugins/code/completers/python_completer/manifest.json similarity index 100% rename from plugins/completers/python_completer/manifest.json rename to plugins/code/completers/python_completer/manifest.json diff --git a/plugins/completers/python_completer/plugin.py b/plugins/code/completers/python_completer/plugin.py similarity index 83% rename from plugins/completers/python_completer/plugin.py rename to plugins/code/completers/python_completer/plugin.py index 1d8f81d..5be143b 100644 --- a/plugins/completers/python_completer/plugin.py +++ b/plugins/code/completers/python_completer/plugin.py @@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from libs.dto.base_event import BaseEvent -from libs.event_factory import Event_Factory +from libs.event_factory import Event_Factory, Code_Event_Types from plugins.plugin_types import PluginCode @@ -22,7 +21,7 @@ class Plugin(PluginCode): self.provider: Provider = None - def _controller_message(self, event: BaseEvent): + def _controller_message(self, event: Code_Event_Types.CodeEvent): ... def load(self): diff --git a/plugins/completers/python_completer/provider.py b/plugins/code/completers/python_completer/provider.py similarity index 100% rename from plugins/completers/python_completer/provider.py rename to plugins/code/completers/python_completer/provider.py diff --git a/plugins/completers/python_completer/provider_response_cache.py b/plugins/code/completers/python_completer/provider_response_cache.py similarity index 100% rename from plugins/completers/python_completer/provider_response_cache.py rename to plugins/code/completers/python_completer/provider_response_cache.py diff --git a/plugins/completers/words_completer/__init__.py b/plugins/code/completers/snippets_completer/__init__.py similarity index 100% rename from plugins/completers/words_completer/__init__.py rename to plugins/code/completers/snippets_completer/__init__.py diff --git a/plugins/completers/words_completer/__main__.py b/plugins/code/completers/snippets_completer/__main__.py similarity index 100% rename from plugins/completers/words_completer/__main__.py rename to plugins/code/completers/snippets_completer/__main__.py diff --git a/plugins/completers/snippets_completer/cson/__init__.py b/plugins/code/completers/snippets_completer/cson/__init__.py similarity index 100% rename from plugins/completers/snippets_completer/cson/__init__.py rename to plugins/code/completers/snippets_completer/cson/__init__.py diff --git a/plugins/completers/snippets_completer/cson/parser.py b/plugins/code/completers/snippets_completer/cson/parser.py similarity index 100% rename from plugins/completers/snippets_completer/cson/parser.py rename to plugins/code/completers/snippets_completer/cson/parser.py diff --git a/plugins/completers/snippets_completer/cson/speg/__init__.py b/plugins/code/completers/snippets_completer/cson/speg/__init__.py similarity index 100% rename from plugins/completers/snippets_completer/cson/speg/__init__.py rename to plugins/code/completers/snippets_completer/cson/speg/__init__.py diff --git a/plugins/completers/snippets_completer/cson/speg/peg.py b/plugins/code/completers/snippets_completer/cson/speg/peg.py similarity index 100% rename from plugins/completers/snippets_completer/cson/speg/peg.py rename to plugins/code/completers/snippets_completer/cson/speg/peg.py diff --git a/plugins/completers/snippets_completer/cson/writer.py b/plugins/code/completers/snippets_completer/cson/writer.py similarity index 100% rename from plugins/completers/snippets_completer/cson/writer.py rename to plugins/code/completers/snippets_completer/cson/writer.py diff --git a/plugins/completers/snippets_completer/manifest.json b/plugins/code/completers/snippets_completer/manifest.json similarity index 100% rename from plugins/completers/snippets_completer/manifest.json rename to plugins/code/completers/snippets_completer/manifest.json diff --git a/plugins/completers/snippets_completer/plugin.py b/plugins/code/completers/snippets_completer/plugin.py similarity index 83% rename from plugins/completers/snippets_completer/plugin.py rename to plugins/code/completers/snippets_completer/plugin.py index 100c641..45f7f17 100644 --- a/plugins/completers/snippets_completer/plugin.py +++ b/plugins/code/completers/snippets_completer/plugin.py @@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from libs.dto.base_event import BaseEvent -from libs.event_factory import Event_Factory +from libs.event_factory import Event_Factory, Code_Event_Types from plugins.plugin_types import PluginCode @@ -22,7 +21,7 @@ class Plugin(PluginCode): self.provider: Provider = None - def _controller_message(self, event: BaseEvent): + def _controller_message(self, event: Code_Event_Types.CodeEvent): ... def load(self): diff --git a/plugins/completers/snippets_completer/provider.py b/plugins/code/completers/snippets_completer/provider.py similarity index 100% rename from plugins/completers/snippets_completer/provider.py rename to plugins/code/completers/snippets_completer/provider.py diff --git a/plugins/completers/snippets_completer/provider_response_cache.py b/plugins/code/completers/snippets_completer/provider_response_cache.py similarity index 100% rename from plugins/completers/snippets_completer/provider_response_cache.py rename to plugins/code/completers/snippets_completer/provider_response_cache.py diff --git a/plugins/completers/snippets_completer/snippets.cson b/plugins/code/completers/snippets_completer/snippets.cson similarity index 100% rename from plugins/completers/snippets_completer/snippets.cson rename to plugins/code/completers/snippets_completer/snippets.cson diff --git a/plugins/depricated/commentzar/__init__.py b/plugins/code/completers/words_completer/__init__.py similarity index 100% rename from plugins/depricated/commentzar/__init__.py rename to plugins/code/completers/words_completer/__init__.py diff --git a/plugins/depricated/commentzar/__main__.py b/plugins/code/completers/words_completer/__main__.py similarity index 100% rename from plugins/depricated/commentzar/__main__.py rename to plugins/code/completers/words_completer/__main__.py diff --git a/plugins/completers/words_completer/alt_provider.py b/plugins/code/completers/words_completer/alt_provider.py similarity index 100% rename from plugins/completers/words_completer/alt_provider.py rename to plugins/code/completers/words_completer/alt_provider.py diff --git a/plugins/completers/words_completer/manifest.json b/plugins/code/completers/words_completer/manifest.json similarity index 100% rename from plugins/completers/words_completer/manifest.json rename to plugins/code/completers/words_completer/manifest.json diff --git a/plugins/completers/words_completer/plugin.py b/plugins/code/completers/words_completer/plugin.py similarity index 83% rename from plugins/completers/words_completer/plugin.py rename to plugins/code/completers/words_completer/plugin.py index abb4acf..937cb00 100644 --- a/plugins/completers/words_completer/plugin.py +++ b/plugins/code/completers/words_completer/plugin.py @@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports -from libs.dto.base_event import BaseEvent -from libs.event_factory import Event_Factory +from libs.event_factory import Event_Factory, Code_Event_Types from plugins.plugin_types import PluginCode @@ -22,7 +21,7 @@ class Plugin(PluginCode): self.provider: Provider = None - def _controller_message(self, event: BaseEvent): + def _controller_message(self, event: Code_Event_Types.CodeEvent): ... def load(self): diff --git a/plugins/completers/words_completer/provider.py b/plugins/code/completers/words_completer/provider.py similarity index 89% rename from plugins/completers/words_completer/provider.py rename to plugins/code/completers/words_completer/provider.py index b0d6bbc..845acc8 100644 --- a/plugins/completers/words_completer/provider.py +++ b/plugins/code/completers/words_completer/provider.py @@ -30,6 +30,11 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider): return 'Words Completion' def do_match(self, context): + # Note: If provider is in interactive activation then need to check + # view focus as otherwise non focus views start trying to grab it. + completion = context.get_property("completion") + if not completion.get_view().has_focus(): return + word = self.response_cache.get_word(context) if not word or len(word) < 2: return False return True diff --git a/plugins/completers/words_completer/provider_response_cache.py b/plugins/code/completers/words_completer/provider_response_cache.py similarity index 88% rename from plugins/completers/words_completer/provider_response_cache.py rename to plugins/code/completers/words_completer/provider_response_cache.py index 00a20f6..5a55313 100644 --- a/plugins/completers/words_completer/provider_response_cache.py +++ b/plugins/code/completers/words_completer/provider_response_cache.py @@ -1,5 +1,5 @@ # Python imports -import asyncio +from concurrent.futures import ThreadPoolExecutor # Lib imports import gi @@ -24,7 +24,8 @@ class ProviderResponseCache(ProviderResponseCacheBase): def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent): buffer = event.file.buffer - asyncio.run( self._handle_change(buffer) ) + with ThreadPoolExecutor(max_workers = 1) as executor: + executor.submit(self._handle_change, buffer) def process_file_close(self, event: Code_Event_Types.RemovedFileEvent): self.matchers[event.file.buffer] = set() @@ -35,13 +36,14 @@ class ProviderResponseCache(ProviderResponseCacheBase): def process_file_change(self, event: Code_Event_Types.TextChangedEvent): buffer = event.file.buffer - asyncio.run( self._handle_change(buffer) ) + with ThreadPoolExecutor(max_workers = 1) as executor: + executor.submit(self._handle_change, buffer) - async def _handle_change(self, buffer): + def _handle_change(self, buffer): start_itr = buffer.get_start_iter() end_itr = buffer.get_end_iter() data = buffer.get_text(start_itr, end_itr, False) - + if not data: GLib.idle_add(self.load_empty_set, buffer) return @@ -67,6 +69,9 @@ class ProviderResponseCache(ProviderResponseCacheBase): buffer = self.get_iter_correctly(context).get_buffer() word = self.get_word(context).rstrip() + if not buffer in self.matchers: + self.matchers[buffer] = set() + response: list[dict] = [] for entry in self.matchers[buffer]: if not entry.rstrip().startswith(word): continue @@ -81,7 +86,6 @@ class ProviderResponseCache(ProviderResponseCacheBase): return response - def load_empty_set(self, buffer): self.matchers[buffer] = set() diff --git a/plugins/depricated/search_replace/__init__.py b/plugins/code/search_replace/__init__.py similarity index 100% rename from plugins/depricated/search_replace/__init__.py rename to plugins/code/search_replace/__init__.py diff --git a/plugins/depricated/search_replace/__main__.py b/plugins/code/search_replace/__main__.py similarity index 100% rename from plugins/depricated/search_replace/__main__.py rename to plugins/code/search_replace/__main__.py diff --git a/plugins/code/search_replace/images/only-in-selection.png b/plugins/code/search_replace/images/only-in-selection.png new file mode 100644 index 0000000..3c35fc3 Binary files /dev/null and b/plugins/code/search_replace/images/only-in-selection.png differ diff --git a/plugins/code/search_replace/images/whole-word.png b/plugins/code/search_replace/images/whole-word.png new file mode 100644 index 0000000..d147682 Binary files /dev/null and b/plugins/code/search_replace/images/whole-word.png differ diff --git a/plugins/code/search_replace/manifest.json b/plugins/code/search_replace/manifest.json new file mode 100644 index 0000000..166bb0e --- /dev/null +++ b/plugins/code/search_replace/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Search/Replace", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/code/search_replace/mixins/__init__.py b/plugins/code/search_replace/mixins/__init__.py new file mode 100644 index 0000000..da03c94 --- /dev/null +++ b/plugins/code/search_replace/mixins/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module Mixins +""" diff --git a/plugins/code/search_replace/mixins/replace_mixin.py b/plugins/code/search_replace/mixins/replace_mixin.py new file mode 100644 index 0000000..288f0e4 --- /dev/null +++ b/plugins/code/search_replace/mixins/replace_mixin.py @@ -0,0 +1,50 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class ReplaceMixin: + + def _replace_word( + self, + current_index: int, + to_text: str, + buffer: any + ): + self.clear_highlight(buffer) + + start_itr, end_itr = self.matches[current_index] + self.active_view.scroll_to_iter(end_itr, 0.2, False, 0, 0) + + buffer.begin_user_action() + buffer.delete(start_itr, end_itr) + buffer.insert(start_itr, to_text) + buffer.end_user_action() + + def _replace_all_words(self, to_text: str, buffer: any): + marks: list = [] + + for start_itr, end_itr in self.matches: + start_mark = buffer.create_mark(None, start_itr, left_gravity = True) + end_mark = buffer.create_mark(None, end_itr, left_gravity = False) + marks.append((start_mark, end_mark)) + + buffer.begin_user_action() + + for start_mark, end_mark in reversed(marks): + start_itr = buffer.get_iter_at_mark(start_mark) + end_itr = buffer.get_iter_at_mark(end_mark) + + buffer.delete(start_itr, end_itr) + buffer.insert(start_itr, to_text) + + buffer.end_user_action() + + for start_mark, end_mark in marks: + buffer.delete_mark(start_mark) + buffer.delete_mark(end_mark) + + self.find_entry.grab_focus() diff --git a/plugins/code/search_replace/mixins/search_mixin.py b/plugins/code/search_replace/mixins/search_mixin.py new file mode 100644 index 0000000..52abc84 --- /dev/null +++ b/plugins/code/search_replace/mixins/search_mixin.py @@ -0,0 +1,79 @@ +# Python imports +from contextlib import suppress + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + + +class SearchMixin: + def is_word_char(self, ch): + return ch.isalnum() or ch == "_" + + def is_whole_word(self, start_itr, end_itr, buffer): + if start_itr.backward_char(): + prev_char = start_itr.get_char() + if self.is_word_char(prev_char): + return False + + # if end_itr.forward_char(): + # next_char = end_itr.get_char() + # if self.is_word_char(next_char): + # return False + next_char = end_itr.get_char() + if self.is_word_char(next_char): + return False + + return True + + + def _find_all_matches(self, search_text, buffer): + self.matches.clear() + self.current_index = -1 + + start_itr = buffer.get_start_iter() + end_itr = buffer.get_end_iter() + + case_mode = Gtk.TextSearchFlags.CASE_INSENSITIVE if not self.mode_bttn_box.match_case else Gtk.TextSearchFlags.TEXT_ONLY + whole_word = self.mode_bttn_box.whole_word + if self.mode_bttn_box.in_selection: + with suppress(Exception): + start_itr, end_itr = buffer.get_selection_bounds() + + while True: + match = start_itr.forward_search( + search_text, + case_mode, + end_itr + ) + + if not match: break + + match_start, match_end = match + if whole_word and not self.is_whole_word(match_start.copy(), match_end.copy(), buffer): + start_itr = match_end + continue + + self.matches.append( + (match_start.copy(), match_end.copy()) + ) + + start_itr = match_end + + def _highlight_all_matches(self, buffer): + self.clear_highlight(buffer) + + for start_itr, end_itr in self.matches: + buffer.apply_tag(self.highlight_tag, start_itr, end_itr) + + def _search_for_next_word(self, buffer): + self.current_index = (self.current_index + 1) % len(self.matches) + self._highlight_current(self.current_index, buffer) + + def _search_for_prev_word(self, buffer): + self.current_index = (self.current_index - 1) % len(self.matches) + self._highlight_current(self.current_index, buffer) diff --git a/plugins/code/search_replace/mixins/search_replace_mixin.py b/plugins/code/search_replace/mixins/search_replace_mixin.py new file mode 100644 index 0000000..a68ff16 --- /dev/null +++ b/plugins/code/search_replace/mixins/search_replace_mixin.py @@ -0,0 +1,88 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk + +# Application imports +from .search_mixin import SearchMixin +from .replace_mixin import ReplaceMixin + + + +class SearchReplaceMixin(SearchMixin, ReplaceMixin): + + def _find_entry_focus_in_event(self, entry, event): + search_text = entry.get_text() + buffer = self.active_view.get_buffer() + + if buffer.get_has_selection() and not search_text: + if not self.mode_bttn_box.in_selection: + start_itr, end_itr = buffer.get_selection_bounds() + + entry.set_text( + buffer.get_text( + start_itr, + end_itr, + include_hidden_chars = False + ) + ) + + return + + def _find_entry_search_change(self, entry): + search_text = entry.get_text() + buffer = self.active_view.get_buffer() + self.highlight_tag = buffer.get_tag_table().lookup("search-highlight") + + if not search_text: + self.clear_highlight(buffer) + return + + self._find_all_matches(search_text, buffer) + self._highlight_all_matches(buffer) + + def _find_entry_activate(self, entry): + self._find_entry_next_match(entry) + + def _find_entry_next_match(self, entry): + search_text = entry.get_text() + + if not search_text: return + + buffer = self.active_view.get_buffer() + self._search_for_next_word(buffer) + + def _find_entry_previous_match(self, entry): + search_text = entry.get_text() + + if not search_text: return + + buffer = self.active_view.get_buffer() + self._search_for_prev_word(buffer) + + def _replace_entry_activate(self, entry): + to_text = entry.get_text() + + if not to_text: return + + buffer = self.active_view.get_buffer() + self._replace_word(self.current_index, to_text, buffer) + self._find_entry_search_change(self.find_entry) + + def _replace_all_activate(self, entry): + to_text = entry.get_text() + + if not to_text: return + + buffer = self.active_view.get_buffer() + self._replace_all_words(to_text, buffer) + + def _highlight_current(self, current_index, buffer): + self.clear_highlight(buffer) + + start_itr, end_itr = self.matches[current_index] + buffer.apply_tag(self.highlight_tag, start_itr, end_itr) + + self.active_view.scroll_to_iter(end_itr, 0.2, False, 0, 0) diff --git a/plugins/code/search_replace/mode_buttons.py b/plugins/code/search_replace/mode_buttons.py new file mode 100644 index 0000000..245899e --- /dev/null +++ b/plugins/code/search_replace/mode_buttons.py @@ -0,0 +1,75 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + + +class ModeException(Exception): + ... + + + +class ModeButtons(Gtk.ButtonBox): + def __init__(self): + super(ModeButtons, self).__init__() + + self.use_regex: bool = False + self.match_case: bool = False + self.in_selection: bool = False + self.whole_word: bool = False + + self._setup_styling() + self._setup_signals() + self._load_widgets() + + + def _setup_styling(self): + ctx = self.get_style_context() + ctx.add_class("search-replace-mode-buttons") + + def _setup_signals(self): + ... + + def _load_widgets(self): + use_regex_bttn = Gtk.ToggleButton(label = ".*") + match_case_bttn = Gtk.ToggleButton(label = "Aa") + in_selection_bttn = Gtk.ToggleButton() + whole_word_bttn = Gtk.ToggleButton() + + use_regex_bttn.set_sensitive(False) + + use_regex_bttn.set_tooltip_text("Use Regex") + match_case_bttn.set_tooltip_text("Match Case") + in_selection_bttn.set_tooltip_text("Only In Selection") + whole_word_bttn.set_tooltip_text("Whole Word") + + use_regex_bttn.connect("toggled", self._toggled_button, "use_regex") + match_case_bttn.connect("toggled", self._toggled_button, "match_case") + in_selection_bttn.connect("toggled", self._toggled_button, "in_selection") + whole_word_bttn.connect("toggled", self._toggled_button, "whole_word") + + in_selection_bttn.set_image( + Gtk.Image.new_from_file("images/only-in-selection.png") + ) + whole_word_bttn.set_image( + Gtk.Image.new_from_file("images/whole-word.png") + ) + + self.add(use_regex_bttn) + self.add(match_case_bttn) + self.add(in_selection_bttn) + self.add(whole_word_bttn) + + def _toggled_button(self, toggle_button, mode: str): + setattr(self, mode, not getattr(self, mode)) + self.request_update() + + def request_update(self): + raise ModeException("Must by 'monkey' patched from search_replace.py") + + diff --git a/plugins/code/search_replace/plugin.py b/plugins/code/search_replace/plugin.py new file mode 100644 index 0000000..864c3a6 --- /dev/null +++ b/plugins/code/search_replace/plugin.py @@ -0,0 +1,65 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + +from plugins.plugin_types import PluginCode + +from .search_replace import SearchReplace + + + +search_replace = SearchReplace() + + + +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.FocusedViewEvent): + self._handle_view_change(event) + + def _handle_view_change(self, event: Code_Event_Types.FocusedViewEvent): + if search_replace.is_visible(): + buffer = search_replace.active_view.get_buffer() + search_replace.clear_highlight(buffer) + + search_replace.active_view = event.view + + if search_replace.is_visible(): + search_replace._find_entry_search_change( + search_replace.find_entry + ) + + def load(self): + footer = self.requests_ui_element("footer-container") + footer.add( search_replace ) + + event = Event_Factory.create_event("register_command", + command_name = "search_replace", + command = Handler, + binding_mode = "released", + binding = "f" + ) + + self.message_to("source_views", event) + + def run(self): + ... + + +class Handler: + @staticmethod + def execute( + view: any + ): + logger.debug("Command: Search/Replace") + search_replace.hide() if search_replace.is_visible() else search_replace.show() diff --git a/plugins/code/search_replace/search_replace.py b/plugins/code/search_replace/search_replace.py new file mode 100644 index 0000000..4366f53 --- /dev/null +++ b/plugins/code/search_replace/search_replace.py @@ -0,0 +1,152 @@ +# 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 .mixins.search_replace_mixin import SearchReplaceMixin + +from .mode_buttons import ModeButtons + + + +class SearchReplace(Gtk.Grid, SearchReplaceMixin): + def __init__(self): + super(SearchReplace, self).__init__() + + self.active_view = None + self.highlight_tag = None + self.matches: list[tuple] = [] + + self._setup_styling() + self._setup_signals() + self._load_widgets() + + self.show_all() + self.hide() + + + def _setup_styling(self): + ctx = self.get_style_context() + ctx.add_class("search-replace") + + self.set_hexpand(True) + self.set_column_spacing(15) + self.set_row_spacing(15) + self.set_row_spacing(15) + + self.set_margin_start(15) + self.set_margin_end(15) + self.set_margin_top(15) + self.set_margin_bottom(15) + + def _setup_signals(self): + self.connect("show", self._handle_show) + self.connect("hide", self._handle_hide) + + def _load_widgets(self): + self.stateus_lbl = Gtk.Label(label = "Find in Current Buffer") + self.find_options_lbl = Gtk.Label(label = "Finding with Options: Case Insensitive") + self.mode_bttn_box = ModeButtons() + + self.find_entry = Gtk.SearchEntry() + self.replace_entry = Gtk.SearchEntry() + find_bttn = Gtk.Button(label = "Find") + find_all_bttn = Gtk.Button(label = "Find All") + replace_bttn = Gtk.Button(label = "Replace") + replace_all_bttn = Gtk.Button(label = "Replace All") + + self.find_entry.set_hexpand(True) + self.replace_entry.set_hexpand(True) + self.find_entry.set_max_width_chars(16) + self.replace_entry.set_max_width_chars(16) + self.find_entry.set_placeholder_text("Find in current buffer...") + self.replace_entry.set_placeholder_text("Replace in current buffer...") + + self.mode_bttn_box.request_update = self.request_update + + self.find_entry.connect("focus-in-event", self._find_entry_focus_in_event) + self.find_entry.connect("key-release-event", self._find_entry_key_release_event) + self.find_entry.connect("activate", self._find_entry_activate) + self.find_entry.connect("search-changed", self._find_entry_search_change) + self.find_entry.connect("next-match", self._find_entry_next_match) + self.find_entry.connect("previous-match", self._find_entry_previous_match) + + self.replace_entry.connect("key-release-event", self._replace_entry_key_release_event) + self.replace_entry.connect("activate", self._replace_entry_activate) + + find_bttn.connect( + "clicked", + lambda button: self._find_entry_next_match(self.find_entry) + ) + find_all_bttn.connect( + "clicked", + lambda button: self._find_entry_search_change(self.find_entry) + ) + replace_bttn.connect( + "clicked", + lambda button: self._replace_entry_activate(self.replace_entry) + ) + replace_all_bttn.connect( + "clicked", + lambda button: self._replace_all_activate(self.replace_entry) + ) + + self.attach(child = self.stateus_lbl, left = 0, top = 0, width = 2, height = 1) + self.attach(child = self.find_options_lbl, left = 2, top = 0, width = 2, height = 1) + self.attach(child = self.mode_bttn_box, left = 4, top = 0, width = 2, height = 1) + + self.attach(child = self.find_entry, left = 0, top = 1, width = 4, height = 1) + self.attach(child = find_bttn, left = 4, top = 1, width = 1, height = 1) + self.attach(child = find_all_bttn, left = 5, top = 1, width = 1, height = 1) + + self.attach(child = self.replace_entry, left = 0, top = 2, width = 4, height = 1) + self.attach(child = replace_bttn, left = 4, top = 2, width = 1, height = 1) + self.attach(child = replace_all_bttn, left = 5, top = 2, width = 1, height = 1) + + def _handle_show(self, widget): + self.find_entry.set_text("") + self.find_entry.grab_focus() + + def _handle_hide(self, widget): + if not self.active_view: return + + buffer = self.active_view.get_buffer() + self.clear_highlight(buffer) + self.active_view.grab_focus() + + def request_update(self): + self._find_entry_search_change(self.find_entry) + + def _find_entry_key_release_event(self, widget, event): + modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK) + is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False + is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False + keyname = Gdk.keyval_name(event.keyval).lower() + + if is_control and keyname == "f": + self.hide() + elif is_control and keyname == "r": + self.replace_entry.grab_focus() + + def _replace_entry_key_release_event(self, widget, event): + modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK) + is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False + is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False + keyname = Gdk.keyval_name(event.keyval).lower() + + if is_control and keyname == "l": + self.find_entry.grab_focus() + elif is_control and keyname == "f": + self.hide() + + def clear_highlight(self, buffer): + if not self.highlight_tag: return + start, end = buffer.get_bounds() + buffer.remove_tag(self.highlight_tag, start, end) + diff --git a/plugins/depricated/commentzar/add_comment_mixin.py b/plugins/depricated/commentzar/add_comment_mixin.py deleted file mode 100755 index 6e123f3..0000000 --- a/plugins/depricated/commentzar/add_comment_mixin.py +++ /dev/null @@ -1,66 +0,0 @@ -# Python imports - -# Lib imports - -# Application imports - - - -class AddCommentMixin: - def add_comment_characters(self, buffer, start_tag, end_tag, start, end, deselect, oldPos): - smark = buffer.create_mark("start", start, False) - imark = buffer.create_mark("iter", start, False) - emark = buffer.create_mark("end", end, False) - number_lines = end.get_line() - start.get_line() + 1 - comment_pos_iter = None - count = 0 - - buffer.begin_user_action() - - for i in range(0, number_lines): - iter = buffer.get_iter_at_mark(imark) - if not comment_pos_iter: - (comment_pos_iter, count) = self.discard_white_spaces(iter) - - if self.is_commented(comment_pos_iter, start_tag): - new_code = self.remove_comment_characters(buffer, start_tag, end_tag, start, end) - return - else: - comment_pos_iter = iter - for i in range(count): - c = iter.get_char() - if not c in (" ", "\t"): - break - - iter.forward_char() - - buffer.insert(comment_pos_iter, start_tag) - buffer.insert(comment_pos_iter, " ") - - if end_tag: - if i != number_lines -1: - iter = buffer.get_iter_at_mark(imark) - iter.forward_to_line_end() - buffer.insert(iter, end_tag) - else: - iter = buffer.get_iter_at_mark(emark) - buffer.insert(iter, end_tag) - - iter = buffer.get_iter_at_mark(imark) - iter.forward_line() - buffer.delete_mark(imark) - imark = buffer.create_mark("iter", iter, True) - - buffer.end_user_action() - - buffer.delete_mark(imark) - new_start = buffer.get_iter_at_mark(smark) - new_end = buffer.get_iter_at_mark(emark) - - buffer.select_range(new_start, new_end) - buffer.delete_mark(smark) - buffer.delete_mark(emark) - - if deselect: - oldPosIter = buffer.get_iter_at_offset(oldPos + 2) - buffer.place_cursor(oldPosIter) diff --git a/plugins/depricated/commentzar/codecomment_tags.py b/plugins/depricated/commentzar/codecomment_tags.py deleted file mode 100755 index ac7d110..0000000 --- a/plugins/depricated/commentzar/codecomment_tags.py +++ /dev/null @@ -1,30 +0,0 @@ -# Python imports - -# Lib imports - -# Application imports - - - -class CodeCommentTags: - def get_comment_tags(self, lang): - (s, e) = self.get_line_comment_tags(lang) - if (s, e) == (None, None): - (s, e) = self.get_block_comment_tags(lang) - - return (s, e) - - def get_block_comment_tags(self, lang): - start_tag = lang.get_metadata('block-comment-start') - end_tag = lang.get_metadata('block-comment-end') - if start_tag and end_tag: - return (start_tag, end_tag) - - return (None, None) - - def get_line_comment_tags(self, lang): - start_tag = lang.get_metadata('line-comment-start') - if start_tag: - return (start_tag, None) - - return (None, None) diff --git a/plugins/depricated/commentzar/plugin.py b/plugins/depricated/commentzar/plugin.py deleted file mode 100644 index cbf41bf..0000000 --- a/plugins/depricated/commentzar/plugin.py +++ /dev/null @@ -1,118 +0,0 @@ -# Python imports - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk - -# Application imports -from plugins.plugin_base import PluginBase -from .codecomment_tags import CodeCommentTags -from .remove_comment_mixin import RemoveCommentMixin -from .add_comment_mixin import AddCommentMixin - - - -class Plugin(AddCommentMixin, RemoveCommentMixin, CodeCommentTags, PluginBase): - def __init__(self): - super().__init__() - - self.name = "Commentzar" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus - # where self.name should not be needed for message comms - - - def generate_reference_ui_element(self): - ... - - def run(self): - ... - - def subscribe_to_events(self): - self._event_system.subscribe("keyboard_tggl_comment", self._keyboard_tggl_comment) - self._event_system.subscribe("set_active_src_view", self._set_active_src_view) - - def _set_active_src_view(self, source_view): - self._active_src_view = source_view - self._buffer = self._active_src_view.get_buffer() - self._tag_table = self._buffer.get_tag_table() - - - def _keyboard_tggl_comment(self): - buffer = self._buffer - lang = buffer.get_language() - if lang is None: - return - - (start_tag, end_tag) = self.get_comment_tags(lang) - if not start_tag and not end_tag: - return - - sel = buffer.get_selection_bounds() - currentPosMark = buffer.get_insert() - oldPos = 0 - - # if user selected chars or multilines - if sel != (): - deselect = False - (start, end) = sel - if not start.starts_line(): - start.set_line_offset(0) - if not end.ends_line(): - end.forward_to_line_end() - else: - deselect = True - start = buffer.get_iter_at_mark(currentPosMark) - oldPos = buffer.get_iter_at_mark(currentPosMark).get_offset() - start.set_line_offset(0) - end = start.copy() - - if not end.ends_line(): - end.forward_to_line_end() - - if start.get_offset() == end.get_offset(): - buffer.begin_user_action() - buffer.insert(start, start_tag) - buffer.insert(start, " ") - buffer.end_user_action() - return - - self._event_system.emit("pause_event_processing") - new_code = self.add_comment_characters(buffer, start_tag, end_tag, start, end, deselect, oldPos) - self._event_system.emit("resume_event_processing") - - def discard_white_spaces(self, iter): - count = 0 - while not iter.ends_line(): - c = iter.get_char() - if not c in (" ", "\t"): - return (iter, count) - - iter.forward_char() - count += 1 - - return (iter, 0) - - def is_commented(self, comment_pos_iter, start_tag): - head_iter = comment_pos_iter.copy() - self.forward_tag(head_iter, start_tag) - s = comment_pos_iter.get_slice(head_iter) - if s == start_tag: - return True - - return False - - def forward_tag(self, iter, tag): - iter.forward_chars(len(tag)) - - def backward_tag(self, iter, tag): - iter.backward_chars(len(tag)) - - def get_tag_position_in_line(self, tag, head_iter, iter): - while not iter.ends_line(): - s = iter.get_slice(head_iter) - if s == tag: - return True - else: - head_iter.forward_char() - iter.forward_char() - return False diff --git a/plugins/depricated/commentzar/remove_comment_mixin.py b/plugins/depricated/commentzar/remove_comment_mixin.py deleted file mode 100755 index 0e956f2..0000000 --- a/plugins/depricated/commentzar/remove_comment_mixin.py +++ /dev/null @@ -1,49 +0,0 @@ -# Python imports - -# Lib imports - -# Application imports - - - -class RemoveCommentMixin: - def remove_comment_characters(self, buffer, start_tag, end_tag, start, end): - smark = buffer.create_mark("start", start, False) - emark = buffer.create_mark("end", end, False) - number_lines = end.get_line() - start.get_line() + 1 - iter = start.copy() - head_iter = iter.copy() - self.forward_tag(head_iter, start_tag) - - buffer.begin_user_action() - - for i in range(0, number_lines): - if self.get_tag_position_in_line(start_tag, head_iter, iter): - dmark = buffer.create_mark("delete", iter, False) - buffer.delete(iter, head_iter) - - space_iter = head_iter.copy() - space_iter.forward_char() - s = head_iter.get_slice(space_iter) - if s == " ": - buffer.delete(head_iter, space_iter) - - if end_tag: - iter = buffer.get_iter_at_mark(dmark) - head_iter = iter.copy() - self.forward_tag(head_iter, end_tag) - if self.get_tag_position_in_line(end_tag, head_iter, iter): - buffer.delete(iter, head_iter) - buffer.delete_mark(dmark) - - iter = buffer.get_iter_at_mark(smark) - iter.forward_line() - buffer.delete_mark(smark) - head_iter = iter.copy() - self.forward_tag(head_iter, start_tag) - smark = buffer.create_mark("iter", iter, True) - - buffer.end_user_action() - - buffer.delete_mark(smark) - buffer.delete_mark(emark) diff --git a/plugins/depricated/search_replace/manifest.json b/plugins/depricated/search_replace/manifest.json deleted file mode 100644 index e89dc50..0000000 --- a/plugins/depricated/search_replace/manifest.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "Search/Replace", - "author": "ITDominator", - "version": "0.0.1", - "support": "", - "requests": { - "pass_events": true, - "pass_ui_objects": ["separator_botton"], - "bind_keys": ["Search/Replace||tggl_search_replace:f"] - - } -} diff --git a/plugins/depricated/search_replace/plugin.py b/plugins/depricated/search_replace/plugin.py deleted file mode 100644 index b387f07..0000000 --- a/plugins/depricated/search_replace/plugin.py +++ /dev/null @@ -1,221 +0,0 @@ -# Python imports -import os -import re -import threading - -# 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 -from gi.repository import GLib - -# Application imports -from plugins.plugin_base import PluginBase -from .styling_mixin import StylingMixin -from .replace_mixin import ReplaceMixin - - - -class Plugin(StylingMixin, ReplaceMixin, PluginBase): - def __init__(self): - super().__init__() - - self.name = "Search/Replace" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus - # where self.name should not be needed for message comms - self.path = os.path.dirname(os.path.realpath(__file__)) - self._GLADE_FILE = f"{self.path}/search_replace.glade" - - self._search_replace_dialog = None - self._find_entry = None - self._replace_entry = None - self._active_src_view = None - self._buffer = None - self._tag_table = None - - self.use_regex = False - self.use_case_sensitive = False - self.search_only_in_selection = False - self.use_whole_word_search = False - - self.timer = None - self.search_time = 0.35 - self.find_text = "" - self.search_tag = "search_tag" - self.highlight_color = "#FBF719" - self.text_color = "#000000" - self.alpha_num_under = re.compile(r"[a-zA-Z0-9_]") - - - def run(self): - self._builder = Gtk.Builder() - self._builder.add_from_file(self._GLADE_FILE) - self._connect_builder_signals(self, self._builder) - - separator_botton = self._ui_objects[0] - self._search_replace_dialog = self._builder.get_object("search_replace_dialog") - self._find_status_lbl = self._builder.get_object("find_status_lbl") - self._find_options_lbl = self._builder.get_object("find_options_lbl") - - self._find_entry = self._builder.get_object("find_entry") - self._replace_entry = self._builder.get_object("replace_entry") - - self._search_replace_dialog.set_relative_to(separator_botton) - self._search_replace_dialog.set_hexpand(True) - - def generate_reference_ui_element(self): - ... - - def subscribe_to_events(self): - self._event_system.subscribe("tggl_search_replace", self._tggl_search_replace) - self._event_system.subscribe("set_active_src_view", self._set_active_src_view) - - def _set_active_src_view(self, source_view): - self._active_src_view = source_view - self._buffer = self._active_src_view.get_buffer() - self._tag_table = self._buffer.get_tag_table() - self.search_for_string(self._find_entry) - - def _show_search_replace(self, widget = None, eve = None): - self._search_replace_dialog.popup() - - def _tggl_search_replace(self, widget = None, eve = None): - is_visible = self._search_replace_dialog.is_visible() - buffer = self._active_src_view.get_buffer() - data = None - - if buffer.get_has_selection(): - start, end = buffer.get_selection_bounds() - data = buffer.get_text(start, end, include_hidden_chars = False) - - if data: - self._find_entry.set_text(data) - - if not is_visible: - self._search_replace_dialog.popup(); - self._find_entry.grab_focus() - elif not data and is_visible: - self._search_replace_dialog.popdown() - self._find_entry.set_text("") - else: - self._find_entry.grab_focus() - - - def get_search_tag(self, buffer): - tag_table = buffer.get_tag_table() - search_tag = tag_table.lookup(self.search_tag) - if not search_tag: - search_tag = buffer.create_tag(self.search_tag, background = self.highlight_color, foreground = self.text_color) - - buffer.remove_tag_by_name(self.search_tag, buffer.get_start_iter(), buffer.get_end_iter()) - return search_tag - - - def cancel_timer(self): - if self.timer: - self.timer.cancel() - GLib.idle_remove_by_data(None) - - def delay_search_glib(self): - GLib.idle_add(self._do_highlight) - - def delay_search(self): - wait_time = self.search_time / len(self.find_text) - wait_time = max(wait_time, 0.05) - - self.timer = threading.Timer(wait_time, self.delay_search_glib) - self.timer.daemon = True - self.timer.start() - - - def on_enter_search(self, widget, eve): - text = widget.get_text() - if not text: return - - keyname = Gdk.keyval_name(eve.keyval) - if keyname == "Return": - self.find_next(widget) - - def search_for_string(self, widget): - self.cancel_timer() - - self.find_text = widget.get_text() - if len(self.find_text) > 0 and len(self.find_text) < 5: - self.delay_search() - else: - self._do_highlight(self.find_text) - - - def _do_highlight(self, query = None): - query = self.find_text if not query else query - buffer = self._active_src_view.get_buffer() - # Also clears tag from buffer so if no query we're clean in ui - search_tag = self.get_search_tag(buffer) - - self.update_style(1) - if not query: - self._find_status_lbl.set_label(f"Find in current buffer") - self.update_style(0) - return - - start_itr = buffer.get_start_iter() - end_itr = buffer.get_end_iter() - - results, total_count = self.search(start_itr, query) - self._update_status_lbl(total_count, query) - for start, end in results: - buffer.apply_tag(search_tag, start, end) - - def search(self, start_itr = None, query = None, limit = None): - if not start_itr or not query: return None, None - - flags = Gtk.TextSearchFlags.VISIBLE_ONLY | Gtk.TextSearchFlags.TEXT_ONLY - if not self.use_case_sensitive: - flags = flags | Gtk.TextSearchFlags.CASE_INSENSITIVE - - if self.search_only_in_selection and self._buffer.get_has_selection(): - start_itr, limit = self._buffer.get_selection_bounds() - - _results = [] - while True: - result = start_itr.forward_search(query, flags, limit) - if not result: break - - _results.append(result) - start_itr = result[1] - - results = self.apply_filters(_results, query) - return results, len(results) - - def apply_filters(self, _results, query): - results = [] - for start, end in _results: - text = self._buffer.get_slice(start, end, include_hidden_chars = False) - if self.use_whole_word_search: - if not self.is_whole_word(start, end): - continue - - results.append([start, end]) - - return results - - def find_next(self, widget, eve = None, use_data = None): - mark = self._buffer.get_insert() - iter = self._buffer.get_iter_at_mark(mark) - iter.forward_line() - - search_tag = self._tag_table.lookup(self.search_tag) - next_tag_found = iter.forward_to_tag_toggle(search_tag) - if not next_tag_found: - self._buffer.place_cursor( self._buffer.get_start_iter() ) - mark = self._buffer.get_insert() - iter = self._buffer.get_iter_at_mark(mark) - iter.forward_to_tag_toggle(search_tag) - - self._buffer.place_cursor(iter) - self._active_src_view.scroll_to_mark( self._buffer.get_insert(), 0.0, True, 0.0, 0.0 ) - - - def find_all(self, widget): - ... \ No newline at end of file diff --git a/plugins/depricated/search_replace/replace_mixin.py b/plugins/depricated/search_replace/replace_mixin.py deleted file mode 100644 index 9d5a2f0..0000000 --- a/plugins/depricated/search_replace/replace_mixin.py +++ /dev/null @@ -1,94 +0,0 @@ -# Python imports - -# Lib imports - -# Application imports - - - -class ReplaceMixin: - def replace(self, widget): - replace_text = self._replace_entry.get_text() - if self.find_text and replace_text: - self._buffer.begin_user_action() - - iter = self._buffer.get_start_iter() - search_tag = self._tag_table.lookup(self.search_tag) - - iter.forward_to_tag_toggle(search_tag) - self._do_replace(iter, replace_text) - self._active_src_view.scroll_to_iter( iter, 0.0, True, 0.0, 0.0 ) - - self._buffer.end_user_action() - - def replace_all(self, widget): - replace_text = self._replace_entry.get_text() - if self.find_text: - self._buffer.begin_user_action() - - mark = self._buffer.get_insert() - iter = self._buffer.get_start_iter() - search_tag = self._tag_table.lookup(self.search_tag) - - while iter.forward_to_tag_toggle(search_tag): - self._do_replace(iter, replace_text) - iter = self._buffer.get_start_iter() - - self._buffer.end_user_action() - - - def _do_replace(self, iter, text): - start, end = self.get_start_end(iter) - self.replace_in_buffer(start, end, text) - - def replace_in_buffer(self, start, end, text): - pos_mark = self._buffer.create_mark("find-replace", end, True) - self._buffer.delete(start, end) - replace_iter = self._buffer.get_iter_at_mark(pos_mark) - self._buffer.insert(replace_iter, text) - - def get_start_end(self, iter): - start = iter.copy() - end = None - - while True: - iter.forward_char() - tags = iter.get_tags() - valid = False - for tag in tags: - if tag.props.name and self.search_tag in tag.props.name: - valid = True - break - - if valid: - continue - - end = iter.copy() - break - - return start, end - - # NOTE: Below, lovingly taken from Hamad Al Marri's Gamma text editor. - # Link: https://gitlab.com/hamadmarri/gamma-text-editor - def is_whole_word(self, match_start, match_end): - is_prev_a_char = True - is_next_a_char = True - - prev_iter = match_start.copy() - next_iter = match_end.copy() - if not prev_iter.backward_char(): - is_prev_a_char = False - else: - c = prev_iter.get_char() - is_prev_a_char = (c.isalpha() or c.isdigit()) - - if not next_iter: - is_next_a_char = False - else: - c = next_iter.get_char() - is_next_a_char = (c.isalpha() or c.isdigit()) - - is_word = (not is_prev_a_char and not is_next_a_char) - - # Note: Both must be false to be a word... - return is_word diff --git a/plugins/depricated/search_replace/search_replace.glade b/plugins/depricated/search_replace/search_replace.glade deleted file mode 100644 index ccab6c8..0000000 --- a/plugins/depricated/search_replace/search_replace.glade +++ /dev/null @@ -1,299 +0,0 @@ - - - - - - True - False - gtk-close - - - True - False - ../../icons/only-in-selection.png - - - True - False - ../../icons/whole-word.png - - - False - False - False - none - - - True - False - vertical - - - True - False - - - True - False - 5 - Find in Current Buffer - 0 - - - True - True - 0 - - - - - True - False - - - True - False - 20 - Finding with Options: Case Insensitive - 0 - - - False - True - 0 - - - - - True - False - start - - - .* - True - False - True - False - True - Use Regex - - - - True - True - 0 - - - - - Aa - True - True - False - True - Match Case - - - - True - True - 1 - - - - - True - True - False - True - Only In Selection - only-in-selection - True - - - - True - True - 2 - - - - - True - True - False - True - Whole Word - whole-word - True - - - - True - True - 3 - - - - - True - True - False - True - Close Panel - close_img - True - - - - True - True - 4 - - - - - False - True - 1 - - - - - False - True - 1 - - - - - False - True - 0 - - - - - - True - False - True - - - Replace All - True - True - False - True - Replace All - 5 - 5 - 5 - 5 - - - - 9 - 1 - - - - - Replace - True - True - False - True - Replace Next - 5 - 10 - 5 - 5 - - - - 8 - 1 - - - - - Find All - True - True - False - True - 5 - 5 - 5 - 5 - - - - 9 - 0 - - - - - Find - True - True - False - True - 5 - 5 - 5 - 5 - - - - 8 - 0 - - - - - True - True - edit-find-symbolic - False - False - Find in current buffer - - - - - 0 - 0 - 8 - - - - - True - True - edit-find-symbolic - False - False - Replace in current buffer - - - 0 - 1 - 8 - - - - - False - True - 10 - 3 - - - - - - diff --git a/plugins/depricated/search_replace/styling_mixin.py b/plugins/depricated/search_replace/styling_mixin.py deleted file mode 100644 index 3336bec..0000000 --- a/plugins/depricated/search_replace/styling_mixin.py +++ /dev/null @@ -1,66 +0,0 @@ -# Python imports - -# Lib imports - -# Application imports - - - -class StylingMixin: - def tggle_regex(self, widget): - self.use_regex = not widget.get_active() - self._set_find_options_lbl() - self.search_for_string(self._find_entry) - - def tggle_case_sensitive(self, widget): - self.use_case_sensitive = widget.get_active() - self._set_find_options_lbl() - self.search_for_string(self._find_entry) - - def tggle_selection_only_scan(self, widget): - self.search_only_in_selection = widget.get_active() - self._set_find_options_lbl() - self.search_for_string(self._find_entry) - - def tggle_whole_word_search(self, widget): - self.use_whole_word_search = widget.get_active() - self._set_find_options_lbl() - self.search_for_string(self._find_entry) - - def _set_find_options_lbl(self): - find_options = "Finding with Options: " - - if self.use_regex: - find_options += "Regex" - - find_options += ", " if self.use_regex else "" - find_options += "Case Sensitive" if self.use_case_sensitive else "Case Inensitive" - - if self.search_only_in_selection: - find_options += ", Within Current Selection" - - if self.use_whole_word_search: - find_options += ", Whole Word" - - self._find_options_lbl.set_label(find_options) - - def update_style(self, state): - self._find_entry.get_style_context().remove_class("searching") - self._find_entry.get_style_context().remove_class("search_success") - self._find_entry.get_style_context().remove_class("search_fail") - - if state == 0: - self._find_entry.get_style_context().add_class("searching") - elif state == 1: - self._find_entry.get_style_context().add_class("search_success") - elif state == 2: - self._find_entry.get_style_context().add_class("search_fail") - - def _update_status_lbl(self, total_count: int = 0, query: str = None): - if not query: return - - count = total_count if total_count > 0 else "No" - plural = "s" if total_count > 1 else "" - - if total_count == 0: self.update_style(2) - self._find_status_lbl.set_label(f"{count} result{plural} found for '{query}'") diff --git a/plugins/template/__init__.py b/plugins/ui/template/__init__.py similarity index 100% rename from plugins/template/__init__.py rename to plugins/ui/template/__init__.py diff --git a/plugins/template/__main__.py b/plugins/ui/template/__main__.py similarity index 100% rename from plugins/template/__main__.py rename to plugins/ui/template/__main__.py diff --git a/plugins/template/manifest.json b/plugins/ui/template/manifest.json similarity index 100% rename from plugins/template/manifest.json rename to plugins/ui/template/manifest.json diff --git a/plugins/template/plugin.py b/plugins/ui/template/plugin.py similarity index 76% rename from plugins/template/plugin.py rename to plugins/ui/template/plugin.py index 6fb6694..fd843eb 100644 --- a/plugins/template/plugin.py +++ b/plugins/ui/template/plugin.py @@ -21,14 +21,14 @@ class Plugin(PluginUI): ... def load(self): - ui_element = self.requests_ui_element("plugin_control_list") + ui_element = self.requests_ui_element("header-container") ui_element.add( self.generate_plugin_element() ) def run(self): ... def generate_plugin_element(self): - button = Gtk.Button(label = self.name) + button = Gtk.Button(label = "Hello, World!") button.connect("button-release-event", self.send_message) button.show() @@ -36,6 +36,5 @@ class Plugin(PluginUI): return button def send_message(self, widget = None, eve = None): - message = "Hello, World!" - self.emit("display_message", ("warning", message, None)) + logger.info("Hello, World!") \ No newline at end of file diff --git a/src/__builtins__.py b/src/__builtins__.py index 5359fe8..c43ebec 100644 --- a/src/__builtins__.py +++ b/src/__builtins__.py @@ -68,6 +68,7 @@ builtins.call_chain = call_chain_wrapper # def custom_except_hook(exc_type, exc_value, exc_traceback): # if issubclass(exc_type, KeyboardInterrupt): # sys.__excepthook__(exc_type, exc_value, exc_traceback) +# sys.__excepthook__(exc_type, exc_value, exc_traceback) # return # logger.error("Uncaught exception", exc_info = (exc_type, exc_value, exc_traceback)) diff --git a/src/core/containers/footer_container.py b/src/core/containers/footer_container.py index 9d7d0fa..4dcdd71 100644 --- a/src/core/containers/footer_container.py +++ b/src/core/containers/footer_container.py @@ -27,7 +27,7 @@ class FooterContainer(Gtk.Box): self.ctx = self.get_style_context() self.ctx.add_class("footer-container") - self.set_orientation(Gtk.Orientation.HORIZONTAL) + self.set_orientation(Gtk.Orientation.VERTICAL) self.set_hexpand(True) def _setup_signals(self): diff --git a/src/core/controllers/base_controller.py b/src/core/controllers/base_controller.py index 92adf74..523b53d 100644 --- a/src/core/controllers/base_controller.py +++ b/src/core/controllers/base_controller.py @@ -12,6 +12,7 @@ from libs.mixins.ipc_signals_mixin import IPCSignalsMixin from libs.mixins.keyboard_signals_mixin import KeyboardSignalsMixin from ..containers.base_container import BaseContainer +from ..containers.code.code_container import CodeContainer from .base_controller_mixin import BaseControllerMixin from .bridge_controller import BridgeController diff --git a/src/core/widgets/code/command_system/command_system.py b/src/core/widgets/code/command_system/command_system.py index 48af7ed..0cbfbd0 100644 --- a/src/core/widgets/code/command_system/command_system.py +++ b/src/core/widgets/code/command_system/command_system.py @@ -37,6 +37,9 @@ class CommandSystem: method = getattr(commands, command) return method.execute(*args) + def add_command(self, command_name: str, command: callable): + setattr(commands, command_name, command) + def emit(self, event: Code_Event_Types.CodeEvent): """ Monkey patch 'emit' from command controller... """ diff --git a/src/core/widgets/code/command_system/commands/__init__.py b/src/core/widgets/code/command_system/commands/__init__.py index d249eb4..54539e2 100644 --- a/src/core/widgets/code/command_system/commands/__init__.py +++ b/src/core/widgets/code/command_system/commands/__init__.py @@ -9,7 +9,7 @@ __all__ = [] for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): module = importlib.import_module(f"{__name__}.{module_name}") - globals()[module_name] = module # Add module to package namespace + # globals()[module_name] = module # Add module to package namespace __all__.append(module_name) del pkgutil diff --git a/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py b/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py index 713f7a3..653cc40 100644 --- a/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py +++ b/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py @@ -18,12 +18,7 @@ def execute( uri: str ): logger.debug("Command: DnD Load File To Buffer") - - file = view.command.get_file(view) - buffer = file.buffer - - if not file.ftype == "buffer": - file = view.command.new_file(view) + file = view.command.new_file(view) gfile = Gio.File.new_for_uri(uri) view.command.exec_with_args( @@ -31,4 +26,5 @@ def execute( (view, gfile, file) ) + view.set_buffer(file.buffer) update_info_bar_if_focused(view.command, view) diff --git a/src/core/widgets/code/controllers/commands_controller.py b/src/core/widgets/code/controllers/commands_controller.py index e1aec04..532bb07 100644 --- a/src/core/widgets/code/controllers/commands_controller.py +++ b/src/core/widgets/code/controllers/commands_controller.py @@ -17,10 +17,10 @@ class CommandsController(ControllerBase, list): def _controller_message(self, event: Code_Event_Types.CodeEvent): - if isinstance(event, Code_Event_Types.GetCommandSystemEvent): - event.response = self.get_command_system() + if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent): + event.response = self.get_new_command_system() - def get_command_system(self): + def get_new_command_system(self): command_system = CommandSystem() command_system.emit = self.emit command_system.emit_to = self.emit_to 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 f5c7e60..e183daa 100644 --- a/src/core/widgets/code/controllers/views/source_views_controller.py +++ b/src/core/widgets/code/controllers/views/source_views_controller.py @@ -27,6 +27,8 @@ class SourceViewsController(ControllerBase, list): def _controller_message(self, event: Code_Event_Types.CodeEvent): if isinstance(event, Code_Event_Types.RemovedFileEvent): self._remove_file(event) + elif isinstance(event, Code_Event_Types.RegisterCommandEvent): + self. _register_command(event) if not self.signal_mapper.active_view: return @@ -40,8 +42,22 @@ class SourceViewsController(ControllerBase, list): elif isinstance(event, Code_Event_Types.TextInsertedEvent): self.signal_mapper.insert_text(event.file, event.text) + def _register_command(self, event: Code_Event_Types.RegisterCommandEvent): + self.state_manager.key_mapper.map_command( + event.command_name, + { + f"{event.binding_mode}": event.binding + } + ) + + for view in self: + view.command.add_command( + event.command_name, + event.command + ) + def _get_command_system(self): - event = Event_Factory.create_event("get_command_system") + event = Event_Factory.create_event("get_new_command_system") self.message_to("commands", event) command = event.response diff --git a/src/core/widgets/code/controllers/views/state_manager.py b/src/core/widgets/code/controllers/views/state_manager.py index 8bae1a1..4aa3201 100644 --- a/src/core/widgets/code/controllers/views/state_manager.py +++ b/src/core/widgets/code/controllers/views/state_manager.py @@ -10,6 +10,7 @@ from ...key_mapper import KeyMapper from .states import * + class SourceViewStateManager: def __init__(self): self.key_mapper: KeyMapper = KeyMapper() diff --git a/src/core/widgets/code/key_mapper.py b/src/core/widgets/code/key_mapper.py index 9193401..862bea7 100644 --- a/src/core/widgets/code/key_mapper.py +++ b/src/core/widgets/code/key_mapper.py @@ -70,29 +70,31 @@ class KeyMapper: with open(bindings_file, 'r') as f: data = json.load(f)["keybindings"] - for command in data: - press_state = "held" if "held" in data[command] else "released" - keyname = data[command][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() - - getattr(self.states[state], press_state)[keyname] = command + self.map_command( command, data[command] ) def re_map(self): self.states = copy.deepcopy(self._map) + def map_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() + + getattr(self.states[state], press_state)[keyname] = command + def _key_press_event(self, eve): keyname = Gdk.keyval_name(eve.keyval).lower() diff --git a/src/core/widgets/code/source_buffer.py b/src/core/widgets/code/source_buffer.py index 8751128..da8cfbb 100644 --- a/src/core/widgets/code/source_buffer.py +++ b/src/core/widgets/code/source_buffer.py @@ -13,9 +13,14 @@ class SourceBuffer(GtkSource.Buffer): def __init__(self): super(SourceBuffer, self).__init__() + self._handler_ids = [] self.is_processing_completion: bool = False - self._handler_ids = [] + self.create_tag( + "search-highlight", + background = "yellow", + foreground = "black" + ) def set_signals( diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py index 43288af..216b33f 100644 --- a/src/core/widgets/code/source_file.py +++ b/src/core/widgets/code/source_file.py @@ -43,10 +43,26 @@ class SourceFile(GtkSource.File): ) def _changed(self, buffer: SourceBuffer): + self.check_file_on_disk() + event = Event_Factory.create_event("text_changed", buffer = buffer) event.file = self self.emit(event) + if self.is_deleted(): + print("deleted") + # event = Event_Factory.create_event("file_deleted", buffer = buffer) + # event.file = self + # self.emit(event) + return + + if self.is_externally_modified(): + print("is_externally_modified") + # event = Event_Factory.create_event("file_externally_modified", buffer = buffer) + # event.file = self + # self.emit(event) + return + def _insert_text( self, buffer: SourceBuffer, diff --git a/src/core/widgets/code/source_view.py b/src/core/widgets/code/source_view.py index bbb752d..1a94a71 100644 --- a/src/core/widgets/code/source_view.py +++ b/src/core/widgets/code/source_view.py @@ -59,6 +59,7 @@ class SourceView(GtkSource.View, SourceViewDnDMixin): def _setup_signals(self): self.connect("drag-data-received", self._on_drag_data_received) + self.connect("populate-popup", self._on_populate_popup) def _subscribe_to_events(self): ... @@ -76,6 +77,37 @@ class SourceView(GtkSource.View, SourceViewDnDMixin): self._set_up_dnd() + def _on_populate_popup(self, view, menu): + buffer = self.get_buffer() + language = buffer.get_language() + + if language.get_id() == "json": + self._load_pretify_json(view, menu) + + menu.show_all() + + def _load_prettify_json(self, view, menu): + menu.append( Gtk.SeparatorMenuItem() ) + + def on_prettify_json(menuitem): + import json + + buffer = self.get_buffer() + start_itr, \ + end_itr = buffer.get_start_iter(), buffer.get_end_iter() + data = buffer.get_text(start_itr, end_itr, False) + text = json.dumps(json.loads(data), separators = (',', ':'), indent = 4) + + buffer.begin_user_action() + buffer.delete(start_itr, end_itr) + buffer.insert(start_itr, text) + buffer.end_user_action() + + item = Gtk.MenuItem(label = "Prettify JSON") + item.connect("activate", on_prettify_json) + menu.append(item) + + def clear_temp_cut_buffer_delayed(self): if self._cut_temp_timeout_id: GLib.source_remove(self._cut_temp_timeout_id) diff --git a/src/core/widgets/vte_widget.py b/src/core/widgets/vte_widget.py index 0325fa7..3e931d4 100644 --- a/src/core/widgets/vte_widget.py +++ b/src/core/widgets/vte_widget.py @@ -45,6 +45,7 @@ class VteWidget(Vte.Terminal): ctx.add_class("vte-widget") self.set_clear_background(False) + self.set_hexpand(True) self.set_enable_sixel(True) self.set_cursor_shape( Vte.CursorShape.IBEAM ) diff --git a/src/libs/dto/code/__init__.py b/src/libs/dto/code/__init__.py index b814883..aff9d7d 100644 --- a/src/libs/dto/code/__init__.py +++ b/src/libs/dto/code/__init__.py @@ -5,8 +5,9 @@ from .code_event import CodeEvent from .register_provider_event import RegisterProviderEvent +from .register_command_event import RegisterCommandEvent -from .get_command_system_event import GetCommandSystemEvent +from .get_new_command_system_event import GetNewCommandSystemEvent from .request_completion_event import RequestCompletionEvent from .cursor_moved_event import CursorMovedEvent from .modified_changed_event import ModifiedChangedEvent diff --git a/src/libs/dto/code/get_command_system_event.py b/src/libs/dto/code/get_new_command_system_event.py similarity index 77% rename from src/libs/dto/code/get_command_system_event.py rename to src/libs/dto/code/get_new_command_system_event.py index ae6b60e..f3831c7 100644 --- a/src/libs/dto/code/get_command_system_event.py +++ b/src/libs/dto/code/get_new_command_system_event.py @@ -9,5 +9,5 @@ from .code_event import CodeEvent @dataclass -class GetCommandSystemEvent(CodeEvent): +class GetNewCommandSystemEvent(CodeEvent): ... diff --git a/src/libs/dto/code/register_command_event.py b/src/libs/dto/code/register_command_event.py new file mode 100644 index 0000000..d4441fe --- /dev/null +++ b/src/libs/dto/code/register_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 ..base_event import BaseEvent + + + +@dataclass +class RegisterCommandEvent(BaseEvent): + command_name: str = "" + command: callable = None + binding_mode: str = "" + binding: str = "" diff --git a/src/libs/event_factory.py b/src/libs/event_factory.py index 7354eb4..699121d 100644 --- a/src/libs/event_factory.py +++ b/src/libs/event_factory.py @@ -15,11 +15,11 @@ from .dto import code class EventFactory(Singleton): def __init__(self): + self._event_classes: Dict[str, Type[BaseEvent]] = {} self._auto_register_events( code.__dict__.items() ) - def register_event(self, event_type: str, event_class: Type[BaseEvent]): self._event_classes[event_type] = event_class diff --git a/src/plugins/plugin_types/plugin_code.py b/src/plugins/plugin_types/plugin_code.py index 37438c1..76bd503 100644 --- a/src/plugins/plugin_types/plugin_code.py +++ b/src/plugins/plugin_types/plugin_code.py @@ -31,6 +31,9 @@ class PluginCode(PluginBase): def run(self): raise PluginCodeException("Plugin Code 'run' must be overriden by Plugin") + def requests_ui_element(self, element_id: str): + return self.plugin_context.requests_ui_element(element_id) + def message(self, event: BaseEvent): return self.plugin_context.message(event) diff --git a/src/plugins/plugins_controller_mixin.py b/src/plugins/plugins_controller_mixin.py index 3583ee3..565d04f 100644 --- a/src/plugins/plugins_controller_mixin.py +++ b/src/plugins/plugins_controller_mixin.py @@ -14,10 +14,7 @@ class InvalidPluginException(Exception): class PluginsControllerMixin: def requests_ui_element(self, target_id: str): - builder = settings_manager.get_builder() - ui_target = builder.get_object(target_id) + if not target_id in widget_registery.objects: + raise InvalidPluginException('Unknown UI "target_id" given in requests.') - if not ui_target: - raise InvalidPluginException('Unknown "target_id" given in requests.') - - return ui_target + return widget_registery.objects[target_id]