Moved plugins and refactor command system

- Moved plugins to apropriate sub folders
- Refactor command system with new add_command method and rename GetCommandSystemEvent to GetNewCommandSystemEvent
- Add RegisterCommandEvent for dynamic command registration
- Change footer container orientation to VERTICAL
- Add search-highlight tag to source buffer
- Add file change detection (deleted, externally modified) in source_file
- Add JSON prettify option to source view popup menu
- Enable hexpand on VTE widget
- Update plugins_controller_mixin to use widget_registry
This commit is contained in:
2026-02-18 23:49:01 -06:00
parent 2819598ae2
commit 61b8bbc5fa
89 changed files with 850 additions and 1029 deletions

View File

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

View File

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

View File

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

View File

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