From 849e7611ca05f3135ee958495769f0e2dc8fbbe4 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 14 Dec 2025 02:44:05 -0600 Subject: [PATCH] Added code view widget and base command system; updated keybinding system --- src/core/containers/footer_container.py | 1 - src/core/widgets/code/__init__.py | 3 + src/core/widgets/code/command_system.py | 29 ++++ src/core/widgets/code/commands/__init__.py | 12 ++ src/core/widgets/code/commands/close_file.py | 23 ++++ src/core/widgets/code/commands/line_down.py | 19 +++ src/core/widgets/code/commands/line_up.py | 19 +++ src/core/widgets/code/commands/new_file.py | 26 ++++ src/core/widgets/code/commands/open_files.py | 36 +++++ .../widgets/code/commands/show_completion.py | 18 +++ src/core/widgets/code/completion_manager.py | 61 +++++++++ .../custom_completion_providers/__init__.py | 3 + .../example_completion_provider.py | 74 ++++++++++ .../lsp_completion_provider.py | 117 ++++++++++++++++ .../python_completion_provider.py | 108 +++++++++++++++ src/core/widgets/code/key_mapper.py | 127 ++++++++++++++++++ src/core/widgets/code/source_buffer.py | 40 ++++++ src/core/widgets/code/source_file.py | 77 +++++++++++ src/core/widgets/code/source_files_manager.py | 36 +++++ src/core/widgets/code/view.py | 115 ++++++++++++++++ src/core/window.py | 3 +- .../usr/share/app_name/code-key-bindings.json | 10 ++ .../code_styles/catppuccin-frappe.xml | 100 ++++++++++++++ .../app_name/code_styles/catppuccin-latte.xml | 100 ++++++++++++++ .../code_styles/catppuccin-macchiato.xml | 100 ++++++++++++++ .../app_name/code_styles/catppuccin-mocha.xml | 100 ++++++++++++++ .../code_styles/peacocks-in-space.xml | 123 +++++++++++++++++ .../code_styles/penguins-in-space.xml | 119 ++++++++++++++++ .../usr/share/app_name/key-bindings.json | 16 +-- 29 files changed, 1597 insertions(+), 18 deletions(-) create mode 100644 src/core/widgets/code/__init__.py create mode 100644 src/core/widgets/code/command_system.py create mode 100644 src/core/widgets/code/commands/__init__.py create mode 100644 src/core/widgets/code/commands/close_file.py create mode 100644 src/core/widgets/code/commands/line_down.py create mode 100644 src/core/widgets/code/commands/line_up.py create mode 100644 src/core/widgets/code/commands/new_file.py create mode 100644 src/core/widgets/code/commands/open_files.py create mode 100644 src/core/widgets/code/commands/show_completion.py create mode 100644 src/core/widgets/code/completion_manager.py create mode 100644 src/core/widgets/code/custom_completion_providers/__init__.py create mode 100644 src/core/widgets/code/custom_completion_providers/example_completion_provider.py create mode 100644 src/core/widgets/code/custom_completion_providers/lsp_completion_provider.py create mode 100644 src/core/widgets/code/custom_completion_providers/python_completion_provider.py create mode 100644 src/core/widgets/code/key_mapper.py create mode 100644 src/core/widgets/code/source_buffer.py create mode 100644 src/core/widgets/code/source_file.py create mode 100644 src/core/widgets/code/source_files_manager.py create mode 100644 src/core/widgets/code/view.py create mode 100644 user_config/usr/share/app_name/code-key-bindings.json create mode 100644 user_config/usr/share/app_name/code_styles/catppuccin-frappe.xml create mode 100644 user_config/usr/share/app_name/code_styles/catppuccin-latte.xml create mode 100644 user_config/usr/share/app_name/code_styles/catppuccin-macchiato.xml create mode 100644 user_config/usr/share/app_name/code_styles/catppuccin-mocha.xml create mode 100644 user_config/usr/share/app_name/code_styles/peacocks-in-space.xml create mode 100644 user_config/usr/share/app_name/code_styles/penguins-in-space.xml diff --git a/src/core/containers/footer_container.py b/src/core/containers/footer_container.py index 4e21cea..7ab1757 100644 --- a/src/core/containers/footer_container.py +++ b/src/core/containers/footer_container.py @@ -36,6 +36,5 @@ class FooterContainer(Gtk.Box): def _subscribe_to_events(self): ... - def _load_widgets(self): ... diff --git a/src/core/widgets/code/__init__.py b/src/core/widgets/code/__init__.py new file mode 100644 index 0000000..24809ae --- /dev/null +++ b/src/core/widgets/code/__init__.py @@ -0,0 +1,3 @@ +""" + Code Package +""" \ No newline at end of file diff --git a/src/core/widgets/code/command_system.py b/src/core/widgets/code/command_system.py new file mode 100644 index 0000000..ecbdb92 --- /dev/null +++ b/src/core/widgets/code/command_system.py @@ -0,0 +1,29 @@ +# Python imports + +# Lib imports + +# Application imports +from .commands import * + + + +class CommandSystem: + def __init__(self): + super(CommandSystem, self).__init__() + + self.data: list = () + + + def set_data(self, *args, **kwargs): + self.data = (args, kwargs) + + def exec(self, command: str): + if not command in globals(): return + + # method = getattr(self, command, None) + method = globals()[command] + args, kwargs = self.data + if kwargs: + method.execute(*args, kwargs) + else: + method.execute(*args) diff --git a/src/core/widgets/code/commands/__init__.py b/src/core/widgets/code/commands/__init__.py new file mode 100644 index 0000000..6044830 --- /dev/null +++ b/src/core/widgets/code/commands/__init__.py @@ -0,0 +1,12 @@ +""" + Commands Package +""" + +import os + + +__all__ = [ + command.replace(".py", "") for command in os.listdir( + os.path.dirname(__file__) + ) if "__init__" not in command +] \ No newline at end of file diff --git a/src/core/widgets/code/commands/close_file.py b/src/core/widgets/code/commands/close_file.py new file mode 100644 index 0000000..a175bfa --- /dev/null +++ b/src/core/widgets/code/commands/close_file.py @@ -0,0 +1,23 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None, + buffer: GtkSource.Buffer= None +): + logger.debug("Close File Command") + file = editor.files.new() + buffer = editor.get_buffer() + editor.set_buffer(file.buffer) + + editor.files.remove_file(buffer) diff --git a/src/core/widgets/code/commands/line_down.py b/src/core/widgets/code/commands/line_down.py new file mode 100644 index 0000000..362057a --- /dev/null +++ b/src/core/widgets/code/commands/line_down.py @@ -0,0 +1,19 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None, + buffer: GtkSource.Buffer= None +): + logger.debug("Line Up Command") + editor.emit("move-lines", True) diff --git a/src/core/widgets/code/commands/line_up.py b/src/core/widgets/code/commands/line_up.py new file mode 100644 index 0000000..a644702 --- /dev/null +++ b/src/core/widgets/code/commands/line_up.py @@ -0,0 +1,19 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None, + buffer: GtkSource.Buffer= None +): + logger.debug("Line Up Command") + editor.emit("move-lines", False) diff --git a/src/core/widgets/code/commands/new_file.py b/src/core/widgets/code/commands/new_file.py new file mode 100644 index 0000000..b74649b --- /dev/null +++ b/src/core/widgets/code/commands/new_file.py @@ -0,0 +1,26 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None, + buffer: GtkSource.Buffer= None +): + logger.debug("New File Command") + file = editor.files.new() + language = editor.language_manager \ + .guess_language("file.txt", None) + + file.buffer.set_language(language) + file.buffer.set_style_scheme(editor.syntax_theme) + + editor.set_buffer(file.buffer) diff --git a/src/core/widgets/code/commands/open_files.py b/src/core/widgets/code/commands/open_files.py new file mode 100644 index 0000000..991ce33 --- /dev/null +++ b/src/core/widgets/code/commands/open_files.py @@ -0,0 +1,36 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None, + buffer: GtkSource.Buffer= None +): + logger.debug("Open File(s) Command") + gfiles = event_system.emit_and_await("open_files") + if not gfiles: return + + size = len(gfiles) + for i, gfile in enumerate(gfiles): + file = editor.files.new() + file.set_path(gfile) + + language = editor.language_manager \ + .guess_language(file.fname, None) + file.ftype = language + file.buffer.set_language(language) + file.buffer.set_style_scheme(editor.syntax_theme) + + if i == (size - 1): + editor.set_buffer(file.buffer) + + diff --git a/src/core/widgets/code/commands/show_completion.py b/src/core/widgets/code/commands/show_completion.py new file mode 100644 index 0000000..be165cf --- /dev/null +++ b/src/core/widgets/code/commands/show_completion.py @@ -0,0 +1,18 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports + + + +def execute( + editor: GtkSource.View = None, + buffer: GtkSource.Buffer= None +): + logger.debug("Show Completion Command") diff --git a/src/core/widgets/code/completion_manager.py b/src/core/widgets/code/completion_manager.py new file mode 100644 index 0000000..1809542 --- /dev/null +++ b/src/core/widgets/code/completion_manager.py @@ -0,0 +1,61 @@ +# Python imports + +# Lib imports +import gi +from gi.repository import GLib + +# Application imports +from .custom_completion_providers.lsp_completion_provider import LSPCompletionProvider + + + +class CompletionManager(): + def __init__(self, completer): + super(CompletionManager, self).__init__() + + self._completor = completer + self._lsp_provider = LSPCompletionProvider() + self._timeout_id = None + + + def request_completion(self): + if self._timeout_id: + GLib.source_remove(self._timeout_id) + + self._timeout_id = GLib.timeout_add( + 1000, + self._process_request_completion + ) + + def _process_request_completion(self): + print('hello') + + self._timeout_id = None + return False + + def _do_completion(self): + if self._completor.get_providers(): + self._mach_completion() + else: + self._start_completion() + + def _mach_completion(self): + """ + Note: Use IF providers were added to completion... + """ + self._completion.match( + self._completion.create_context() + ) + + def _start_completion(self): + """ + Note: Use IF NO providers have been added to completion... + """ + self._completion.start( + [ + self._lsp_provider + ], + self._completion.create_context() + ) + + \ No newline at end of file diff --git a/src/core/widgets/code/custom_completion_providers/__init__.py b/src/core/widgets/code/custom_completion_providers/__init__.py new file mode 100644 index 0000000..fdff2e4 --- /dev/null +++ b/src/core/widgets/code/custom_completion_providers/__init__.py @@ -0,0 +1,3 @@ +""" + Custom Completion Providers Module +""" diff --git a/src/core/widgets/code/custom_completion_providers/example_completion_provider.py b/src/core/widgets/code/custom_completion_providers/example_completion_provider.py new file mode 100644 index 0000000..a363516 --- /dev/null +++ b/src/core/widgets/code/custom_completion_providers/example_completion_provider.py @@ -0,0 +1,74 @@ +# 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__ = 'CustomProvider' + + def __init__(self): + GObject.Object.__init__(self) + + def do_get_name(self): + """ Returns: a new string containing the name of the provider. """ + return _('ExampleProvider') + + 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_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) \ No newline at end of file diff --git a/src/core/widgets/code/custom_completion_providers/lsp_completion_provider.py b/src/core/widgets/code/custom_completion_providers/lsp_completion_provider.py new file mode 100644 index 0000000..03d57b0 --- /dev/null +++ b/src/core/widgets/code/custom_completion_providers/lsp_completion_provider.py @@ -0,0 +1,117 @@ +# 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 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): + return True + + def do_get_priority(self): + return 1 + + def do_get_activation(self): + return GtkSource.CompletionActivation.USER_REQUESTED + + + def do_populate(self, context, items = []): + self.lsp_data + + + + + + + + + + + + + + + + + # 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 \ No newline at end of file diff --git a/src/core/widgets/code/custom_completion_providers/python_completion_provider.py b/src/core/widgets/code/custom_completion_providers/python_completion_provider.py new file mode 100644 index 0000000..d8268fe --- /dev/null +++ b/src/core/widgets/code/custom_completion_providers/python_completion_provider.py @@ -0,0 +1,108 @@ +# 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: + try: + return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0) + except: + return None \ No newline at end of file diff --git a/src/core/widgets/code/key_mapper.py b/src/core/widgets/code/key_mapper.py new file mode 100644 index 0000000..d0ae4c1 --- /dev/null +++ b/src/core/widgets/code/key_mapper.py @@ -0,0 +1,127 @@ +# Python imports +import copy +import json + +# Lib imports +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk + +# Application imports + + + +class NoKeyState: + held: dict = {} + released: dict = {} + +class CtrlKeyState: + held: dict = {} + released: dict = {} + +class ShiftKeyState: + held: dict = {} + released: dict = {} + +class AltKeyState: + held: dict = {} + released: dict = {} + +class CtrlShiftKeyState: + held: dict = {} + released: dict = {} + +class CtrlAltKeyState: + held: dict = {} + released: dict = {} + +class AltShiftKeyState: + held: dict = {} + released: dict = {} + +class CtrlShiftAltKeyState: + held: dict = {} + released: dict = {} + + + +class KeyMapper: + def __init__(self): + super(KeyMapper, self).__init__() + + self.state = NoKeyState + self._map = { + NoKeyState: NoKeyState(), + NoKeyState | CtrlKeyState : CtrlKeyState(), + NoKeyState | ShiftKeyState: ShiftKeyState(), + NoKeyState | AltKeyState : AltKeyState(), + NoKeyState | CtrlKeyState | ShiftKeyState : CtrlShiftKeyState(), + NoKeyState | CtrlKeyState | AltKeyState : CtrlAltKeyState(), + NoKeyState | AltKeyState | ShiftKeyState : AltShiftKeyState(), + NoKeyState | CtrlKeyState | ShiftKeyState | AltKeyState: CtrlShiftAltKeyState(), + } + + self.load_map() + + + def load_map(self): + self.states = copy.deepcopy(self._map) + bindings_file = f"{settings_manager.get_home_config_path()}/code-key-bindings.json" + + 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 "" in keyname: + state = state | CtrlKeyState + if "" in keyname: + state = state | ShiftKeyState + if "" in keyname: + state = state | AltKeyState + + keyname = keyname.replace("", "") \ + .replace("", "") \ + .replace("", "") + + getattr(self.states[state], press_state)[keyname] = command + + def re_map(self): + self.states = copy.deepcopy(self._map) + + def _key_press_event(self, eve): + keyname = Gdk.keyval_name(eve.keyval) + print(keyname) + + self._set_key_state(eve) + if keyname in self.states[self.state].held: + return self.states[self.state].held[keyname] + + def _key_release_event(self, eve): + keyname = Gdk.keyval_name(eve.keyval) + + self._set_key_state(eve) + if keyname in self.states[self.state].released: + return self.states[self.state].released[keyname] + + def _set_key_state(self, eve): + modifiers = Gdk.ModifierType(eve.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 + + try: + is_alt = True if modifiers & Gdk.ModifierType.ALT_MASK else False + except Exception: + is_alt = True if modifiers & Gdk.ModifierType.MOD1_MASK else False + + self.state = NoKeyState + if is_control: + self.state = self.state | CtrlKeyState + if is_shift: + self.state = self.state | ShiftKeyState + if is_alt: + self.state = self.state | AltKeyState + diff --git a/src/core/widgets/code/source_buffer.py b/src/core/widgets/code/source_buffer.py new file mode 100644 index 0000000..8c70296 --- /dev/null +++ b/src/core/widgets/code/source_buffer.py @@ -0,0 +1,40 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('GtkSource', '4') +from gi.repository import GtkSource + +# Application imports + + + +class SourceBuffer(GtkSource.Buffer): + def __init__(self): + super(SourceBuffer, self).__init__() + + self._handler_ids = [] + + + def set_signals( + self, + _changed, + _mark_set, + _insert_text, + _modified_changed, + ): + + self._handler_ids = [ + self.connect("changed", _changed), + self.connect("mark-set", _mark_set), + self.connect("insert-text", _insert_text), + self.connect("modified-changed", _modified_changed) + ] + + def clear_signals(self): + for handle_id in self._handler_ids: + self.disconnect(handle_id) + + def __del__(self): + for handle_id in self._handler_ids: + self.disconnect(handle_id) \ No newline at end of file diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py new file mode 100644 index 0000000..f6496ed --- /dev/null +++ b/src/core/widgets/code/source_file.py @@ -0,0 +1,77 @@ +# Python imports +import os + +# 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 Gio + +# Application imports +from .source_buffer import SourceBuffer + + + +class SourceFile(GtkSource.File): + def __init__(self): + super(SourceFile, self).__init__() + + self.encoding: str = "UTF-8" + self.fname: str = "buffer" + self.fpath: str = "buffer" + self.ftype: str = "buffer" + + self.buffer: SourceBuffer = SourceBuffer() + + self._set_signals() + + + def set_path(self, gfile: Gio.File.new_for_path): + if not gfile: return + + self.set_location(gfile) + + self.fpath = gfile.get_parent().get_path(), + self.fname = gfile.get_basename() + data = gfile.load_bytes()[0].get_data().decode("UTF-8") + + self.buffer.insert_at_cursor(data) + + def _set_signals(self): + self.buffer.set_signals( + self._changed, + self._mark_set, + self._insert_text, + self._modified_changed + ) + + def _insert_text( + self, + buffer: SourceBuffer, + location: Gtk.TextIter, + text: str, + length: int + ): + logger.info("SourceFile._insert_text") + + def _changed(self, buffer: SourceBuffer): + logger.info("SourceFile._changed") + + def _mark_set( + self, + buffer: SourceBuffer, + location: Gtk.TextIter, + mark: Gtk.TextMark + ): + # logger.info("SourceFile._mark_set") + ... + + def _modified_changed(self,buffer: SourceBuffer): + logger.info("SourceFile._modified_changed") + + def close(self): + del self.buffer \ No newline at end of file diff --git a/src/core/widgets/code/source_files_manager.py b/src/core/widgets/code/source_files_manager.py new file mode 100644 index 0000000..68b08a4 --- /dev/null +++ b/src/core/widgets/code/source_files_manager.py @@ -0,0 +1,36 @@ +# Python imports + +# Lib imports + +# Application imports +from .source_file import SourceFile +from .source_buffer import SourceBuffer + + + +class SourceFilesManager(list): + def __init__(self): + super(SourceFilesManager, self).__init__() + + + def new(self): + file = SourceFile() + super().append(file) + + return file + + def append(self, file: SourceFile): + if not file: return + + super().append(file) + + def remove_file(self, buffer: SourceBuffer): + if not buffer: return + + for file in self: + if not buffer == file.buffer: continue + self.remove(file) + + file.close() + del file + break diff --git a/src/core/widgets/code/view.py b/src/core/widgets/code/view.py new file mode 100644 index 0000000..c4509ec --- /dev/null +++ b/src/core/widgets/code/view.py @@ -0,0 +1,115 @@ +# 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 GLib +from gi.repository import GtkSource + +# Application imports +from .source_files_manager import SourceFilesManager +from .completion_manager import CompletionManager +from .command_system import CommandSystem +from .key_mapper import KeyMapper + + +class SourceView(GtkSource.View): + def __init__(self): + super(SourceView, self).__init__() + + self.key_mapper = KeyMapper() + + self._setup_styles() + self._setup_signals() + self._subscribe_to_events() + self._load_widgets() + + + def _setup_styles(self): + ctx = self.get_style_context() + ctx.add_class("source-view") + + self.set_vexpand(True) + + self.set_show_line_marks(True) + self.set_show_line_numbers(True) + self.set_smart_backspace(True) + self.set_indent_on_tab(True) + self.set_insert_spaces_instead_of_tabs(True) + self.set_auto_indent(True) + self.set_monospace(True) + self.set_tab_width(4) + self.set_show_right_margin(True) + self.set_right_margin_position(80) + self.set_background_pattern(0) # 0 = None, 1 = Grid + self.set_highlight_current_line(True) + + def _setup_signals(self): + # self.connect("show-completion", self._show_completion) + self.map_id = self.connect("map", self._init_map) + + # self.connect("focus", self._on_widget_focus) + # self.connect("focus-in-event", self._focus_in_event) + + # self.connect("drag-data-received", self._on_drag_data_received) + self.connect("key-press-event", self._key_press_event) + self.connect("key-release-event", self._key_release_event) + # self.connect("button-press-event", self._button_press_event) + # self.connect("button-release-event", self._button_release_event) + # self.connect("scroll-event", self._scroll_event) + + def _subscribe_to_events(self): + ... + + def _load_widgets(self): + ... + + def _init_map(self, view): + def _first_show_init(): + self.disconnect(self.map_id) + del self.map_id + + self._handle_first_show() + + return False + + # GLib.timeout_add(1000, _first_show_init) + GLib.idle_add(_first_show_init) + + def _handle_first_show(self): + self.language_manager = GtkSource.LanguageManager() + self.style_scheme_manager = GtkSource.StyleSchemeManager() + self.command = CommandSystem() + self.files = SourceFilesManager() + self.completion = CompletionManager( + self.get_completion() + ) + + self.style_scheme_manager.append_search_path(f"{settings_manager.get_home_config_path()}/code_styles") + self.syntax_theme = self.style_scheme_manager.get_scheme("penguins-in-space") + + self.command.set_data(self, self.get_buffer()) + self.exec_command("new_file") + + def _key_press_event(self, view, eve): + command = self.key_mapper._key_press_event(eve) + if not command: return False + + self.exec_command(command) + return True + + def _key_release_event(self, view, eve): + command = self.key_mapper._key_release_event(eve) + if not command: return False + + self.exec_command(command) + return True + + def _show_completion(self, view): + self.completion.request_completion() + + def exec_command(self, command: str): + self.command.exec(command) diff --git a/src/core/window.py b/src/core/window.py index 8199215..9b32edc 100644 --- a/src/core/window.py +++ b/src/core/window.py @@ -118,9 +118,8 @@ class Window(Gtk.ApplicationWindow): # bind css file cssProvider = Gtk.CssProvider() - cssProvider.load_from_path( settings_manager.get_css_file() ) - screen = Gdk.Screen.get_default() styleContext = Gtk.StyleContext() + cssProvider.load_from_path( settings_manager.get_css_file() ) styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None: diff --git a/user_config/usr/share/app_name/code-key-bindings.json b/user_config/usr/share/app_name/code-key-bindings.json new file mode 100644 index 0000000..927bc37 --- /dev/null +++ b/user_config/usr/share/app_name/code-key-bindings.json @@ -0,0 +1,10 @@ +{ + "keybindings": { + "show_completion" : { "released" : "space" }, + "line_up" : { "held" : "Up" }, + "line_down" : { "held" : "Down" }, + "new_file" : { "released" : "t" }, + "open_files" : { "released" : "o" }, + "close_file" : { "released" : "w" } + } +} \ No newline at end of file diff --git a/user_config/usr/share/app_name/code_styles/catppuccin-frappe.xml b/user_config/usr/share/app_name/code_styles/catppuccin-frappe.xml new file mode 100644 index 0000000..76b043e --- /dev/null +++ b/user_config/usr/share/app_name/code_styles/catppuccin-frappe.xml @@ -0,0 +1,100 @@ + + + + + + sacerdos + <_description>Soothing pastel theme for Gedit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +