Worked on the plugin system; added preliminary search and replace plugin

This commit is contained in:
itdominator 2023-10-10 22:26:05 -05:00
parent 2caf381742
commit dd086ec1d6
19 changed files with 589 additions and 34 deletions

View File

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

View File

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

View File

@ -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:<Control>f"]
}
}
}

View File

@ -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):
...

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkImage" id="close_img">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stock">gtk-close</property>
</object>
<object class="GtkPopover" id="search_replace_dialog">
<property name="can-focus">False</property>
<property name="modal">False</property>
<property name="transitions-enabled">False</property>
<property name="constrain-to">none</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="find_status_lbl">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="label" translatable="yes">Find in Current Buffer</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="find_options_lbl">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xpad">20</property>
<property name="label" translatable="yes">Finding with Options: Case Insensitive</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="layout-style">start</property>
<child>
<object class="GtkToggleButton">
<property name="label" translatable="yes">.*</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Use Regex</property>
<signal name="toggled" handler="tggle_regex" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton">
<property name="label" translatable="yes">Aa</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Match Case</property>
<signal name="toggled" handler="tggle_case_sensitive" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton">
<property name="label" translatable="yes">togglebutton</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Only In Selection</property>
<signal name="toggled" handler="tggle_selection_only_scan" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton">
<property name="label" translatable="yes">togglebutton</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Whole Word</property>
<signal name="toggled" handler="tggle_fuzzy_search" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Close Panel</property>
<property name="image">close_img</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="_tggl_search_replace" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<!-- n-columns=10 n-rows=2 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="column-homogeneous">True</property>
<child>
<object class="GtkEntry" id="find_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-start">5</property>
<property name="margin-end">10</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="placeholder-text" translatable="yes">Find in current buffer</property>
<signal name="changed" handler="search_for_string" swapped="no"/>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="width">8</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="replace_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-start">5</property>
<property name="margin-end">10</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="placeholder-text" translatable="yes">Replace in current buffer</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
<property name="width">8</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Replace All</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Replace All</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
</object>
<packing>
<property name="left-attach">9</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Replace</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Replace Next</property>
<property name="margin-start">5</property>
<property name="margin-end">10</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
</object>
<packing>
<property name="left-attach">8</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Find All</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
</object>
<packing>
<property name="left-attach">9</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Find</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
</object>
<packing>
<property name="left-attach">8</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">10</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -6,7 +6,7 @@
"support": "",
"requests": {
"ui_target": "plugin_control_list",
"pass_fm_events": "true",
"pass_events": "true",
"bind_keys": ["Example Plugin||send_message:<Control>f"]
}
}

View File

@ -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"))

View File

@ -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):
...

View File

@ -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":

View File

@ -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)

View File

@ -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())

View File

@ -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):
...

View File

@ -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

View File

@ -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. """

View File

@ -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.")

View File

@ -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

View File

@ -6,10 +6,13 @@
"tear_down" : "<Control>q",
"toggle_highlight_line" : "<Control>h",
"open_files" : "<Control>o",
"move_lines_up" : "<Control>Up",
"move_lines_down" : "<Control>Down",
"keyboard_create_tab" : "<Control>t",
"keyboard_close_tab" : "<Control>w",
"keyboard_save_file" : "<Control>s",
"keyboard_insert_mark" : "<Control>m",
"keyboard_tggl_comment" : "<Control>slash",
"keyboard_clear_marks" : "<Shift><Control>m",
"keyboard_save_file_as" : "<Shift><Control>s",
"keyboard_up" : "Up",

View File

@ -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 {

View File

@ -9,8 +9,7 @@
<property name="margin-right">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<property name="margin-top">5</property>
<property name="spacing">15</property>
<property name="baseline-position">top</property>
<child type="center">