Major completion provider overhaul; pluigin load and pattern improvements; css overhaul/cleanup; source view state modes added
This commit is contained in:
@@ -24,11 +24,14 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self._setup_controller_data()
|
self._setup_controller_data()
|
||||||
|
|
||||||
|
self._load_plugins(is_pre = True)
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_controllers()
|
self._load_controllers()
|
||||||
self._load_plugins_and_files()
|
self._load_plugins(is_pre = False)
|
||||||
|
self._load_files()
|
||||||
|
|
||||||
logger.info(f"Made it past {self.__class__} loading...")
|
logger.info(f"Made it past {self.__class__} loading...")
|
||||||
settings_manager.set_end_load_time()
|
settings_manager.set_end_load_time()
|
||||||
@@ -62,13 +65,19 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
|
|||||||
def _load_controllers(self):
|
def _load_controllers(self):
|
||||||
BridgeController()
|
BridgeController()
|
||||||
|
|
||||||
def _load_plugins_and_files(self):
|
def _load_plugins(self, is_pre: bool):
|
||||||
args, unknownargs = settings_manager.get_starting_args()
|
args, unknownargs = settings_manager.get_starting_args()
|
||||||
|
if args.no_plugins == "true": return
|
||||||
|
|
||||||
if args.no_plugins == "false":
|
if is_pre:
|
||||||
self.plugins_controller.pre_launch_plugins()
|
self.plugins_controller.pre_launch_plugins()
|
||||||
self.plugins_controller.post_launch_plugins()
|
return
|
||||||
|
|
||||||
|
if not is_pre:
|
||||||
|
self.plugins_controller.post_launch_plugins()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _load_files(self):
|
||||||
for file in settings_manager.get_starting_files():
|
for file in settings_manager.get_starting_files():
|
||||||
event_system.emit("post-file-to-ipc", file)
|
event_system.emit("post-file-to-ipc", file)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from .controllers.files_controller import FilesController
|
|||||||
from .controllers.tabs_controller import TabsController
|
from .controllers.tabs_controller import TabsController
|
||||||
from .controllers.commands_controller import CommandsController
|
from .controllers.commands_controller import CommandsController
|
||||||
from .controllers.completion_controller import CompletionController
|
from .controllers.completion_controller import CompletionController
|
||||||
from .controllers.source_views_controller import SourceViewsController
|
from .controllers.views.source_views_controller import SourceViewsController
|
||||||
|
|
||||||
from .mini_view_widget import MiniViewWidget
|
from .mini_view_widget import MiniViewWidget
|
||||||
|
|
||||||
@@ -50,7 +50,12 @@ class CodeBase:
|
|||||||
return self.miniview_widget
|
return self.miniview_widget
|
||||||
|
|
||||||
def create_source_view(self):
|
def create_source_view(self):
|
||||||
return self.controller_manager["source_views"].create_source_view()
|
source_view = self.controller_manager["source_views"].create_source_view()
|
||||||
|
self.controller_manager["completion"].register_completer(
|
||||||
|
source_view.get_completion()
|
||||||
|
)
|
||||||
|
|
||||||
|
return source_view
|
||||||
|
|
||||||
def first_map_load(self):
|
def first_map_load(self):
|
||||||
self.controller_manager["source_views"].first_map_load()
|
self.controller_manager["source_views"].first_map_load()
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ class CommandSystem:
|
|||||||
|
|
||||||
|
|
||||||
def emit(self, event: Code_Event_Types.CodeEvent):
|
def emit(self, event: Code_Event_Types.CodeEvent):
|
||||||
""" Monky patch 'emit' from command controller... """
|
""" Monkey patch 'emit' from command controller... """
|
||||||
...
|
...
|
||||||
|
|
||||||
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
|
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
|
||||||
""" Monky patch 'emit' from command controller... """
|
""" Monkey patch 'emit_to' from command controller... """
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,4 +15,14 @@ def execute(
|
|||||||
view: GtkSource.View = None
|
view: GtkSource.View = None
|
||||||
):
|
):
|
||||||
logger.debug("Command: Show Completion")
|
logger.debug("Command: Show Completion")
|
||||||
|
completer = view.get_completion()
|
||||||
|
providers = completer.get_providers()
|
||||||
|
|
||||||
|
if not providers:
|
||||||
view.command.request_completion(view)
|
view.command.request_completion(view)
|
||||||
|
return
|
||||||
|
|
||||||
|
completer.start(
|
||||||
|
providers,
|
||||||
|
completer.create_context()
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
from gi.repository import GObject
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ExampleCompletionProvider(GObject.GObject, GtkSource.CompletionProvider):
|
|
||||||
"""
|
|
||||||
This is a custom Completion Example Provider.
|
|
||||||
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'ExampleCompletionProvider'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
GObject.Object.__init__(self)
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
""" Returns: a new string containing the name of the provider. """
|
|
||||||
return 'Example Completion Provider'
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
""" Get whether the provider match the context of completion detailed in context. """
|
|
||||||
# NOTE: True for debugging but context needs to normally get checked for actual usage needs.
|
|
||||||
# TODO: Fix me
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
""" Determin position in result list along other providor results. """
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# 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):
|
|
||||||
"""
|
|
||||||
In this instance, it will do 2 things:
|
|
||||||
1) always provide Hello World! (Not ideal but an option so its in the example)
|
|
||||||
2) Utilizes the Gtk.TextIter from the TextBuffer to determine if there is a jinja
|
|
||||||
example of '{{ custom.' if so it will provide you with the options of foo and bar.
|
|
||||||
If selected it will insert foo }} or bar }}, completing your syntax...
|
|
||||||
|
|
||||||
PLEASE NOTE the GtkTextIter Logic and regex are really rough and should be adjusted and tuned
|
|
||||||
"""
|
|
||||||
|
|
||||||
proposals = [
|
|
||||||
GtkSource.CompletionItem(label='Hello World!', text = 'Hello World!', icon = None, info = None) # NOTE: Always proposed...
|
|
||||||
]
|
|
||||||
|
|
||||||
# Gtk Versions differ on get_iter responses...
|
|
||||||
end_iter = context.get_iter()
|
|
||||||
if not isinstance(end_iter, Gtk.TextIter):
|
|
||||||
_, end_iter = context.get_iter()
|
|
||||||
|
|
||||||
if end_iter:
|
|
||||||
buf = end_iter.get_buffer()
|
|
||||||
mov_iter = end_iter.copy()
|
|
||||||
if mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY):
|
|
||||||
mov_iter, _ = mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY)
|
|
||||||
left_text = buf.get_text(mov_iter, end_iter, True)
|
|
||||||
else:
|
|
||||||
left_text = ''
|
|
||||||
|
|
||||||
if re.match(r'.*\{\{\s*custom\.$', left_text):
|
|
||||||
proposals.append(
|
|
||||||
GtkSource.CompletionItem(label='foo', text='foo }}') # optionally proposed based on left search via regex
|
|
||||||
)
|
|
||||||
proposals.append(
|
|
||||||
GtkSource.CompletionItem(label='bar', text='bar }}') # optionally proposed based on left search via regex
|
|
||||||
)
|
|
||||||
|
|
||||||
context.add_proposals(self, proposals, True)
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
from gi.repository import GObject
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LSPCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
|
|
||||||
"""
|
|
||||||
This code is an LSP code completion plugin for Newton.
|
|
||||||
# NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'LSPProvider'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
GObject.Object.__init__(self)
|
|
||||||
|
|
||||||
self._icon_theme = Gtk.IconTheme.get_default()
|
|
||||||
|
|
||||||
self.lsp_data = None
|
|
||||||
|
|
||||||
|
|
||||||
def pre_populate(self, context):
|
|
||||||
...
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
return "LSP Code Completion"
|
|
||||||
|
|
||||||
def get_iter_correctly(self, context):
|
|
||||||
return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter()
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
iter = self.get_iter_correctly(context)
|
|
||||||
iter.backward_char()
|
|
||||||
|
|
||||||
buffer = iter.get_buffer()
|
|
||||||
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
|
|
||||||
return False
|
|
||||||
|
|
||||||
ch = iter.get_char()
|
|
||||||
# NOTE: Look to re-add or apply supprting logic to use spaces
|
|
||||||
# As is it slows down the editor in certain contexts...
|
|
||||||
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
|
||||||
if not (ch in ('_', '.') or ch.isalnum()):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
return 5
|
|
||||||
|
|
||||||
def do_populate(self, context, items = []):
|
|
||||||
# self.lsp_data
|
|
||||||
proposals = []
|
|
||||||
|
|
||||||
comp_item = GtkSource.CompletionItem.new()
|
|
||||||
comp_item.set_label("LSP Class")
|
|
||||||
comp_item.set_text("LSP Code")
|
|
||||||
# comp_item.set_icon(self.get_icon_for_type(completion.type))
|
|
||||||
comp_item.set_info("A test LSP completion item...")
|
|
||||||
|
|
||||||
context.add_proposals(self, [comp_item], True)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# def do_populate(self, context, items = []):
|
|
||||||
# if hasattr(self._source_view, "completion_items"):
|
|
||||||
# items = self._source_view.completion_items
|
|
||||||
|
|
||||||
# proposals = []
|
|
||||||
# for item in items:
|
|
||||||
# proposals.append( self.create_completion_item(item) )
|
|
||||||
|
|
||||||
# context.add_proposals(self, proposals, True)
|
|
||||||
|
|
||||||
# def get_icon_for_type(self, _type):
|
|
||||||
# try:
|
|
||||||
# return self._theme.load_icon(icon_names[_type.lower()], 16, 0)
|
|
||||||
# except:
|
|
||||||
# ...
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
|
|
||||||
# except:
|
|
||||||
# ...
|
|
||||||
|
|
||||||
# return None
|
|
||||||
|
|
||||||
# def create_completion_item(self, item):
|
|
||||||
# comp_item = GtkSource.CompletionItem.new()
|
|
||||||
# keys = item.keys()
|
|
||||||
# comp_item.set_label(item["label"])
|
|
||||||
|
|
||||||
# if "insertText" in keys:
|
|
||||||
# comp_item.set_text(item["insertText"])
|
|
||||||
|
|
||||||
# if "additionalTextEdits" in keys:
|
|
||||||
# comp_item.additionalTextEdits = item["additionalTextEdits"]
|
|
||||||
|
|
||||||
# return comp_item
|
|
||||||
|
|
||||||
|
|
||||||
# def create_completion_item(self, item):
|
|
||||||
# comp_item = GtkSource.CompletionItem.new()
|
|
||||||
# comp_item.set_label(item.label)
|
|
||||||
|
|
||||||
# if item.textEdit:
|
|
||||||
# if isinstance(item.textEdit, dict):
|
|
||||||
# comp_item.set_text(item.textEdit["newText"])
|
|
||||||
# else:
|
|
||||||
# comp_item.set_text(item.textEdit)
|
|
||||||
# else:
|
|
||||||
# comp_item.set_text(item.insertText)
|
|
||||||
|
|
||||||
# comp_item.set_icon( self.get_icon_for_type(item.kind) )
|
|
||||||
# comp_item.set_info(item.documentation)
|
|
||||||
|
|
||||||
# return comp_item
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderResponseCacheException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderResponseCacheBase:
|
||||||
|
def __init__(self):
|
||||||
|
super(ProviderResponseCacheBase, self).__init__()
|
||||||
|
|
||||||
|
self._icon_theme = Gtk.IconTheme.get_default()
|
||||||
|
|
||||||
|
|
||||||
|
def process_file_load(self, buffer: GtkSource.Buffer):
|
||||||
|
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_load' not implemented...")
|
||||||
|
|
||||||
|
def process_file_close(self, buffer: GtkSource.Buffer):
|
||||||
|
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_close' not implemented...")
|
||||||
|
|
||||||
|
def process_file_save(self, buffer: GtkSource.Buffer):
|
||||||
|
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...")
|
||||||
|
|
||||||
|
def process_file_change(self, buffer: GtkSource.Buffer):
|
||||||
|
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_change' not implemented...")
|
||||||
|
|
||||||
|
def filter(self, word: str):
|
||||||
|
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...")
|
||||||
|
|
||||||
|
def filter_with_context(self, context: GtkSource.CompletionContext):
|
||||||
|
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter_with_context' not implemented...")
|
||||||
|
|
||||||
|
|
||||||
|
def create_completion_item(
|
||||||
|
self,
|
||||||
|
label: str = "",
|
||||||
|
text: str = "",
|
||||||
|
info: str = "",
|
||||||
|
completion: any = None
|
||||||
|
):
|
||||||
|
if not label or not text: return
|
||||||
|
|
||||||
|
comp_item = GtkSource.CompletionItem.new()
|
||||||
|
comp_item.set_label(label)
|
||||||
|
comp_item.set_text(text)
|
||||||
|
|
||||||
|
if info:
|
||||||
|
comp_item.set_info(info)
|
||||||
|
# comp_item.set_markup(f"<h3>{info}</h3>")
|
||||||
|
|
||||||
|
if completion:
|
||||||
|
comp_item.set_icon(
|
||||||
|
self.get_icon_for_type(completion.type)
|
||||||
|
)
|
||||||
|
|
||||||
|
return comp_item
|
||||||
|
|
||||||
|
def get_word(self, context):
|
||||||
|
start_iter = context.get_iter()
|
||||||
|
end_iter = None
|
||||||
|
|
||||||
|
if not isinstance(start_iter, Gtk.TextIter):
|
||||||
|
_, start_iter = context.get_iter()
|
||||||
|
end_iter = start_iter.copy()
|
||||||
|
|
||||||
|
if not start_iter.starts_word():
|
||||||
|
start_iter.backward_word_start()
|
||||||
|
|
||||||
|
end_iter.forward_word_end()
|
||||||
|
|
||||||
|
buffer = start_iter.get_buffer()
|
||||||
|
|
||||||
|
return buffer.get_text(start_iter, end_iter, False)
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('GtkSource', '4')
|
|
||||||
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GtkSource
|
|
||||||
from gi.repository import GObject
|
|
||||||
|
|
||||||
import jedi
|
|
||||||
from jedi.api import Script
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Find real icon names...
|
|
||||||
icon_names = {
|
|
||||||
'import': '',
|
|
||||||
'module': '',
|
|
||||||
'class': '',
|
|
||||||
'function': '',
|
|
||||||
'statement': '',
|
|
||||||
'param': ''
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Jedi:
|
|
||||||
def get_script(file, doc_text):
|
|
||||||
return Script(code = doc_text, path = file)
|
|
||||||
|
|
||||||
|
|
||||||
class PythonCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
|
|
||||||
"""
|
|
||||||
This code is A python code completion plugin for Newton.
|
|
||||||
# NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi
|
|
||||||
"""
|
|
||||||
__gtype_name__ = 'PythonProvider'
|
|
||||||
|
|
||||||
def __init__(self, file):
|
|
||||||
GObject.Object.__init__(self)
|
|
||||||
self._theme = Gtk.IconTheme.get_default()
|
|
||||||
self._file = file
|
|
||||||
|
|
||||||
def do_get_name(self):
|
|
||||||
return "Python Code Completion"
|
|
||||||
|
|
||||||
def get_iter_correctly(self, context):
|
|
||||||
return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter()
|
|
||||||
|
|
||||||
def do_match(self, context):
|
|
||||||
iter = self.get_iter_correctly(context)
|
|
||||||
iter.backward_char()
|
|
||||||
|
|
||||||
buffer = iter.get_buffer()
|
|
||||||
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
|
|
||||||
return False
|
|
||||||
|
|
||||||
ch = iter.get_char()
|
|
||||||
# NOTE: Look to re-add or apply supprting logic to use spaces
|
|
||||||
# As is it slows down the editor in certain contexts...
|
|
||||||
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
|
||||||
if not (ch in ('_', '.') or ch.isalnum()):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_get_priority(self):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def do_get_activation(self):
|
|
||||||
return GtkSource.CompletionActivation.INTERACTIVE
|
|
||||||
|
|
||||||
def do_populate(self, context):
|
|
||||||
# TODO: Maybe convert async?
|
|
||||||
it = self.get_iter_correctly(context)
|
|
||||||
buffer = it.get_buffer()
|
|
||||||
proposals = []
|
|
||||||
|
|
||||||
doc_text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False)
|
|
||||||
iter_cursor = buffer.get_iter_at_mark(buffer.get_insert())
|
|
||||||
linenum = iter_cursor.get_line() + 1
|
|
||||||
charnum = iter_cursor.get_line_index()
|
|
||||||
|
|
||||||
def create_generator():
|
|
||||||
for completion in Jedi.get_script(self._file, doc_text).complete(line = linenum, column = None, fuzzy = False):
|
|
||||||
comp_item = GtkSource.CompletionItem.new()
|
|
||||||
comp_item.set_label(completion.name)
|
|
||||||
comp_item.set_text(completion.name)
|
|
||||||
comp_item.set_icon(self.get_icon_for_type(completion.type))
|
|
||||||
comp_item.set_info(completion.docstring())
|
|
||||||
yield comp_item
|
|
||||||
|
|
||||||
for item in create_generator():
|
|
||||||
proposals.append(item)
|
|
||||||
|
|
||||||
context.add_proposals(self, proposals, True)
|
|
||||||
|
|
||||||
def get_icon_for_type(self, _type):
|
|
||||||
try:
|
|
||||||
return self._theme.load_icon(icon_names[_type.lower()], 16, 0)
|
|
||||||
except (KeyError, AttributeError, GObject.GError) as e:
|
|
||||||
return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
|
|
||||||
except (GObject.GError, AttributeError) as e:
|
|
||||||
return None
|
|
||||||
@@ -11,93 +11,84 @@ from gi.repository import GtkSource
|
|||||||
from libs.controllers.controller_base import ControllerBase
|
from libs.controllers.controller_base import ControllerBase
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
from ..completion_providers.example_completion_provider import ExampleCompletionProvider
|
|
||||||
from ..completion_providers.lsp_completion_provider import LSPCompletionProvider
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionController(ControllerBase):
|
class CompletionController(ControllerBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CompletionController, self).__init__()
|
super(CompletionController, self).__init__()
|
||||||
|
|
||||||
self._completor: GtkSource.Completion = None
|
self.words_provider = GtkSource.CompletionWords.new("words", None)
|
||||||
self._timeout_id: int = None
|
self.words_provider.props.activation = GtkSource.CompletionActivation.INTERACTIVE
|
||||||
self._lsp_provider: LSPCompletionProvider = LSPCompletionProvider()
|
|
||||||
|
|
||||||
|
self._completers: list[GtkSource.Completion] = []
|
||||||
|
self._providers: dict[str, GtkSource.CompletionProvider] = {}
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
if isinstance(event, Code_Event_Types.FocusedViewEvent):
|
if isinstance(event, Code_Event_Types.RegisterProviderEvent):
|
||||||
self._completor = event.view.get_completion()
|
self.register_provider(
|
||||||
|
event.provider_name,
|
||||||
if not self._timeout_id: return
|
event.provider,
|
||||||
|
event.language_ids
|
||||||
GLib.source_remove(self._timeout_id)
|
|
||||||
self._timeout_id = None
|
|
||||||
elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
|
|
||||||
self.request_completion()
|
|
||||||
# elif isinstance(event, Code_Event_Types.TextInsertedEvent):
|
|
||||||
# self.request_completion()
|
|
||||||
|
|
||||||
def _process_request_completion(self):
|
|
||||||
self._start_completion()
|
|
||||||
|
|
||||||
self._timeout_id = None
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _do_completion(self):
|
|
||||||
if self._completor.get_providers():
|
|
||||||
self._match_completion()
|
|
||||||
else:
|
|
||||||
self._start_completion()
|
|
||||||
|
|
||||||
def _match_completion(self):
|
|
||||||
"""
|
|
||||||
Note: Use IF providers were added to completion...
|
|
||||||
"""
|
|
||||||
self._completion.match(
|
|
||||||
self._completion.create_context()
|
|
||||||
)
|
)
|
||||||
|
elif isinstance(event, Code_Event_Types.AddedNewFileEvent):
|
||||||
|
self.provider_process_file_load(event)
|
||||||
|
elif isinstance(event, Code_Event_Types.RemovedFileEvent):
|
||||||
|
self.provider_process_file_close(event)
|
||||||
|
elif isinstance(event, Code_Event_Types.SavedFileEvent):
|
||||||
|
self.provider_process_file_save(event)
|
||||||
|
elif isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||||
|
self.provider_process_file_change(event)
|
||||||
|
# elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
|
||||||
|
# self.request_unbound_completion( event.view.get_completion() )
|
||||||
|
|
||||||
|
|
||||||
def _start_completion(self):
|
def register_completer(self, completer: GtkSource.Completion):
|
||||||
"""
|
self._completers.append(completer)
|
||||||
Note: Use IF NO providers have been added to completion...
|
|
||||||
"""
|
|
||||||
self._completor.start(
|
|
||||||
[
|
|
||||||
ExampleCompletionProvider(),
|
|
||||||
self._lsp_provider
|
|
||||||
],
|
|
||||||
self._completor.create_context()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
completer.add_provider(self.words_provider)
|
||||||
def set_completer(self, completer):
|
for provider in self._providers.values():
|
||||||
self._completor = completer
|
completer.add_provider(provider)
|
||||||
|
|
||||||
def request_completion(self):
|
|
||||||
if self._timeout_id:
|
|
||||||
GLib.source_remove(self._timeout_id)
|
|
||||||
|
|
||||||
self._timeout_id = GLib.timeout_add(
|
|
||||||
800,
|
|
||||||
self._process_request_completion
|
|
||||||
)
|
|
||||||
|
|
||||||
def register_provider(
|
def register_provider(
|
||||||
self,
|
self,
|
||||||
provider_name: str,
|
provider_name: str,
|
||||||
provider: GtkSource.CompletionProvider,
|
provider: GtkSource.CompletionProvider,
|
||||||
priority: int = 0,
|
language_ids: list = []
|
||||||
language_ids: list = None
|
|
||||||
):
|
):
|
||||||
"""Register completion providers with priority and language filtering"""
|
self._providers[provider_name] = provider
|
||||||
...
|
|
||||||
|
for completer in self._completers:
|
||||||
|
completer.add_provider(provider)
|
||||||
|
|
||||||
def unregister_provider(self, provider_name: str):
|
def unregister_provider(self, provider_name: str):
|
||||||
"""Remove completion providers"""
|
provider = self._providers[provider_name]
|
||||||
...
|
del self._providers[provider_name]
|
||||||
|
|
||||||
def get_active_providers(self, language_id: str = None) -> list:
|
for completer in self._completers:
|
||||||
"""Get providers filtered by language"""
|
completer.remove_provider(provider)
|
||||||
...
|
|
||||||
|
def provider_process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
||||||
|
self.words_provider.register(event.file.buffer)
|
||||||
|
|
||||||
|
for provider in self._providers.values():
|
||||||
|
provider.response_cache.process_file_load(event)
|
||||||
|
|
||||||
|
def provider_process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
||||||
|
self.words_provider.unregister(event.file.buffer)
|
||||||
|
|
||||||
|
for provider in self._providers.values():
|
||||||
|
provider.response_cache.process_file_close(event)
|
||||||
|
|
||||||
|
def provider_process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||||
|
for provider in self._providers.values():
|
||||||
|
provider.response_cache.process_file_save(event)
|
||||||
|
|
||||||
|
def provider_process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||||
|
for provider in self._providers.values():
|
||||||
|
provider.response_cache.process_file_change(event)
|
||||||
|
|
||||||
|
def request_unbound_completion(self, completer: GtkSource.Completion):
|
||||||
|
completer.start(
|
||||||
|
[ *self._providers.values() ],
|
||||||
|
completer.create_context()
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from libs.controllers.controller_base import ControllerBase
|
|
||||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
|
||||||
|
|
||||||
from ..command_system import CommandSystem
|
|
||||||
from ..key_mapper import KeyMapper
|
|
||||||
|
|
||||||
from ..source_view import SourceView
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SourceViewsController(ControllerBase, list):
|
|
||||||
def __init__(self):
|
|
||||||
super(SourceViewsController, self).__init__()
|
|
||||||
|
|
||||||
self.key_mapper: KeyMapper = KeyMapper()
|
|
||||||
self.active_view: SourceView = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_command_system(self):
|
|
||||||
event = Event_Factory.create_event("get_command_system")
|
|
||||||
self.message_to("commands", event)
|
|
||||||
command = event.response
|
|
||||||
|
|
||||||
del event
|
|
||||||
return command
|
|
||||||
|
|
||||||
def create_source_view(self):
|
|
||||||
source_view: SourceView = SourceView()
|
|
||||||
source_view.command = self.get_command_system()
|
|
||||||
source_view.command.set_data(source_view)
|
|
||||||
|
|
||||||
self._map_signals(source_view)
|
|
||||||
|
|
||||||
self.append(source_view)
|
|
||||||
return source_view
|
|
||||||
|
|
||||||
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.TextChangedEvent):
|
|
||||||
self.active_view.command.exec("update_info_bar")
|
|
||||||
|
|
||||||
def _map_signals(self, source_view: SourceView):
|
|
||||||
source_view.connect("focus-in-event", self._focus_in_event)
|
|
||||||
source_view.connect("move-cursor", self._move_cursor)
|
|
||||||
source_view.connect("key-press-event", self._key_press_event)
|
|
||||||
source_view.connect("key-release-event", self._key_release_event)
|
|
||||||
source_view.connect("button-press-event", self._button_press_event)
|
|
||||||
source_view.connect("button-release-event", self._button_release_event)
|
|
||||||
|
|
||||||
def _focus_in_event(self, view, eve):
|
|
||||||
self.active_view = view
|
|
||||||
|
|
||||||
view.command.exec("set_miniview")
|
|
||||||
view.command.exec("set_focus_border")
|
|
||||||
view.command.exec("update_info_bar")
|
|
||||||
|
|
||||||
event = Event_Factory.create_event("focused_view", view = view)
|
|
||||||
self.emit(event)
|
|
||||||
|
|
||||||
def _move_cursor(self, view, step, count, extend_selection):
|
|
||||||
buffer = view.get_buffer()
|
|
||||||
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
|
||||||
line = iter.get_line()
|
|
||||||
char = iter.get_line_offset()
|
|
||||||
|
|
||||||
event = Event_Factory.create_event(
|
|
||||||
"cursor_moved",
|
|
||||||
view = view,
|
|
||||||
buffer = buffer,
|
|
||||||
line = line,
|
|
||||||
char = char
|
|
||||||
)
|
|
||||||
|
|
||||||
self.emit(event)
|
|
||||||
|
|
||||||
view.command.exec("update_info_bar")
|
|
||||||
|
|
||||||
def _button_press_event(self, view, eve):
|
|
||||||
self.active_view.command.exec("update_info_bar")
|
|
||||||
|
|
||||||
def _button_release_event(self, view, eve):
|
|
||||||
self.active_view.command.exec("update_info_bar")
|
|
||||||
|
|
||||||
def _key_press_event(self, view, eve):
|
|
||||||
command = self.key_mapper._key_press_event(eve)
|
|
||||||
is_future = self.key_mapper._key_release_event(eve)
|
|
||||||
|
|
||||||
if is_future: return True
|
|
||||||
if not command: return False
|
|
||||||
|
|
||||||
view.command.exec(command)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _key_release_event(self, view, eve):
|
|
||||||
command = self.key_mapper._key_release_event(eve)
|
|
||||||
is_past = self.key_mapper._key_press_event(eve)
|
|
||||||
|
|
||||||
if is_past: return True
|
|
||||||
if not command: return False
|
|
||||||
|
|
||||||
view.command.exec(command)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _remove_file(self, event: Code_Event_Types.RemovedFileEvent):
|
|
||||||
for view in self:
|
|
||||||
if not event.file.buffer == view.get_buffer(): continue
|
|
||||||
if not event.next_file:
|
|
||||||
view.command.exec("new_file")
|
|
||||||
continue
|
|
||||||
|
|
||||||
view.set_buffer(event.next_file.buffer)
|
|
||||||
|
|
||||||
def first_map_load(self):
|
|
||||||
for view in self:
|
|
||||||
view.command.exec("new_file")
|
|
||||||
|
|
||||||
view = self[0]
|
|
||||||
view.grab_focus()
|
|
||||||
view.command.exec("load_start_files")
|
|
||||||
|
|
||||||
10
src/core/widgets/code/controllers/views/__init__.py
Normal file
10
src/core/widgets/code/controllers/views/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
Code Controllers Package
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .state_manager import SourceViewStateManager
|
||||||
|
from .signal_mapper import SourceViewSignalMapper
|
||||||
|
from .source_views_controller import SourceViewsController
|
||||||
|
|
||||||
|
# State imports
|
||||||
|
from .states import *
|
||||||
63
src/core/widgets/code/controllers/views/signal_mapper.py
Normal file
63
src/core/widgets/code/controllers/views/signal_mapper.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ...source_view import SourceView
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewSignalMapper:
|
||||||
|
def __init__(self):
|
||||||
|
self.active_view: SourceView = None
|
||||||
|
|
||||||
|
|
||||||
|
def bind_emit(self, emit: callable):
|
||||||
|
self.emit = emit
|
||||||
|
|
||||||
|
def set_state_manager(self, state_manager):
|
||||||
|
self.state_manager = state_manager
|
||||||
|
|
||||||
|
def connect_signals(self, source_view: SourceView):
|
||||||
|
signal_mappings = self._get_signal_mappings()
|
||||||
|
for signal, handler in signal_mappings.items():
|
||||||
|
source_view.connect(signal, handler)
|
||||||
|
|
||||||
|
def disconnect_signals(self, source_view: SourceView):
|
||||||
|
signal_mappings = self._get_signal_mappings()
|
||||||
|
for signal, handler in signal_mappings.items():
|
||||||
|
source_view.disconnect_by_func(handler)
|
||||||
|
|
||||||
|
def insert_text(self, file, string: str):
|
||||||
|
return self.state_manager.handle_insert_text(self.active_view, file, string)
|
||||||
|
|
||||||
|
def _get_signal_mappings(self):
|
||||||
|
return {
|
||||||
|
"focus-in-event": self._focus_in_event,
|
||||||
|
"move-cursor": self._move_cursor,
|
||||||
|
"key-press-event": self._key_press_event,
|
||||||
|
"key-release-event": self._key_release_event,
|
||||||
|
"button-press-event": self._button_press_event,
|
||||||
|
"button-release-event": self._button_release_event
|
||||||
|
}
|
||||||
|
|
||||||
|
def _focus_in_event(self, source_view: SourceView, eve):
|
||||||
|
self.active_view = source_view
|
||||||
|
return self.state_manager.handle_focus_in_event(source_view, eve, self.emit)
|
||||||
|
|
||||||
|
def _move_cursor(self, source_view: SourceView, step, count, extend_selection):
|
||||||
|
return self.state_manager.handle_move_cursor(
|
||||||
|
source_view, step, count, extend_selection, self.emit
|
||||||
|
)
|
||||||
|
|
||||||
|
def _button_press_event(self, source_view: SourceView, eve):
|
||||||
|
return self.state_manager.handle_button_press_event(source_view, eve)
|
||||||
|
|
||||||
|
def _button_release_event(self, source_view: SourceView, eve):
|
||||||
|
return self.state_manager.handle_button_release_event(source_view, eve)
|
||||||
|
|
||||||
|
def _key_press_event(self, source_view: SourceView, eve):
|
||||||
|
return self.state_manager.handle_key_press_event(source_view, eve)
|
||||||
|
|
||||||
|
def _key_release_event(self, source_view: SourceView, eve):
|
||||||
|
return self.state_manager.handle_key_release_event(source_view, eve)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.controllers.controller_base import ControllerBase
|
||||||
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
|
from ...source_view import SourceView
|
||||||
|
|
||||||
|
from .state_manager import SourceViewStateManager
|
||||||
|
from .signal_mapper import SourceViewSignalMapper
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewsController(ControllerBase, list):
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceViewsController, self).__init__()
|
||||||
|
|
||||||
|
self.state_manager: SourceViewStateManager = SourceViewStateManager()
|
||||||
|
self.signal_mapper: SourceViewSignalMapper = SourceViewSignalMapper()
|
||||||
|
|
||||||
|
self.signal_mapper.bind_emit(self.emit)
|
||||||
|
self.signal_mapper.set_state_manager(self.state_manager)
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
|
if isinstance(event, Code_Event_Types.RemovedFileEvent):
|
||||||
|
self._remove_file(event)
|
||||||
|
|
||||||
|
if not self.signal_mapper.active_view: return
|
||||||
|
|
||||||
|
if isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||||
|
if not self.signal_mapper.active_view: return
|
||||||
|
self.signal_mapper.active_view.command.exec("update_info_bar")
|
||||||
|
elif isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||||
|
self.signal_mapper.active_view.command.exec("update_info_bar")
|
||||||
|
elif isinstance(event, Code_Event_Types.TextInsertedEvent):
|
||||||
|
self.signal_mapper.insert_text(event.file, event.text)
|
||||||
|
|
||||||
|
def _get_command_system(self):
|
||||||
|
event = Event_Factory.create_event("get_command_system")
|
||||||
|
self.message_to("commands", event)
|
||||||
|
command = event.response
|
||||||
|
|
||||||
|
del event
|
||||||
|
return command
|
||||||
|
|
||||||
|
def _remove_file(self, event: Code_Event_Types.RemovedFileEvent):
|
||||||
|
for source_view in self:
|
||||||
|
if not event.file.buffer == source_view.get_buffer(): continue
|
||||||
|
if not event.next_file:
|
||||||
|
source_view.command.exec("new_file")
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_view.set_buffer(event.next_file.buffer)
|
||||||
|
|
||||||
|
def create_source_view(self):
|
||||||
|
source_view: SourceView = SourceView()
|
||||||
|
source_view.command = self._get_command_system()
|
||||||
|
source_view.command.set_data(source_view)
|
||||||
|
|
||||||
|
self.signal_mapper.connect_signals(source_view)
|
||||||
|
|
||||||
|
self.append(source_view)
|
||||||
|
return source_view
|
||||||
|
|
||||||
|
def first_map_load(self):
|
||||||
|
for source_view in self:
|
||||||
|
source_view.command.exec("new_file")
|
||||||
|
|
||||||
|
source_view = self[0]
|
||||||
|
source_view.grab_focus()
|
||||||
|
source_view.command.exec("load_start_files")
|
||||||
65
src/core/widgets/code/controllers/views/state_manager.py
Normal file
65
src/core/widgets/code/controllers/views/state_manager.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.states import SourceViewStates
|
||||||
|
|
||||||
|
from ...key_mapper import KeyMapper
|
||||||
|
|
||||||
|
from .states import *
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewStateManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.key_mapper: KeyMapper = KeyMapper()
|
||||||
|
|
||||||
|
self.states: dict = {
|
||||||
|
SourceViewStates.INSERT: SourceViewsInsertState(),
|
||||||
|
SourceViewStates.MULTIINSERT: SourceViewsMultiInsertState(),
|
||||||
|
SourceViewStates.COMMAND: SourceViewsCommandState(),
|
||||||
|
SourceViewStates.READONLY: SourceViewsReadOnlyState()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def handle_focus_in_event(self, source_view, eve, emit):
|
||||||
|
return self.states[source_view.state].focus_in_event(source_view, eve, emit)
|
||||||
|
|
||||||
|
def handle_insert_text(self, source_view, file, text):
|
||||||
|
return self.states[source_view.state].insert_text(file, text)
|
||||||
|
|
||||||
|
def handle_move_cursor(self, source_view, step, count, extend_selection, emit):
|
||||||
|
return self.states[source_view.state].move_cursor(
|
||||||
|
source_view, step, count, extend_selection, emit
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_button_press_event(self, source_view, eve):
|
||||||
|
return self.states[source_view.state].button_press_event(source_view, eve)
|
||||||
|
|
||||||
|
def handle_button_release_event(self, source_view, eve):
|
||||||
|
# Handle state transitions (multi-insert toggling)
|
||||||
|
self._handle_multi_insert_toggle(source_view, eve)
|
||||||
|
|
||||||
|
return self.states[source_view.state].button_release_event(source_view, eve)
|
||||||
|
|
||||||
|
def handle_key_press_event(self, source_view, eve):
|
||||||
|
return self.states[source_view.state].key_press_event(
|
||||||
|
source_view, eve, self.key_mapper
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_key_release_event(self, source_view, eve):
|
||||||
|
return self.states[source_view.state].key_release_event(
|
||||||
|
source_view, eve, self.key_mapper
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_multi_insert_toggle(self, source_view, eve):
|
||||||
|
is_control = self.key_mapper.is_control(eve)
|
||||||
|
if is_control and not source_view.state == SourceViewStates.MULTIINSERT:
|
||||||
|
logger.debug("Entered Multi-Insert Mode...")
|
||||||
|
source_view.state = SourceViewStates.MULTIINSERT
|
||||||
|
|
||||||
|
if not is_control and source_view.state == SourceViewStates.MULTIINSERT:
|
||||||
|
logger.debug("Entered Regular Insert Mode...")
|
||||||
|
self.states[source_view.state].clear_markers(source_view)
|
||||||
|
|
||||||
|
source_view.state = SourceViewStates.INSERT
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
Code Controllers Views States Package
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .source_view_insert_state import SourceViewsInsertState
|
||||||
|
from .source_view_multi_insert_state import SourceViewsMultiInsertState
|
||||||
|
from .source_view_command_state import SourceViewsCommandState
|
||||||
|
from .source_view_read_only_state import SourceViewsReadOnlyState
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
|
from libs.dto.states import SourceViewStates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewsCommandState:
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceViewsCommandState, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def focus_in_event(self, source_view, eve, emit):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def move_cursor(self, source_view, step, count, extend_selection, emit):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def insert_text(self, file, text):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def button_press_event(self, source_view, eve):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def button_release_event(self, source_view, eve):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def key_press_event(self, source_view, eve, key_mapper):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def key_release_event(self, source_view, eve, key_mapper):
|
||||||
|
return True
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
|
from libs.dto.states import SourceViewStates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewsInsertState:
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceViewsInsertState, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def focus_in_event(self, source_view, eve, emit):
|
||||||
|
source_view.command.exec("set_miniview")
|
||||||
|
source_view.command.exec("set_focus_border")
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
event = Event_Factory.create_event("focused_view", view = source_view)
|
||||||
|
emit(event)
|
||||||
|
|
||||||
|
def insert_text(self, file, text):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def move_cursor(self, source_view, step, count, extend_selection, emit):
|
||||||
|
buffer = source_view.get_buffer()
|
||||||
|
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
|
line = itr.get_line()
|
||||||
|
char = itr.get_line_offset()
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"cursor_moved",
|
||||||
|
view = source_view,
|
||||||
|
buffer = buffer,
|
||||||
|
line = line,
|
||||||
|
char = char
|
||||||
|
)
|
||||||
|
|
||||||
|
emit(event)
|
||||||
|
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def button_press_event(self, source_view, eve):
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def button_release_event(self, source_view, eve):
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def key_press_event(self, source_view, eve, key_mapper):
|
||||||
|
command = key_mapper._key_press_event(eve)
|
||||||
|
is_future = key_mapper._key_release_event(eve)
|
||||||
|
|
||||||
|
if is_future: return True
|
||||||
|
if not command: return False
|
||||||
|
|
||||||
|
source_view.command.exec(command)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def key_release_event(self, source_view, eve, key_mapper):
|
||||||
|
command = key_mapper._key_release_event(eve)
|
||||||
|
is_past = key_mapper._key_press_event(eve)
|
||||||
|
|
||||||
|
if is_past: return True
|
||||||
|
if not command: return False
|
||||||
|
|
||||||
|
source_view.command.exec(command)
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# 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 libs.dto.states import SourceViewStates, MoveDirection, CursorAction
|
||||||
|
|
||||||
|
from ....mixins.source_mark_events_mixin import MarkEventsMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewsMultiInsertState(MarkEventsMixin):
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceViewsMultiInsertState, self).__init__()
|
||||||
|
|
||||||
|
self.cursor_action: CursorAction = 0
|
||||||
|
self.move_direction: MoveDirection = 0
|
||||||
|
self.insert_markers: list = []
|
||||||
|
|
||||||
|
|
||||||
|
def focus_in_event(self, source_view, eve, emit):
|
||||||
|
source_view.command.exec("set_miniview")
|
||||||
|
source_view.command.exec("set_focus_border")
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
event = Event_Factory.create_event("focused_view", view = source_view)
|
||||||
|
emit(event)
|
||||||
|
|
||||||
|
def insert_text(self, file, text):
|
||||||
|
if not self.insert_markers: return False
|
||||||
|
|
||||||
|
buffer = file.buffer
|
||||||
|
|
||||||
|
# freeze buffer and insert to each mark (if any)
|
||||||
|
buffer.block_insert_after_signal()
|
||||||
|
buffer.begin_user_action()
|
||||||
|
|
||||||
|
with buffer.freeze_notify():
|
||||||
|
for mark in self.insert_markers:
|
||||||
|
itr = buffer.get_iter_at_mark(mark)
|
||||||
|
buffer.insert(itr, text, -1)
|
||||||
|
|
||||||
|
buffer.end_user_action()
|
||||||
|
buffer.unblock_insert_after_signal()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def move_cursor(self, source_view, step, count, extend_selection, emit):
|
||||||
|
buffer = source_view.get_buffer()
|
||||||
|
|
||||||
|
self._process_move_direction(buffer)
|
||||||
|
self._signal_cursor_moved(source_view, emit)
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def button_press_event(self, source_view, eve):
|
||||||
|
source_view.command.exec("update_info_bar")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def button_release_event(self, source_view, eve):
|
||||||
|
buffer = source_view.get_buffer()
|
||||||
|
insert_iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
|
data = source_view.window_to_buffer_coords(
|
||||||
|
Gtk.TextWindowType.TEXT,
|
||||||
|
eve.x,
|
||||||
|
eve.y
|
||||||
|
)
|
||||||
|
is_over_text, \
|
||||||
|
target_iter, \
|
||||||
|
is_trailing = source_view.get_iter_at_position(data.buffer_x, data.buffer_y)
|
||||||
|
|
||||||
|
if not is_over_text:
|
||||||
|
# NOTE: Trying to put at very end of line if not over text (aka, clicking right of text)
|
||||||
|
target_iter.forward_visible_line()
|
||||||
|
target_iter.backward_char()
|
||||||
|
|
||||||
|
self._insert_mark(insert_iter, target_iter, buffer)
|
||||||
|
|
||||||
|
def key_press_event(self, source_view, eve, key_mapper):
|
||||||
|
char = key_mapper.get_raw_keyname(eve)
|
||||||
|
|
||||||
|
for action in CursorAction:
|
||||||
|
if not action.name == char.upper(): continue
|
||||||
|
self.cursor_action = action.value
|
||||||
|
self._process_cursor_action(source_view.get_buffer())
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
for direction in MoveDirection:
|
||||||
|
if not direction.name == char.upper(): continue
|
||||||
|
self.move_direction = direction.value
|
||||||
|
return False
|
||||||
|
|
||||||
|
is_future = key_mapper._key_release_event(eve)
|
||||||
|
if is_future: return False
|
||||||
|
|
||||||
|
command = key_mapper._key_press_event(eve)
|
||||||
|
if not command: return False
|
||||||
|
|
||||||
|
source_view.command.exec(command)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def key_release_event(self, source_view, eve, key_mapper):
|
||||||
|
command = key_mapper._key_release_event(eve)
|
||||||
|
is_past = key_mapper._key_press_event(eve)
|
||||||
|
|
||||||
|
if is_past: return False
|
||||||
|
if not command: return False
|
||||||
|
|
||||||
|
source_view.command.exec(command)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _signal_cursor_moved(self, source_view, emit):
|
||||||
|
buffer = source_view.get_buffer()
|
||||||
|
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
|
line = itr.get_line()
|
||||||
|
char = itr.get_line_offset()
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"cursor_moved",
|
||||||
|
view = source_view,
|
||||||
|
buffer = buffer,
|
||||||
|
line = line,
|
||||||
|
char = char
|
||||||
|
)
|
||||||
|
|
||||||
|
emit(event)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
|
|
||||||
|
from libs.dto.states import SourceViewStates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewsReadOnlyState:
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceViewsReadOnlyState, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def focus_in_event(self, source_view, eve, emit):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def move_cursor(self, source_view, step, count, extend_selection, emit):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def insert_text(self, file, text):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def button_press_event(self, source_view, eve):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def button_release_event(self, source_view, eve):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def key_press_event(self, source_view, eve, key_mapper):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def key_release_event(self, source_view, eve, key_mapper):
|
||||||
|
return True
|
||||||
@@ -125,3 +125,13 @@ class KeyMapper:
|
|||||||
if is_alt:
|
if is_alt:
|
||||||
self.state = self.state | AltKeyState
|
self.state = self.state | AltKeyState
|
||||||
|
|
||||||
|
def is_control(self, eve):
|
||||||
|
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||||
|
return True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
|
||||||
|
|
||||||
|
def is_shift(self, eve):
|
||||||
|
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||||
|
return True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
|
||||||
|
|
||||||
|
def get_raw_keyname(self, eve):
|
||||||
|
return Gdk.keyval_name(eve.keyval)
|
||||||
|
|||||||
107
src/core/widgets/code/mixins/source_mark_events_mixin.py
Normal file
107
src/core/widgets/code/mixins/source_mark_events_mixin.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Python imports
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.states import SourceViewStates, MoveDirection, CursorAction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MarkEventsMixin:
|
||||||
|
def clear_markers(self, source_view):
|
||||||
|
buffer = source_view.get_buffer()
|
||||||
|
|
||||||
|
for mark in self.insert_markers:
|
||||||
|
mark.set_visible(False)
|
||||||
|
buffer.delete_mark(mark)
|
||||||
|
|
||||||
|
self.insert_markers.clear()
|
||||||
|
|
||||||
|
def _insert_mark(self, insert_iter, target_iter, buffer):
|
||||||
|
mark_found = self._check_for_insert_marks(target_iter, buffer)
|
||||||
|
|
||||||
|
if mark_found: return
|
||||||
|
|
||||||
|
random_bits = random.getrandbits(128)
|
||||||
|
hash = "%032x" % random_bits
|
||||||
|
|
||||||
|
mark = Gtk.TextMark.new(
|
||||||
|
name = f"multi_insert_{hash}",
|
||||||
|
left_gravity = False
|
||||||
|
)
|
||||||
|
|
||||||
|
buffer.add_mark(mark, target_iter)
|
||||||
|
mark.set_visible(True)
|
||||||
|
self.insert_markers.append(mark)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_for_insert_marks(self, target_iter, buffer):
|
||||||
|
marks = target_iter.get_marks()
|
||||||
|
|
||||||
|
for mark in marks:
|
||||||
|
if mark in self.insert_markers[:]:
|
||||||
|
mark.set_visible(False)
|
||||||
|
self.insert_markers.remove(mark)
|
||||||
|
buffer.delete_mark(mark)
|
||||||
|
return True
|
||||||
|
|
||||||
|
insert_itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
|
if target_iter.equal(insert_itr): return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _process_cursor_action(self, buffer):
|
||||||
|
if not self.insert_markers: return
|
||||||
|
if self.cursor_action == CursorAction.NONE.value: return
|
||||||
|
|
||||||
|
action = self.cursor_action
|
||||||
|
for mark in self.insert_markers:
|
||||||
|
itr = buffer.get_iter_at_mark(mark)
|
||||||
|
|
||||||
|
if action == CursorAction.BACKSPACE.value:
|
||||||
|
buffer.backspace(itr, interactive = True, default_editable = True)
|
||||||
|
elif action == CursorAction.DELETE.value:
|
||||||
|
itr.forward_char()
|
||||||
|
buffer.backspace(itr, interactive = True, default_editable = True)
|
||||||
|
elif action == CursorAction.ENTER.value:
|
||||||
|
...
|
||||||
|
|
||||||
|
self.cursor_action = CursorAction.NONE.value
|
||||||
|
|
||||||
|
def _process_move_direction(self, buffer):
|
||||||
|
if not self.insert_markers: return
|
||||||
|
if self.move_direction == MoveDirection.NONE.value: return
|
||||||
|
|
||||||
|
direction = self.move_direction
|
||||||
|
for mark in self.insert_markers:
|
||||||
|
itr = buffer.get_iter_at_mark(mark)
|
||||||
|
|
||||||
|
if direction == MoveDirection.UP.value:
|
||||||
|
new_line = itr.get_line() - 1
|
||||||
|
new_itr = buffer.get_iter_at_line_offset(
|
||||||
|
new_line,
|
||||||
|
itr.get_line_index()
|
||||||
|
)
|
||||||
|
|
||||||
|
elif direction == MoveDirection.DOWN.value:
|
||||||
|
new_line = itr.get_line() + 1
|
||||||
|
new_itr = buffer.get_iter_at_line_offset(
|
||||||
|
new_line,
|
||||||
|
itr.get_line_index()
|
||||||
|
)
|
||||||
|
elif direction == MoveDirection.LEFT.value:
|
||||||
|
if not itr.backward_char(): break
|
||||||
|
new_itr = itr
|
||||||
|
elif direction == MoveDirection.RIGHT.value:
|
||||||
|
if not itr.forward_char(): break
|
||||||
|
new_itr = itr
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
buffer.move_mark_by_name(mark.get_name(), new_itr)
|
||||||
|
|
||||||
|
self.move_direction = MoveDirection.NONE
|
||||||
@@ -21,6 +21,7 @@ class SourceBuffer(GtkSource.Buffer):
|
|||||||
_changed,
|
_changed,
|
||||||
_mark_set,
|
_mark_set,
|
||||||
_insert_text,
|
_insert_text,
|
||||||
|
_after_insert_text,
|
||||||
_modified_changed,
|
_modified_changed,
|
||||||
):
|
):
|
||||||
|
|
||||||
@@ -28,9 +29,28 @@ class SourceBuffer(GtkSource.Buffer):
|
|||||||
self.connect("changed", _changed),
|
self.connect("changed", _changed),
|
||||||
self.connect("mark-set", _mark_set),
|
self.connect("mark-set", _mark_set),
|
||||||
self.connect("insert-text", _insert_text),
|
self.connect("insert-text", _insert_text),
|
||||||
|
self.connect_after("insert-text", _after_insert_text),
|
||||||
self.connect("modified-changed", _modified_changed)
|
self.connect("modified-changed", _modified_changed)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def block_changed_signal(self):
|
||||||
|
self.handler_block(self._handler_ids[0])
|
||||||
|
|
||||||
|
def block_insert_after_signal(self):
|
||||||
|
self.handler_block(self._handler_ids[3])
|
||||||
|
|
||||||
|
def block_modified_changed_signal(self):
|
||||||
|
self.handler_block(self._handler_ids[4])
|
||||||
|
|
||||||
|
def unblock_changed_signal(self):
|
||||||
|
self.handler_unblock(self._handler_ids[0])
|
||||||
|
|
||||||
|
def unblock_insert_after_signal(self):
|
||||||
|
self.handler_unblock(self._handler_ids[3])
|
||||||
|
|
||||||
|
def unblock_modified_changed_signal(self):
|
||||||
|
self.handler_unblock(self._handler_ids[4])
|
||||||
|
|
||||||
def clear_signals(self):
|
def clear_signals(self):
|
||||||
for handle_id in self._handler_ids:
|
for handle_id in self._handler_ids:
|
||||||
self.disconnect(handle_id)
|
self.disconnect(handle_id)
|
||||||
@@ -38,3 +58,4 @@ class SourceBuffer(GtkSource.Buffer):
|
|||||||
def __del__(self):
|
def __del__(self):
|
||||||
for handle_id in self._handler_ids:
|
for handle_id in self._handler_ids:
|
||||||
self.disconnect(handle_id)
|
self.disconnect(handle_id)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
gi.require_version('GtkSource', '4')
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import GLib
|
||||||
from gi.repository import GtkSource
|
from gi.repository import GtkSource
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ class SourceFile(GtkSource.File):
|
|||||||
self._changed,
|
self._changed,
|
||||||
self._mark_set,
|
self._mark_set,
|
||||||
self._insert_text,
|
self._insert_text,
|
||||||
|
self._after_insert_text,
|
||||||
self._modified_changed
|
self._modified_changed
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,17 +47,38 @@ class SourceFile(GtkSource.File):
|
|||||||
event.file = self
|
event.file = self
|
||||||
self.emit(event)
|
self.emit(event)
|
||||||
|
|
||||||
def _insert_text(self, buffer: SourceBuffer, location: Gtk.TextIter,
|
def _insert_text(
|
||||||
|
self,
|
||||||
|
buffer: SourceBuffer,
|
||||||
|
location: Gtk.TextIter,
|
||||||
|
text: str, length: int
|
||||||
|
):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _after_insert_text(
|
||||||
|
self,
|
||||||
|
buffer: SourceBuffer,
|
||||||
|
location: Gtk.TextIter,
|
||||||
text: str, length: int
|
text: str, length: int
|
||||||
):
|
):
|
||||||
event = Event_Factory.create_event(
|
event = Event_Factory.create_event(
|
||||||
"text_inserted",
|
"text_inserted",
|
||||||
file = self,
|
file = self,
|
||||||
buffer = buffer
|
buffer = self.buffer,
|
||||||
|
location = location,
|
||||||
|
text = text,
|
||||||
|
length = length
|
||||||
)
|
)
|
||||||
self.emit(event)
|
|
||||||
|
|
||||||
def _mark_set(self, buffer: SourceBuffer, location: Gtk.TextIter,
|
# Note: 'idle_add' needed b/c markers don't get thir positions
|
||||||
|
# updated relative to the initial insert.
|
||||||
|
# If not used, seg faults galor during multi insert.
|
||||||
|
GLib.idle_add(self.emit, event)
|
||||||
|
|
||||||
|
def _mark_set(
|
||||||
|
self,
|
||||||
|
buffer: SourceBuffer,
|
||||||
|
location: Gtk.TextIter,
|
||||||
mark: Gtk.TextMark
|
mark: Gtk.TextMark
|
||||||
):
|
):
|
||||||
# event = Event_Factory.create_event(
|
# event = Event_Factory.create_event(
|
||||||
@@ -108,6 +131,12 @@ class SourceFile(GtkSource.File):
|
|||||||
self.emit(event)
|
self.emit(event)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"saved_file",
|
||||||
|
file = self, buffer = self.buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit(event)
|
||||||
self._write_file( self.get_location() )
|
self._write_file( self.get_location() )
|
||||||
|
|
||||||
def save_as(self):
|
def save_as(self):
|
||||||
|
|||||||
@@ -10,14 +10,18 @@ from gi.repository import GLib
|
|||||||
from gi.repository import GtkSource
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from libs.dto.states import SourceViewStates
|
||||||
|
|
||||||
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
|
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SourceView(GtkSource.View, SourceViewDnDMixin):
|
class SourceView(GtkSource.View, SourceViewDnDMixin):
|
||||||
def __init__(self):
|
def __init__(self, state: SourceViewStates = SourceViewStates.INSERT):
|
||||||
super(SourceView, self).__init__()
|
super(SourceView, self).__init__()
|
||||||
|
|
||||||
|
self.state = state
|
||||||
|
|
||||||
self._cut_temp_timeout_id = None
|
self._cut_temp_timeout_id = None
|
||||||
self._cut_buffer = ""
|
self._cut_buffer = ""
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class WebkitUI(WebKit2.WebView):
|
|||||||
data = f.read()
|
data = f.read()
|
||||||
|
|
||||||
self.load_html(content = data, base_uri = f"file://{path}/")
|
self.load_html(content = data, base_uri = f"file://{path}/")
|
||||||
|
# self.load_uri("https://duckduckgo.com/")
|
||||||
|
|
||||||
def _setup_content_manager(self):
|
def _setup_content_manager(self):
|
||||||
content_manager = self.get_user_content_manager()
|
content_manager = self.get_user_content_manager()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.singleton import Singleton
|
from ..singleton import Singleton
|
||||||
|
|
||||||
from ..dto.base_event import BaseEvent
|
from ..dto.base_event import BaseEvent
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ class ControllerBase(Singleton, EmitDispatcher):
|
|||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: BaseEvent):
|
||||||
raise ControllerBaseException("Controller Base must override '_controller_message'...")
|
raise ControllerBaseException("Controller Base '_controller_message' must be overridden...")
|
||||||
|
|
||||||
def set_controller_context(self, controller_context: ControllerContext):
|
def set_controller_context(self, controller_context: ControllerContext):
|
||||||
self.controller_context = controller_context
|
self.controller_context = controller_context
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
|
|
||||||
from .code_event import CodeEvent
|
from .code_event import CodeEvent
|
||||||
|
from .register_provider_event import RegisterProviderEvent
|
||||||
|
|
||||||
from .get_command_system_event import GetCommandSystemEvent
|
from .get_command_system_event import GetCommandSystemEvent
|
||||||
from .request_completion_event import RequestCompletionEvent
|
from .request_completion_event import RequestCompletionEvent
|
||||||
from .cursor_moved_event import CursorMovedEvent
|
from .cursor_moved_event import CursorMovedEvent
|
||||||
@@ -18,6 +20,7 @@ from .added_new_file_event import AddedNewFileEvent
|
|||||||
from .swapped_file_event import SwappedFileEvent
|
from .swapped_file_event import SwappedFileEvent
|
||||||
from .popped_file_event import PoppedFileEvent
|
from .popped_file_event import PoppedFileEvent
|
||||||
from .removed_file_event import RemovedFileEvent
|
from .removed_file_event import RemovedFileEvent
|
||||||
|
from .saved_file_event import SavedFileEvent
|
||||||
|
|
||||||
from .get_file_event import GetFileEvent
|
from .get_file_event import GetFileEvent
|
||||||
from .get_swap_file_event import GetSwapFileEvent
|
from .get_swap_file_event import GetSwapFileEvent
|
||||||
|
|||||||
19
src/libs/dto/code/register_provider_event.py
Normal file
19
src/libs/dto/code/register_provider_event.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 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 RegisterProviderEvent(BaseEvent):
|
||||||
|
provider_name: str = ""
|
||||||
|
provider: GtkSource.CompletionProvider = None
|
||||||
|
language_ids: list = field(default_factory=lambda: [])
|
||||||
13
src/libs/dto/code/saved_file_event.py
Normal file
13
src/libs/dto/code/saved_file_event.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Python imports
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .code_event import CodeEvent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SavedFileEvent(CodeEvent):
|
||||||
|
...
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .code_event import CodeEvent
|
from .code_event import CodeEvent
|
||||||
@@ -10,6 +15,6 @@ from .code_event import CodeEvent
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TextInsertedEvent(CodeEvent):
|
class TextInsertedEvent(CodeEvent):
|
||||||
line: int = 0
|
location: Gtk.TextIter = None
|
||||||
char: int = 0
|
text: str = ""
|
||||||
value: str = ""
|
length: int = 0
|
||||||
|
|||||||
7
src/libs/dto/states/__init__.py
Normal file
7
src/libs/dto/states/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Code DTO States Package
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .source_view_states import SourceViewStates
|
||||||
|
from .cursor_action import CursorAction
|
||||||
|
from .move_direction import MoveDirection
|
||||||
14
src/libs/dto/states/cursor_action.py
Normal file
14
src/libs/dto/states/cursor_action.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Python imports
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CursorAction(Enum):
|
||||||
|
NONE = 0
|
||||||
|
DELETE = 1
|
||||||
|
BACKSPACE = 2
|
||||||
|
ENTER = 3
|
||||||
15
src/libs/dto/states/move_direction.py
Normal file
15
src/libs/dto/states/move_direction.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Python imports
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MoveDirection(Enum):
|
||||||
|
NONE = 0
|
||||||
|
UP = 1
|
||||||
|
DOWN = 2
|
||||||
|
LEFT = 3
|
||||||
|
RIGHT = 4
|
||||||
14
src/libs/dto/states/source_view_states.py
Normal file
14
src/libs/dto/states/source_view_states.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Python imports
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewStates(Enum):
|
||||||
|
INSERT = 0
|
||||||
|
MULTIINSERT = 1
|
||||||
|
COMMAND = 2
|
||||||
|
READONLY = 3
|
||||||
@@ -39,4 +39,5 @@ class WebkitUISettings(WebKit2.Settings):
|
|||||||
self.set_enable_webaudio(True)
|
self.set_enable_webaudio(True)
|
||||||
self.set_enable_accelerated_2d_canvas(True)
|
self.set_enable_accelerated_2d_canvas(True)
|
||||||
|
|
||||||
self.set_user_agent(f"{APP_NAME}")
|
self.set_user_agent(f"Mozilla/5.0 (macOS, AArch64) {APP_NAME}/1.0 Chrome/140.0.0 AppleWebKit/537.36 Safari/537.36")
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
|
|||||||
for file in os.listdir(path):
|
for file in os.listdir(path):
|
||||||
_path = os.path.join(path, file)
|
_path = os.path.join(path, file)
|
||||||
if os.path.isdir(_path):
|
if os.path.isdir(_path):
|
||||||
self.collect_search_locations(_path, locations)
|
self._collect_search_locations(_path, locations)
|
||||||
|
|
||||||
def _load_plugins(
|
def _load_plugins(
|
||||||
self,
|
self,
|
||||||
@@ -70,7 +70,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
|
|||||||
try:
|
try:
|
||||||
target = join(path, "plugin.py")
|
target = join(path, "plugin.py")
|
||||||
if not os.path.exists(target):
|
if not os.path.exists(target):
|
||||||
raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
|
raise PluginsControllerException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
|
||||||
|
|
||||||
module = self._load_plugin_module(path, folder, target)
|
module = self._load_plugin_module(path, folder, target)
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,8 @@ class ManifestManager:
|
|||||||
|
|
||||||
for path, folder in [
|
for path, folder in [
|
||||||
[join(self._plugins_path, item), item]
|
[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)
|
for item in os.listdir(self._plugins_path)
|
||||||
|
if os.path.isdir( join(self._plugins_path, item) )
|
||||||
]:
|
]:
|
||||||
self.load(folder, path)
|
self.load(folder, path)
|
||||||
|
|
||||||
|
|||||||
7
src/plugins/plugin_types/__init__.py
Normal file
7
src/plugins/plugin_types/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Plugin Types Module
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .plugin_base import PluginBase
|
||||||
|
from .plugin_ui import PluginUI
|
||||||
|
from .plugin_code import PluginCode
|
||||||
49
src/plugins/plugin_types/plugin_base.py
Normal file
49
src/plugins/plugin_types/plugin_base.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.base_event import BaseEvent
|
||||||
|
|
||||||
|
from ..plugin_context import PluginContext
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PluginBaseException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PluginBase:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PluginBase, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.plugin_context: PluginContext = None
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: BaseEvent):
|
||||||
|
raise PluginBaseException("Plugin Base '_controller_message' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
raise PluginBaseException("Plugin Base 'load' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
raise PluginBaseException("Plugin Base 'run' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def requests_ui_element(self, element_id: str):
|
||||||
|
raise PluginBaseException("Plugin Base 'requests_ui_element' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def message(self, event: BaseEvent):
|
||||||
|
raise PluginBaseException("Plugin Base 'message' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def message_to(self, name: str, event: BaseEvent):
|
||||||
|
raise PluginBaseException("Plugin Base 'message_to' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def message_to_selected(self, names: list[str], event: BaseEvent):
|
||||||
|
raise PluginBaseException("Plugin Base 'message_to_selected' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def emit(self, event_type: str, data: tuple = ()):
|
||||||
|
raise PluginBaseException("Plugin Base 'emit' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def emit_and_await(self, event_type: str, data: tuple = ()):
|
||||||
|
raise PluginBaseException("Plugin Base 'emit_and_await' must be overriden by Plugin")
|
||||||
41
src/plugins/plugin_types/plugin_code.py
Normal file
41
src/plugins/plugin_types/plugin_code.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.base_event import BaseEvent
|
||||||
|
|
||||||
|
from ..plugin_context import PluginContext
|
||||||
|
from .plugin_base import PluginBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PluginCodeException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PluginCode(PluginBase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PluginCode, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.plugin_context: PluginContext = None
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: BaseEvent):
|
||||||
|
raise PluginCodeException("Plugin Code '_controller_message' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
raise PluginCodeException("Plugin Code 'load' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
raise PluginCodeException("Plugin Code 'run' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def message(self, event: BaseEvent):
|
||||||
|
return self.plugin_context.message(event)
|
||||||
|
|
||||||
|
def message_to(self, name: str, event: BaseEvent):
|
||||||
|
return self.plugin_context.message_to(name, event)
|
||||||
|
|
||||||
|
def message_to_selected(self, names: list[str], event: BaseEvent):
|
||||||
|
return self.plugin_context.message_to_selected(names, event)
|
||||||
@@ -5,30 +5,31 @@
|
|||||||
# Application imports
|
# Application imports
|
||||||
from libs.dto.base_event import BaseEvent
|
from libs.dto.base_event import BaseEvent
|
||||||
|
|
||||||
from .plugin_context import PluginContext
|
from ..plugin_context import PluginContext
|
||||||
|
from .plugin_base import PluginBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PluginBaseException(Exception):
|
class PluginCodeException(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PluginBase:
|
class PluginUI(PluginBase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(PluginBase, self).__init__(*args, **kwargs)
|
super(PluginUI, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.plugin_context: PluginContext = None
|
self.plugin_context: PluginContext = None
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: BaseEvent):
|
||||||
raise PluginBaseException("Plugin Base '_controller_message' must be overriden by Plugin")
|
raise PluginCodeException("Plugin UI '_controller_message' must be overriden by Plugin")
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
raise PluginBaseException("Plugin Base 'load' must be overriden by Plugin")
|
raise PluginCodeException("Plugin UI 'load' must be overriden by Plugin")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
raise PluginBaseException("Plugin Base 'run' must be overriden by Plugin")
|
raise PluginCodeException("Plugin UI 'run' must be overriden by Plugin")
|
||||||
|
|
||||||
def requests_ui_element(self, element_id: str):
|
def requests_ui_element(self, element_id: str):
|
||||||
return self.plugin_context.requests_ui_element(element_id)
|
return self.plugin_context.requests_ui_element(element_id)
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPluginException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PluginsControllerMixin:
|
class PluginsControllerMixin:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user