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

View File

@@ -4,8 +4,5 @@
"credit": "Hamad Al Marri",
"version": "0.0.1",
"support": "",
"requests": {
"pass_events": true,
"bind_keys": ["Commentzar||keyboard_tggl_comment:<Control>slash"]
}
"requests": {}
}

View File

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

View File

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

View File

@@ -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 = "<Control>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() )

View File

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

View File

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

View File

@@ -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!",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,7 @@
{
"name": "Search/Replace",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

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)

View File

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

View File

@@ -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 = "<Control>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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,299 +0,0 @@
<?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="GtkImage" id="only-in-selection">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pixbuf">../../icons/only-in-selection.png</property>
</object>
<object class="GtkImage" id="whole-word">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pixbuf">../../icons/whole-word.png</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="sensitive">False</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="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>
<property name="image">only-in-selection</property>
<property name="always-show-image">True</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="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>
<property name="image">whole-word</property>
<property name="always-show-image">True</property>
<signal name="toggled" handler="tggle_whole_word_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="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>
<signal name="clicked" handler="replace_all" swapped="no"/>
</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>
<signal name="clicked" handler="replace" swapped="no"/>
</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="focus-on-click">False</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>
<signal name="clicked" handler="find_all" swapped="no"/>
</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="focus-on-click">False</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>
<signal name="clicked" handler="find_next" swapped="no"/>
</object>
<packing>
<property name="left-attach">8</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkSearchEntry" id="find_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">Find in current buffer</property>
<signal name="key-release-event" handler="on_enter_search" swapped="no"/>
<signal name="search-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="GtkSearchEntry" id="replace_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</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>
</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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ from ...key_mapper import KeyMapper
from .states import *
class SourceViewStateManager:
def __init__(self):
self.key_mapper: KeyMapper = KeyMapper()

View File

@@ -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 "<Control>" in keyname:
state = state | CtrlKeyState
if "<Shift>" in keyname:
state = state | ShiftKeyState
if "<Alt>" in keyname:
state = state | AltKeyState
keyname = keyname.replace("<Control>", "") \
.replace("<Shift>", "") \
.replace("<Alt>", "") \
.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 "<Control>" in keyname:
state = state | CtrlKeyState
if "<Shift>" in keyname:
state = state | ShiftKeyState
if "<Alt>" in keyname:
state = state | AltKeyState
keyname = keyname.replace("<Control>", "") \
.replace("<Shift>", "") \
.replace("<Alt>", "") \
.lower()
getattr(self.states[state], press_state)[keyname] = command
def _key_press_event(self, eve):
keyname = Gdk.keyval_name(eve.keyval).lower()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,5 +9,5 @@ from .code_event import CodeEvent
@dataclass
class GetCommandSystemEvent(CodeEvent):
class GetNewCommandSystemEvent(CodeEvent):
...

View File

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

View File

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

View File

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

View File

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