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:45:07 -06:00
parent 69c8418a72
commit 6714053776
51 changed files with 176 additions and 68 deletions

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
# Python imports
import re
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
from .provider_response_cache import ProviderResponseCache
class Provider(GtkSource.CompletionWords):
"""
This is a Words Completion Provider.
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
"""
__gtype_name__ = 'WordsCompletionProvider'
def __init__(self):
super(Provider, self).__init__()
self.response_cache: ProviderResponseCache = ProviderResponseCache()
def do_get_name(self):
return 'Words Completion'
def do_match(self, context):
word = self.response_cache.get_word(context)
if not word or len(word) < 2: return False
return True
def do_get_priority(self):
return 0
def do_activate_proposal(self, proposal, iter_):
buffer = iter_.get_buffer()
# Note: Flag mostly intended for SourceViewsMultiInsertState
# to insure marker processes inserted text correctly.
buffer.is_processing_completion = True
return False
def do_get_activation(self):
""" The context for when a provider will show results """
# return GtkSource.CompletionActivation.NONE
# return GtkSource.CompletionActivation.USER_REQUESTED
return GtkSource.CompletionActivation.INTERACTIVE

View File

@@ -0,0 +1,7 @@
{
"name": "Words Completer",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -0,0 +1,39 @@
# 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 .provider import Provider
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
self.provider: Provider = None
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
self.provider = Provider()
event = Event_Factory.create_event(
"register_provider",
provider_name = "Words Completer",
provider = self.provider,
language_ids = []
)
self.message_to("completion", event)
def run(self):
...

View File

@@ -0,0 +1,76 @@
# Python imports
import re
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GObject
from gi.repository import GtkSource
# Application imports
from .provider_response_cache import ProviderResponseCache
class Provider(GObject.GObject, GtkSource.CompletionProvider):
"""
This is a Words Completion Provider.
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
"""
# __gtype_name__ = 'WordsCompletionProvider'
def __init__(self):
super(Provider, self).__init__()
self.response_cache: ProviderResponseCache = ProviderResponseCache()
def do_get_name(self):
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
def do_get_priority(self):
return 0
def do_activate_proposal(self, proposal, iter_):
buffer = iter_.get_buffer()
# Note: Flag mostly intended for SourceViewsMultiInsertState
# to insure marker processes inserted text correctly.
buffer.is_processing_completion = True
return False
def do_get_activation(self):
""" The context for when a provider will show results """
# return GtkSource.CompletionActivation.NONE
# return GtkSource.CompletionActivation.USER_REQUESTED
return GtkSource.CompletionActivation.INTERACTIVE
def do_populate(self, context):
word = self.response_cache.get_word(context)
results = self.response_cache.filter_with_context(context)
# results = self.response_cache.filter(word)
# if not results:
# results = self.response_cache.filter_with_context(context)
proposals = []
for entry in results:
proposals.append(
self.response_cache.create_completion_item(
entry["label"],
entry["text"],
entry["info"]
)
)
context.add_proposals(self, proposals, True)

View File

@@ -0,0 +1,121 @@
# Python imports
from concurrent.futures import ThreadPoolExecutor
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GLib
from gi.repository import GtkSource
# Application imports
from libs.event_factory import Code_Event_Types
from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase
class ProviderResponseCache(ProviderResponseCacheBase):
def __init__(self):
super(ProviderResponseCache, self).__init__()
self.matchers: dict = {}
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
buffer = event.file.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()
del self.matchers[event.file.buffer]
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
...
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
buffer = event.file.buffer
with ThreadPoolExecutor(max_workers = 1) as executor:
executor.submit(self._handle_change, 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
if not buffer in self.matchers:
GLib.idle_add(self.load_as_new_set, buffer, data)
return
new_words = self.get_all_words(data)
GLib.idle_add(self.load_into_set, buffer, new_words)
def filter(self, word: str) -> list[dict]:
response: list[dict] = []
for entry in self.matchers:
if not word in entry: continue
data = self.matchers[entry]
response.append(data)
return response
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
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
data = {
"label": entry,
"text": entry,
"info": ""
}
response.append(data)
return response
def load_empty_set(self, buffer):
self.matchers[buffer] = set()
def load_into_set(self, buffer, new_words):
self.matchers[buffer].update(new_words)
def load_as_new_set(self, buffer, data):
self.matchers[buffer] = self.get_all_words(data)
def get_all_words(self, data: str):
words = set()
def is_word_char(c):
return c.isalnum() or c == '_'
size = len(data)
i = 0
while i < size:
# Skip non-word characters
while i < size and not is_word_char(data[i]):
i += 1
start = i
# Consume word characters
while i < size and is_word_char(data[i]):
i += 1
word = data[start:i]
if not word: continue
words.add(word)
return words