diff --git a/plugins/search_replace/__init__.py b/plugins/search_replace/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/search_replace/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/search_replace/__main__.py b/plugins/search_replace/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/search_replace/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/search_replace/manifest.json b/plugins/search_replace/manifest.json new file mode 100644 index 0000000..a98cc50 --- /dev/null +++ b/plugins/search_replace/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest": { + "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/search_replace/plugin.py b/plugins/search_replace/plugin.py new file mode 100644 index 0000000..4bcbb12 --- /dev/null +++ b/plugins/search_replace/plugin.py @@ -0,0 +1,166 @@ +# Python imports +import os + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from plugins.plugin_base import PluginBase + + + + +class Plugin(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.use_regex = False + self.use_case_sensitive = False + self.use_selection_only_scan = False + self.use_fuzzy_search = False + self.highlight_color = "#FBF719" + self.text_color = "#000000" + + + 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.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() + if not is_visible: + self._search_replace_dialog.popup(); + self._find_entry.grab_focus() + else: + self._search_replace_dialog.popdown() + + def tggle_regex(self, widget): + self.use_regex = not widget.get_active() + self._set_find_options_lbl() + + def tggle_case_sensitive(self, widget): + self.use_case_sensitive = not widget.get_active() + self._set_find_options_lbl() + + def tggle_selection_only_scan(self, widget): + self.use_selection_only_scan = not widget.get_active() + self._set_find_options_lbl() + + def tggle_fuzzy_search(self, widget): + self.use_fuzzy_search = not widget.get_active() + self._set_find_options_lbl() + + def _set_find_options_lbl(self): + # Finding with Options: Case Insensitive + # Finding with Options: Regex, Case Sensitive, Within Current Selection, Whole Word + # Finding with Options: Regex, Case Inensitive, Within Current Selection, Whole Word + # f"Finding with Options: {regex}, {case}, {selection}, {word}" + ... + + def _update_status_lbl(self, total_count: int = None, query: str = None): + if not total_count or not query: return + + count = total_count if total_count > 0 else "No" + plural = "s" if total_count > 1 else "" + self._find_status_lbl.set_label(f"{count} results{plural} found for '{query}'") + + def get_search_tag(self, buffer): + tag_table = buffer.get_tag_table() + search_tag = tag_table.lookup("search_tag") + if not search_tag: + search_tag = buffer.create_tag("search_tag", background = self.highlight_color, foreground = self.text_color) + + buffer.remove_tag_by_name("search_tag", buffer.get_start_iter(), buffer.get_end_iter()) + return search_tag + + def search_for_string(self, widget): + query = widget.get_text() + 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) + + if not query: + self._find_status_lbl.set_label(f"Find in current buffer") + return + + start_itr = buffer.get_start_iter() + end_itr = buffer.get_end_iter() + + results, total_count = self.search(start_itr, query) + for start, end in results: + buffer.apply_tag(search_tag, start, end) + self._update_status_lbl(total_count, query) + + def search(self, start_itr = None, query = None): + if not start_itr or not query: return None, None + + if not self.use_case_sensitive: + _flags = Gtk.TextSearchFlags.VISIBLE_ONLY & Gtk.TextSearchFlags.TEXT_ONLY & Gtk.TextSearchFlags.CASE_INSENSITIVE + else: + _flags = Gtk.TextSearchFlags.VISIBLE_ONLY & Gtk.TextSearchFlags.TEXT_ONLY + + results = [] + while True: + result = start_itr.forward_search(query, flags = _flags, limit = None) + if not result: break + + results.append(result) + start_itr = result[1] + + return results, len(results) + + + + + + + + def find_next(self): + ... + + def find_all(self): + ... + + def replace_next(self): + ... + + def replace_all(self): + ... diff --git a/plugins/search_replace/search_replace.glade b/plugins/search_replace/search_replace.glade new file mode 100644 index 0000000..ba68b3e --- /dev/null +++ b/plugins/search_replace/search_replace.glade @@ -0,0 +1,281 @@ + + + + + + True + False + gtk-close + + + 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 + True + False + True + Use Regex + + + + True + True + 0 + + + + + Aa + True + True + False + True + Match Case + + + + True + True + 1 + + + + + togglebutton + True + True + False + True + Only In Selection + + + + True + True + 2 + + + + + togglebutton + True + True + False + True + Whole Word + + + + 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 + + + True + True + 5 + 10 + 5 + 5 + Find in current buffer + + + + 0 + 0 + 8 + + + + + True + True + 5 + 10 + 5 + 5 + Replace in current buffer + + + 0 + 1 + 8 + + + + + 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 + True + 5 + 5 + 5 + 5 + + + 9 + 0 + + + + + Find + True + True + True + 5 + 5 + 5 + 5 + + + 8 + 0 + + + + + False + True + 10 + 3 + + + + + + diff --git a/plugins/template/manifest.json b/plugins/template/manifest.json index 4dcbf47..1f8c8a5 100644 --- a/plugins/template/manifest.json +++ b/plugins/template/manifest.json @@ -6,7 +6,7 @@ "support": "", "requests": { "ui_target": "plugin_control_list", - "pass_fm_events": "true", + "pass_events": "true", "bind_keys": ["Example Plugin||send_message:f"] } } diff --git a/src/core/core_widget.py b/src/core/core_widget.py index 03e7cb0..391b25b 100644 --- a/src/core/core_widget.py +++ b/src/core/core_widget.py @@ -6,10 +6,11 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports +from .widgets.separator_widget import Separator from .widgets.save_file_dialog import SaveFileDialog +from .widgets.base.general_info_widget import GeneralInfoWidget from .widgets.base.banner_controls import BannerControls from .editors_container import EditorsContainer -from .widgets.base.general_info_widget import GeneralInfoWidget @@ -28,13 +29,17 @@ class CoreWidget(Gtk.Box): def _setup_styling(self): - self.set_orientation(1) + self.set_orientation(1) # VERTICAL = 1 + def _setup_signals(self): ... def _load_widgets(self): SaveFileDialog() - self.add(BannerControls()) GeneralInfoWidget() + + self.add(BannerControls()) + self.add(Separator("separator_top")) self.add(EditorsContainer()) + self.add(Separator("separator_botton")) diff --git a/src/core/widgets/base/banner_controls.py b/src/core/widgets/base/banner_controls.py index cbb7ca6..b96d54e 100644 --- a/src/core/widgets/base/banner_controls.py +++ b/src/core/widgets/base/banner_controls.py @@ -30,6 +30,8 @@ class BannerControls(Gtk.Box): def _setup_styling(self): self.set_orientation(0) + self.set_margin_top(5) + self.set_margin_bottom(5) def _setup_signals(self): ... diff --git a/src/core/widgets/base/notebook/editor_controller.py b/src/core/widgets/base/notebook/editor_controller.py index c6b3f76..51a8fd6 100644 --- a/src/core/widgets/base/notebook/editor_controller.py +++ b/src/core/widgets/base/notebook/editor_controller.py @@ -52,8 +52,6 @@ class EditorControllerMixin: self.move_lines_down(source_view) if action == "keyboard_tggl_comment": self.keyboard_tggl_comment(source_view) - if action == "do_text_search": - self.do_text_search(source_view, query) if action == "set_buffer_language": self.set_buffer_language(source_view, query) if action == "set_buffer_style": diff --git a/src/core/widgets/base/notebook/editor_events.py b/src/core/widgets/base/notebook/editor_events.py index 779200c..85b15e7 100644 --- a/src/core/widgets/base/notebook/editor_events.py +++ b/src/core/widgets/base/notebook/editor_events.py @@ -125,12 +125,8 @@ class EditorEventsMixin: def keyboard_tggl_comment(self, source_view): source_view.keyboard_tggl_comment() - def do_text_search(self, query = ""): - # source_view.scale_down_text() - ... - def set_buffer_language(self, source_view, language = "python3"): source_view.set_buffer_language(language) - def set_buffer_style(self, source_view, style = "tango"): + def set_buffer_style(self, source_view, style = settings.theming.syntax_theme): source_view.set_buffer_style(style) diff --git a/src/core/widgets/base/notebook/editor_notebook.py b/src/core/widgets/base/notebook/editor_notebook.py index 53e200e..38986b7 100644 --- a/src/core/widgets/base/notebook/editor_notebook.py +++ b/src/core/widgets/base/notebook/editor_notebook.py @@ -92,17 +92,12 @@ class EditorNotebook(EditorEventsMixin, EditorControllerMixin, Gtk.Notebook): start_box = Gtk.Box() end_box = Gtk.Box() - search = Gtk.SearchEntry() - search.set_placeholder_text("Search...") - search.connect("changed", self._text_search) - add_btn = Gtk.Button() add_btn.set_image( Gtk.Image.new_from_icon_name("add", 4) ) add_btn.set_always_show_image(True) add_btn.connect("released", self.create_view) end_box.add(add_btn) - end_box.add(search) start_box.show_all() end_box.show_all() @@ -210,6 +205,3 @@ class EditorNotebook(EditorEventsMixin, EditorControllerMixin, Gtk.Notebook): def _keyboard_save_file_as(self): self.action_controller("save_file_as") - - def _text_search(self, widget = None, eve = None): - self.action_controller("do_text_search", widget.get_text()) diff --git a/src/core/widgets/separator_widget.py b/src/core/widgets/separator_widget.py new file mode 100644 index 0000000..7725b21 --- /dev/null +++ b/src/core/widgets/separator_widget.py @@ -0,0 +1,37 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + + +class Separator(Gtk.Separator): + def __init__(self, id: str = None, ORIENTATION: int = 0): + super(Separator, self).__init__() + + builder = settings_manager.get_builder() + if id: + builder.expose_object(id, self) + + self.ORIENTATION = ORIENTATION + self._setup_styling() + self._setup_signals() + self._load_widgets() + + self.show() + + + def _setup_styling(self): + # HORIZONTAL = 0, VERTICAL = 1 + self.set_orientation(self.ORIENTATION) + + + def _setup_signals(self): + ... + + def _load_widgets(self): + ... diff --git a/src/plugins/manifest.py b/src/plugins/manifest.py index 4088eed..1b93f34 100644 --- a/src/plugins/manifest.py +++ b/src/plugins/manifest.py @@ -61,4 +61,8 @@ class ManifestProcessor: if isinstance(requests["bind_keys"], list): loading_data["bind_keys"] = requests["bind_keys"] + if "pass_ui_objects" in keys: + if isinstance(requests["pass_ui_objects"], list): + loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ] + return self._plugin, loading_data diff --git a/src/plugins/plugin_base.py b/src/plugins/plugin_base.py index 3130bb4..3650495 100644 --- a/src/plugins/plugin_base.py +++ b/src/plugins/plugin_base.py @@ -1,6 +1,7 @@ # Python imports import os import time +import inspect # Lib imports @@ -12,7 +13,8 @@ class PluginBaseException(Exception): class PluginBase: - def __init__(self): + def __init__(self, **kwargs): + super().__init__(**kwargs) self.name = "Example Plugin" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus # where self.name should not be needed for message comms @@ -36,13 +38,6 @@ class PluginBase: """ raise PluginBaseException("Method hasn't been overriden...") - def set_event_system(self, event_system): - """ - Requests Key: 'pass_events': "true" - Must define in plugin if "pass_events" is set to "true" string. - """ - self._event_system = event_system - def set_ui_object_collection(self, ui_objects): """ Requests Key: "pass_ui_objects": [""] @@ -51,9 +46,45 @@ class PluginBase: """ self._ui_objects = ui_objects + def set_event_system(self, event_system): + """ + Requests Key: 'pass_events': "true" + Must define in plugin if "pass_events" is set to "true" string. + """ + self._event_system = event_system + def subscribe_to_events(self): ... + def _connect_builder_signals(self, caller_class, builder): + classes = [caller_class] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + logger.debug(repr(e)) + + builder.connect_signals(handlers) + + def reload_package(self, plugin_path, module_dict_main=locals()): + import importlib + from pathlib import Path + + def reload_package_recursive(current_dir, module_dict): + for path in current_dir.iterdir(): + if "__init__" in str(path) or path.stem not in module_dict: + continue + + if path.is_file() and path.suffix == ".py": + importlib.reload(module_dict[path.stem]) + elif path.is_dir(): + reload_package_recursive(path, module_dict[path.stem].__dict__) + + reload_package_recursive(Path(plugin_path).parent, module_dict_main["module_dict_main"]) + def clear_children(self, widget: type) -> None: """ Clear children of a gtk widget. """ diff --git a/src/plugins/plugins_controller.py b/src/plugins/plugins_controller.py index eae8678..1b88ab5 100644 --- a/src/plugins/plugins_controller.py +++ b/src/plugins/plugins_controller.py @@ -53,7 +53,7 @@ class PluginsController: self.reload_plugins(file) def load_plugins(self, file: str = None) -> None: - print(f"Loading plugins...") + logger.info(f"Loading plugins...") parent_path = os.getcwd() for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]: @@ -68,8 +68,8 @@ class PluginsController: module = self.load_plugin_module(path, folder, target) self.execute_plugin(module, plugin, loading_data) except Exception as e: - print(f"Malformed Plugin: Not loading -->: '{folder}' !") - traceback.print_exc() + logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") + logger.debug("Trace: ", traceback.print_exc()) os.chdir(parent_path) @@ -106,7 +106,7 @@ class PluginsController: plugin.reference.set_ui_object_collection( loading_data["pass_ui_objects"] ) if "pass_events" in keys: - plugin.reference.set_fm_event_system(event_system) + plugin.reference.set_event_system(event_system) plugin.reference.subscribe_to_events() if "bind_keys" in keys: @@ -116,4 +116,4 @@ class PluginsController: self._plugin_collection.append(plugin) def reload_plugins(self, file: str = None) -> None: - print(f"Reloading plugins... stub.") + logger.info(f"Reloading plugins... stub.") diff --git a/src/utils/keybindings.py b/src/utils/keybindings.py index cb47685..3571207 100644 --- a/src/utils/keybindings.py +++ b/src/utils/keybindings.py @@ -42,6 +42,17 @@ class Keybindings: self.keymap = Gdk.Keymap.get_default() self.configure({}) + def print_keys(self): + print(self.keys) + + def append_bindings(self, combos): + """ Accept new binding(s) and reload """ + for item in combos: + method, keys = item.split(":") + self.keys[method] = keys + + self.reload() + def configure(self, bindings): """ Accept new bindings and reconfigure with them """ self.keys = bindings diff --git a/user_config/usr/share/newton/key-bindings.json b/user_config/usr/share/newton/key-bindings.json index 7c98d49..ae12253 100644 --- a/user_config/usr/share/newton/key-bindings.json +++ b/user_config/usr/share/newton/key-bindings.json @@ -6,10 +6,13 @@ "tear_down" : "q", "toggle_highlight_line" : "h", "open_files" : "o", + "move_lines_up" : "Up", + "move_lines_down" : "Down", "keyboard_create_tab" : "t", "keyboard_close_tab" : "w", "keyboard_save_file" : "s", "keyboard_insert_mark" : "m", + "keyboard_tggl_comment" : "slash", "keyboard_clear_marks" : "m", "keyboard_save_file_as" : "s", "keyboard_up" : "Up", diff --git a/user_config/usr/share/newton/stylesheet.css b/user_config/usr/share/newton/stylesheet.css index 24624db..cf55153 100644 --- a/user_config/usr/share/newton/stylesheet.css +++ b/user_config/usr/share/newton/stylesheet.css @@ -1,4 +1,7 @@ /* ---- make most desired things base transparent ---- */ +popover, +/* popover *, */ +popover > box window > box, window > box > box, window > box > box > paned, @@ -57,6 +60,13 @@ notebook > stack > scrolledwindow > textview { color: rgba(255, 255, 255, 1); } +/* any popover */ +popover { + background: rgba(39, 43, 52, 0.86); + color: rgba(255, 255, 255, 1); +} + + /* ---- miniview properties ---- */ /* the mini view container of text */ .mini-view > text { diff --git a/user_config/usr/share/newton/ui_widgets/general_info_ui.glade b/user_config/usr/share/newton/ui_widgets/general_info_ui.glade index 7594499..a1e394c 100644 --- a/user_config/usr/share/newton/ui_widgets/general_info_ui.glade +++ b/user_config/usr/share/newton/ui_widgets/general_info_ui.glade @@ -9,8 +9,7 @@ 10 10 10 - 6 - 6 + 5 15 top