Refactored mixins; linked most buttons; added more keybindings

This commit is contained in:
itdominator 2024-02-15 21:55:18 -06:00
parent f17e92d148
commit d98982bc86
39 changed files with 155 additions and 1751 deletions

View File

@ -6,7 +6,6 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.base.notebook.editor_notebook import EditorNotebook
from ..widgets.base.webkit.editor import Editor from ..widgets.base.webkit.editor import Editor
@ -34,10 +33,7 @@ class EditorsPaned(Gtk.Paned):
def _load_widgets(self): def _load_widgets(self):
self.add1( Editor() ) self.add1( Editor() )
self.add2( Editor() ) # self.add2( Editor() )
# self.add1( EditorNotebook() )
# self.add2( EditorNotebook() )
def _update_paned_handle(self): def _update_paned_handle(self):
rect = self.get_allocation() rect = self.get_allocation()
@ -49,4 +45,4 @@ class EditorsPaned(Gtk.Paned):
except: except:
... ...
self.set_position(size) self.set_position(size)

View File

@ -10,17 +10,16 @@ from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
# Application imports # Application imports
from ..mixins.signals_mixins import SignalsMixins from libs.mixins.keyboard_signals_mixin import KeyboardSignalsMixin
from ..containers.base_container import BaseContainer from ..containers.base_container import BaseContainer
# from ..containers.core_widget import CoreWidget
from .base_controller_data import BaseControllerData from .base_controller_data import BaseControllerData
from .bridge_controller import BridgeController from .bridge_controller import BridgeController
class BaseController(SignalsMixins, BaseControllerData): class BaseController(KeyboardSignalsMixin, BaseControllerData):
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
messages = [] messages = []
for arg in unknownargs + [args.new_tab,]: for arg in unknownargs + [args.new_tab,]:

View File

@ -11,6 +11,7 @@ class BridgeController:
def __init__(self): def __init__(self):
self.opened_files = {} self.opened_files = {}
self.originator = -1
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
@ -21,9 +22,27 @@ class BridgeController:
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("handle_bridge_event", self.handle_bridge_event) event_system.subscribe("handle_bridge_event", self.handle_bridge_event)
event_system.subscribe(f"keyboard_open_file", self.keyboard_open_file)
event_system.subscribe(f"keyboard_scale_up_text", self.keyboard_scale_up_text)
event_system.subscribe(f"keyboard_scale_down_text", self.keyboard_scale_down_text)
event_system.subscribe(f"toggle_highlight_line", self.toggle_highlight_line)
def keyboard_open_file(self, gfiles):
event_system.emit(f"set_pre_drop_dnd_{self.originator}", (gfiles,))
def keyboard_scale_up_text(self):
event_system.emit(f"keyboard_scale_up_text_{self.originator}")
def keyboard_scale_down_text(self):
event_system.emit(f"keyboard_scale_down_text_{self.originator}")
def toggle_highlight_line(self):
event_system.emit(f"toggle_highlight_line_{self.originator}")
def handle_bridge_event(self, event): def handle_bridge_event(self, event):
self.originator = event.originator
match event.topic: match event.topic:
case "save": case "save":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle_file_event_{event.originator}", (event,))
@ -35,6 +54,15 @@ class BridgeController:
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle_file_event_{event.originator}", (event,))
case "open_file": case "open_file":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle_file_event_{event.originator}", (event,))
case "tggl_top_main_menubar":
event_system.emit("tggl_top_main_menubar")
case "set_info_labels":
content = base64.b64decode( event.content.encode() ).decode("utf-8")
path = event.fpath
line_char = content
file_type = event.ftype
encoding_type = "utf-8"
event_system.emit(f"set_info_labels", (path, line_char, file_type, encoding_type,))
case "error": case "error":
content = base64.b64decode( event.content.encode() ).decode("utf-8") content = base64.b64decode( event.content.encode() ).decode("utf-8")
logger.info(content) logger.info(content)

View File

@ -1,3 +0,0 @@
"""
Mixins module
"""

View File

@ -1,3 +0,0 @@
"""
Signals module
"""

View File

@ -1,12 +0,0 @@
# Python imports
# Lib imports
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
# Application imports
class SignalsMixins(KeyboardSignalsMixin):
...

View File

@ -35,7 +35,7 @@ class GeneralInfoWidget:
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("set_bottom_labels", self.set_bottom_labels) event_system.subscribe("set_info_labels", self.set_info_labels)
event_system.subscribe("set_path_label", self._set_path_label) event_system.subscribe("set_path_label", self._set_path_label)
event_system.subscribe("set_encoding_label", self._set_encoding_label) event_system.subscribe("set_encoding_label", self._set_encoding_label)
event_system.subscribe("set_line_char_label", self._set_line_char_label) event_system.subscribe("set_line_char_label", self._set_line_char_label)
@ -63,7 +63,7 @@ class GeneralInfoWidget:
builder.get_object("core_widget").add(self.bottom_status_info) builder.get_object("core_widget").add(self.bottom_status_info)
def set_bottom_labels(self, path = None, line_char = None, file_type = None, encoding_type = None): def set_info_labels(self, path = None, line_char = None, file_type = None, encoding_type = None):
self._set_path_label(path) self._set_path_label(path)
self._set_line_char_label(line_char) self._set_line_char_label(line_char)
self._set_file_type_label(file_type) self._set_file_type_label(file_type)

View File

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

View File

@ -1,33 +0,0 @@
# Python imports
# Lib imports
# Application imports
from .key_input_controller import KeyInputController
from .editor_events import EditorEventsMixin
class EditorControllerMixin(KeyInputController, EditorEventsMixin):
def get_active_view(self):
page_num = self.get_current_page()
container = self.get_nth_page( page_num )
source_view = container.get_source_view()
return page_num, container, source_view
def action_controller(self, action = "", query = ""):
# NOTE: Not efficent here as multiple same calls
if not self.is_editor_focused: # TODO: Find way to converge this
return
page_num, container, source_view = self.get_active_view()
# NOTE: These feel bad being here man...
if action == "scale_up_text":
self.scale_up_text(source_view)
if action == "scale_down_text":
self.scale_down_text(source_view)
if action == "set_buffer_language":
self.set_buffer_language(source_view, query)
if action == "set_buffer_style":
self.set_buffer_style(source_view, query)

View File

@ -1,160 +0,0 @@
# Python imports
# Lib imports
# Application imports
from ..sourceview_container import SourceViewContainer
class EditorEventsMixin:
def create_view(self, widget = None, eve = None, gfile = None, line: int = 0):
container = SourceViewContainer(self.close_tab)
page_num = self.append_page(container, container.get_tab_widget())
self.set_tab_detachable(container, True)
self.set_tab_reorderable(container, True)
self.show_all()
self.set_current_page(page_num)
if gfile:
source_view = container.get_source_view()
source_view.open_file(gfile, line)
source_view.grab_focus()
def open_file(self, gfile):
page_num = self.get_current_page()
container = self.get_nth_page( page_num )
source_view = container.get_source_view()
if source_view._current_filename == "":
source_view.open_file(gfile)
else:
self.create_view(None, None, gfile)
# Note: Need to get parent instead given we pass the close_tab method
# from a potentially former notebook.
def close_tab(self, button, container, source_view, eve = None):
notebook = container.get_parent()
if notebook.NAME == "notebook_1" and notebook.get_n_pages() == 1:
return
file_type = source_view.get_filetype()
if not file_type == "buffer":
uri = source_view.get_current_file().get_uri()
event_system.emit("textDocument/didClose", (file_type, uri,))
page_num = notebook.page_num(container)
source_view._cancel_current_file_watchers()
notebook.remove_page(page_num)
if notebook.NAME == "notebook_2" and notebook.get_n_pages() == 0:
notebook.hide()
event_system.emit("focused_target_changed", ("notebook_1",))
def keyboard_prev_tab(self, page_num):
page_num = self.get_n_pages() - 1 if page_num == 0 else page_num - 1
self.set_current_page(page_num)
def keyboard_next_tab(self, page_num):
page_num = 0 if self.get_n_pages() - 1 == page_num else page_num + 1
self.set_current_page(page_num)
def keyboard_focus_1st_pane(self):
if self.NAME == "notebook_1":
return
notebook = self.builder.get_object("notebook_1")
i = notebook.get_current_page()
page = notebook.get_nth_page(i)
self.set_page_focus(page, notebook, self)
def keyboard_focus_2nd_pane(self):
if self.NAME == "notebook_2":
return
notebook = self.builder.get_object("notebook_2")
if not notebook.is_visible():
notebook.show()
notebook.create_view()
event_system.emit("update_paned_handle")
i = notebook.get_current_page()
page = notebook.get_nth_page(i)
self.set_page_focus(page, notebook, self)
def keyboard_move_tab_to_1(self, page_num):
if self.NAME == "notebook_1": return
notebook = self.builder.get_object("notebook_1")
page = self.get_nth_page(page_num)
tab = page.get_tab_widget()
self.detach_tab(page)
notebook.show()
notebook.insert_page(page, tab, -1)
if self.get_n_pages() == 0:
self.hide()
self.set_page_focus(page, notebook, self)
def keyboard_move_tab_to_2(self, page_num):
if self.NAME == "notebook_2":
return
if self.NAME == "notebook_1" and self.get_n_pages() == 1:
return
notebook = self.builder.get_object("notebook_2")
page = self.get_nth_page(page_num)
tab = page.get_tab_widget()
self.detach_tab(page)
if not notebook.is_visible():
notebook.show()
event_system.emit("update_paned_handle")
notebook.insert_page(page, tab, -1)
self.set_page_focus(page, notebook, self)
def set_page_focus(self, page, notebook, old_notebook):
old_notebook.is_editor_focused = False
notebook.set_current_page(-1)
page.get_children()[0].grab_focus()
notebook.is_editor_focused = True
ctx = old_notebook.get_style_context()
ctx.remove_class("notebook-selected-focus")
ctx = notebook.get_style_context()
ctx.add_class("notebook-selected-focus")
def keyboard_move_tab_left(self, page_num):
page = self.get_nth_page(page_num)
page_num = self.get_n_pages() - 1 if page_num == 0 else page_num - 1
self.reorder_child(page, page_num)
def keyboard_move_tab_right(self, page_num):
page = self.get_nth_page(page_num)
page_num = 0 if self.get_n_pages() - 1 == page_num else page_num + 1
self.reorder_child(page, page_num)
# NOTE: These feel bad being here man...
def scale_up_text(self, source_view):
source_view.scale_up_text()
def scale_down_text(self, source_view):
source_view.scale_down_text()
def set_buffer_language(self, source_view, language = "python3"):
source_view.set_buffer_language(language)
def set_buffer_style(self, source_view, style = settings.theming.syntax_theme):
buffer = source_view.get_buffer()
source_view.set_buffer_style(buffer, style)

View File

@ -1,142 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
# Application imports
from .editor_controller import EditorControllerMixin
# NOTE: https://github.com/Axel-Erfurt/TextEdit/tree/b65f09be945196eb05bef83d81a6abcd129b4eb0
class EditorNotebook(EditorControllerMixin, Gtk.Notebook):
ccount = 0
def __new__(cls, *args, **kwargs):
obj = super(EditorNotebook, cls).__new__(cls)
cls.ccount += 1
return obj
def __init__(self):
super(EditorNotebook, self).__init__()
self.NAME = f"notebook_{self.ccount}"
self.builder = settings_manager.get_builder()
self.is_editor_focused = False
self.set_group_name("editor_widget")
self.builder.expose_object(self.NAME, self)
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
if self.NAME == "notebook_1":
self.is_editor_focused = True
if self.NAME == "notebook_2":
self.hide()
def _setup_styling(self):
self.set_scrollable(True)
self.set_vexpand(True)
self.set_hexpand(True)
def _setup_signals(self):
self.connect("switch-page", self._switch_page_update)
self.connect("key-press-event", self._key_press_event)
self.connect("key-release-event", self._key_release_event)
def _subscribe_to_events(self):
event_system.subscribe("create_view", self._create_view)
event_system.subscribe("set_buffer_style", self.action_controller)
event_system.subscribe("set_buffer_language", self.action_controller)
event_system.subscribe("focused_target_changed", self._focused_target_changed)
event_system.subscribe("keyboard_open_file", self._keyboard_open_file)
event_system.subscribe("keyboard_scale_up_text", self._keyboard_scale_up_text)
event_system.subscribe("keyboard_scale_down_text", self._keyboard_scale_down_text)
event_system.subscribe("keyboard_focus_1st_pane", self.keyboard_focus_1st_pane)
event_system.subscribe("keyboard_focus_2nd_pane", self.keyboard_focus_2nd_pane)
def _load_widgets(self):
self._add_action_widgets()
if self.NAME == "notebook_1" and not settings_manager.is_starting_with_file():
self.create_view()
def _dbl_click_create_view(self, notebook, eve):
if eve.type == Gdk.EventType.DOUBLE_BUTTON_PRESS and eve.button == 1: # l-click
...
def _focused_target_changed(self, target):
self.is_editor_focused = True if target == self.NAME else False
if self.is_editor_focused:
self.grab_focus()
def _add_action_widgets(self):
start_box = Gtk.Box()
end_box = Gtk.Box()
add_btn = Gtk.Button()
add_btn.set_image( Gtk.Image.new_from_icon_name("add", 4) )
add_btn.set_always_show_image(True)
add_btn.connect("released", self.create_view)
end_box.add(add_btn)
start_box.show_all()
end_box.show_all()
# PACKTYPE: 0 Start, 1 = End
self.set_action_widget(start_box, 0)
self.set_action_widget(end_box, 1)
def _switch_page_update(self, notebook, page, page_num):
source_view = page.get_source_view()
gfile = source_view.get_current_file()
if not gfile:
event_system.emit("set_path_label", ("",))
event_system.emit("set_file_type_label", (source_view._current_filetype,))
else:
source_view.load_file_info(gfile)
source_view.update_cursor_position()
source_view.set_bottom_labels(gfile)
event_system.emit(f"set_source_view", (source_view,))
def _create_view(self, gfile = None, line: int = 0):
if not self.is_editor_focused: # TODO: Find way to converge this
return
if isinstance(gfile, str):
parts = gfile.split(":")
gfile = Gio.File.new_for_path(parts[0])
try:
line = int(parts[1]) if len(parts) > 1 else 0
except Exception:
...
self.create_view(None, None, gfile, line)
def _keyboard_open_file(self, gfile):
if not self.is_editor_focused: # TODO: Find way to converge this
return
self.open_file(gfile)
def _keyboard_scale_up_text(self):
self.action_controller("scale_up_text")
def _keyboard_scale_down_text(self):
self.action_controller("scale_down_text")

View File

@ -1,69 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
# Application imports
class KeyInputController:
def _key_press_event(self, widget, eve):
keyname = Gdk.keyval_name(eve.keyval)
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
def _key_release_event(self, widget, eve):
keyname = Gdk.keyval_name(eve.keyval)
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
page_num, container, source_view = self.get_active_view()
if is_control:
if is_shift:
if keyname in ["Up", "Down"]:
if keyname == "Up":
self.keyboard_move_tab_to_1(page_num)
if keyname == "Down":
self.keyboard_move_tab_to_2(page_num)
return True
if keyname in ["w", "t", "o"]:
if keyname == "w":
self.close_tab(None, container, source_view)
if keyname == "t":
self._create_view()
if keyname == "o":
page_num, container, source_view = self.get_active_view()
event_system.emit("open_files", (source_view,))
return True
if is_alt:
if keyname in ["Up", "Down", "Left", "Right"]:
if keyname == "Up":
self.keyboard_prev_tab(page_num)
if keyname == "Down":
self.keyboard_next_tab(page_num)
if keyname == "Left":
self.keyboard_move_tab_left(page_num)
if keyname == "Right":
self.keyboard_move_tab_right(page_num)
return True

View File

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

View File

@ -1,3 +0,0 @@
"""
Custom Completion Providers Module
"""

View File

@ -1,74 +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__ = '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)

View File

@ -1,91 +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, source_view):
GObject.Object.__init__(self)
self._theme = Gtk.IconTheme.get_default()
self._source_view = source_view
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)
buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
return False
event_system.emit("textDocument/completion", (self._source_view, context, self.do_populate))
return True
def do_get_priority(self):
return 1
def do_get_activation(self):
return GtkSource.CompletionActivation.INTERACTIVE
def do_populate(self, context, result = None):
proposals = []
if result:
if not result.items is None:
for item in result.items:
proposals.append( self.create_completion_item(item) )
else:
proposals.append( self.create_completion_item(result) )
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()
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

View File

@ -1,108 +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:
try:
return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
except:
return None

View File

@ -1,145 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
from gi.repository import GLib
# Application imports
class KeyInputController:
# NOTE: Mostly sinking pre-bound keys here to let our keybinder control instead...
def _key_press_event(self, widget, eve):
keyname = Gdk.keyval_name(eve.keyval)
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
buffer = self.get_buffer()
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
if is_control:
if is_shift:
if keyname in [ "Up", "Down" ]:
return True
if keyname in [ "slash", "Up", "Down", "m", "z", "y" ]:
return True
if is_alt:
if keyname in [ "Up", "Down", "Left", "Right" ]:
return True
if len(self._multi_insert_marks) > 0:
if keyname == "BackSpace":
self.begin_user_action(buffer)
with buffer.freeze_notify():
GLib.idle_add(self._delete_on_multi_line_markers, *(buffer,))
self.end_user_action(buffer)
return True
# NOTE: if a plugin recieves the call and handles, it will be the final decider for propigation
return event_system.emit_and_await("autopairs", (keyname, is_control, is_alt, is_shift))
def _key_release_event(self, widget, eve):
if self.freeze_multi_line_insert: return
keyname = Gdk.keyval_name(eve.keyval)
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
buffer = self.get_buffer()
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
can_continue = self.can_proceed(keyname, is_control, is_shift, is_alt)
if not can_continue:
return can_continue
if is_control:
if is_shift:
if keyname in ["S"]:
if keyname == "S":
self.save_file_as()
return True
if keyname in ["z", "y", "m", "s", "h", "g", "equal", "minus", "Up", "Down"]:
if keyname == "z":
self.keyboard_undo()
if keyname == "y":
self.keyboard_redo()
if keyname == "m":
self.keyboard_insert_mark()
if keyname == "s":
self.save_file()
if keyname == "h":
self.toggle_highlight_line()
if keyname == "g":
self.go_to_call()
if keyname == "equal":
self.scale_up_text()
if keyname == "minus":
self.scale_down_text()
if keyname == "Up":
self.keyboard_move_lines_up()
if keyname == "Down":
self.keyboard_move_lines_down()
return True
# Note: Sink these requets
if keyname in ["Slash"]:
return True
if is_alt:
if keyname == "m":
self.keyboard_clear_marks()
if keyname in {"Return", "Enter"}:
if len(self._multi_insert_marks) > 0:
self.begin_user_action(buffer)
with buffer.freeze_notify():
GLib.idle_add(self._new_line_on_multi_line_markers, *(buffer,))
return
has_selection = buffer.get_has_selection()
if not has_selection:
return self.insert_indent_handler(buffer)
def can_proceed(self, keyname, is_control, is_shift, is_alt):
if is_control:
if is_shift:
if keyname in ["Up", "Down"]:
return False
if keyname in ["w", "t", "o"]:
return False
if is_alt:
if keyname in ["Up", "Down", "Left", "Right"]:
return False
return True

View File

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

View File

@ -1,157 +0,0 @@
# Python imports
import random
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GtkSource
# Application imports
from ..custom_completion_providers.lsp_completion_provider import LSPCompletionProvider
class FileEventsMixin:
def get_current_file(self):
return self._current_file
def open_file(self, gfile, line: int = 0, *args):
self._current_file = gfile
self._loading_file = True
self.load_file_info(gfile)
self.load_file_async(gfile, line)
self._create_file_watcher(gfile)
def save_file(self):
self._skip_file_load = True
gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) if not self._current_file else self._current_file
if not gfile:
self._skip_file_load = False
return
self.open_file( self._write_file(gfile) )
self._skip_file_load = False
def save_file_as(self):
gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file))
self._write_file(gfile, True)
if gfile: event_system.emit("create_view", (gfile,))
def load_file_info(self, gfile):
info = gfile.query_info("standard::*", 0, cancellable=None)
content_type = info.get_content_type()
display_name = info.get_display_name()
self._current_filename = display_name
try:
lm = self._language_manager.guess_language(None, content_type)
self._current_filetype = lm.get_id()
self.set_buffer_language(self._current_filetype)
except Exception as e:
...
logger.debug(f"Detected Content Type: {content_type}")
if self._current_filetype == "buffer":
self._current_filetype = info.get_content_type()
def load_file_async(self, gfile, line: int = 0):
if self._skip_file_load:
self.update_labels(gfile)
return
file = GtkSource.File()
buffer = self.get_buffer()
file.set_location(gfile)
self._file_loader = GtkSource.FileLoader.new(buffer, file)
event_system.emit("pause_event_processing")
def finish_load_callback(obj, res, user_data = None):
event_system.emit("resume_event_processing")
self._file_loader.load_finish(res)
self._document_loaded(line)
self.update_labels(gfile)
self._loading_file = False
self._file_loader.load_async(io_priority = 80,
cancellable = None,
progress_callback = None,
progress_callback_data = None,
callback = finish_load_callback,
user_data = (None))
def _create_file_watcher(self, gfile = None):
if not gfile: return
self._cancel_current_file_watchers()
self._file_change_watcher = gfile.monitor(Gio.FileMonitorFlags.NONE, Gio.Cancellable())
self._file_change_watcher.connect("changed", self._file_monitor)
def _file_monitor(self, file_monitor, file, other_file = None, eve_type = None, data = None):
if not file.get_path() == self._current_file.get_path(): return
if eve_type in [Gio.FileMonitorEvent.CREATED,
Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED,
Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]:
buffer = self.get_buffer()
buffer.set_modified(True)
if eve_type in [ Gio.FileMonitorEvent.CHANGES_DONE_HINT ]:
if self._ignore_internal_change:
self._ignore_internal_change = False
return
# TODO: Any better way to load the difference??
if self._current_file.query_exists():
self.load_file_async(self._current_file)
def _cancel_current_file_watchers(self):
if self._file_change_watcher:
self._file_change_watcher.cancel()
self._file_change_watcher = None
def _write_file(self, gfile, save_as = False):
if not gfile: return
buffer = self.get_buffer()
with open(gfile.get_path(), 'w') as f:
if not save_as:
self._ignore_internal_change = True
start_itr = buffer.get_start_iter()
end_itr = buffer.get_end_iter()
text = buffer.get_text(start_itr, end_itr, True)
f.write(text)
buffer.set_modified(False)
return gfile
def _document_loaded(self, line: int = 0):
for provider in self._completion.get_providers():
self._completion.remove_provider(provider)
uri = self._current_file.get_uri()
buffer = self.get_buffer()
event_system.emit("textDocument/didOpen", (self._current_filetype, uri,))
word_completion = GtkSource.CompletionWords.new("word_completion")
word_completion.register(buffer)
self._completion.add_provider(word_completion)
lsp_completion_provider = LSPCompletionProvider(self)
self._completion.add_provider(lsp_completion_provider)
self.got_to_line(buffer, line)

View File

@ -1,151 +0,0 @@
# Python imports
import random
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class MarkEventsMixin:
def keyboard_insert_mark(self, target_iter = None, is_keyboard_insert = True):
buffer = self.get_buffer()
if not target_iter:
target_iter = buffer.get_iter_at_mark( buffer.get_insert() )
found_mark = self.check_for_insert_marks(target_iter, is_keyboard_insert)
if not found_mark:
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)
self._multi_insert_marks.append(mark)
mark.set_visible(True)
def button_press_insert_mark(self, eve):
data = self.window_to_buffer_coords(Gtk.TextWindowType.TEXT , eve.x, eve.y)
is_over_text, target_iter, is_trailing = self.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.keyboard_insert_mark(target_iter, is_keyboard_insert = False)
def check_for_insert_marks(self, target_iter, is_keyboard_insert):
marks = target_iter.get_marks()
buffer = self.get_buffer()
found_mark = False
for mark in marks:
for _mark in self._multi_insert_marks:
if _mark == mark:
mark.set_visible(False)
buffer.delete_mark(mark)
found_mark = True
break
if found_mark:
self._multi_insert_marks.remove(mark)
break
if not is_keyboard_insert:
for mark in marks:
if "insert" in mark.get_name():
found_mark = True
return found_mark
def keyboard_clear_marks(self):
buffer = self.get_buffer()
buffer.begin_user_action()
for mark in self._multi_insert_marks:
mark.set_visible(False)
buffer.delete_mark(mark)
self._multi_insert_marks.clear()
buffer.end_user_action()
def _update_multi_line_markers(self, buffer, text_str):
for mark in self._multi_insert_marks:
iter = buffer.get_iter_at_mark(mark)
buffer.insert(iter, text_str, -1)
self.end_user_action(buffer)
def _delete_on_multi_line_markers(self, buffer):
iter = buffer.get_iter_at_mark( buffer.get_insert() )
buffer.backspace(iter, interactive = True, default_editable = True)
for mark in self._multi_insert_marks:
iter = buffer.get_iter_at_mark(mark)
buffer.backspace(iter, interactive = True, default_editable = True)
self.end_user_action(buffer)
def _new_line_on_multi_line_markers(self, buffer):
iter = buffer.get_iter_at_mark( buffer.get_insert() )
self._base_indent(buffer, iter)
self.insert_indent_handler(buffer, iter)
for mark in self._multi_insert_marks:
iter = buffer.get_iter_at_mark(mark)
self._base_indent(buffer, iter)
self.insert_indent_handler(buffer, iter)
def insert_indent_handler(self, buffer, iter = None):
if not iter:
iter = buffer.get_iter_at_mark( buffer.get_insert() )
iter_copy = iter.copy()
iter_copy.backward_sentence_start()
iter_copy.forward_sentence_end()
iter_copy.backward_char()
# Note: Need to compare line nums too because backward_sentence_start
# and forward_sentence_end can go farther back if just spaces inbetween...
line_start = iter.get_line()
line_end = iter_copy.get_line()
_char = iter_copy.get_char()
if _char in ["{", ":"] and (line_start - line_end) == 1:
self._indent_deeper(buffer, iter)
def _base_indent(self, buffer, iter):
line_num = iter.get_line()
iter_copy = buffer.get_iter_at_line(line_num)
spaces = ""
_char = iter_copy.get_char()
while _char == " ":
spaces += " "
iter_copy.forward_char()
_char = iter_copy.get_char()
buffer.insert(iter, f"\n{spaces}")
def _indent_deeper(self, buffer, iter):
buffer.insert(iter, " ")
def begin_user_action(self, buffer):
if len(self._multi_insert_marks) > 0:
buffer.begin_user_action()
self.freeze_multi_line_insert = True
def end_user_action(self, buffer):
if len(self._multi_insert_marks) > 0:
buffer.end_user_action()
self.freeze_multi_line_insert = False

View File

@ -1,45 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
# Application imports
class SourceViewDnDMixin:
def _set_up_dnd(self):
PLAIN_TEXT_TARGET_TYPE = 70
URI_TARGET_TYPE = 80
text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE)
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
targets = [ text_target, uri_target ]
self.drag_dest_set_target_list(targets)
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
if info == 70: return
if info == 80:
buffer = self.get_buffer()
uris = data.get_uris()
if len(uris) == 0:
uris = data.get_text().split("\n")
if not self._current_file and not buffer.get_modified():
gfile = Gio.File.new_for_uri(uris[0])
self.open_file(gfile)
uris.pop(0)
for uri in uris:
gfile = None
try:
gfile = Gio.File.new_for_uri(uri)
except Exception as e:
gfile = Gio.File.new_for_path(uri)
event_system.emit('create_view', (gfile,))

View File

@ -1,98 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import GtkSource
# Application imports
from .source_view_controller import SourceViewControllerMixin
# from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider
# from .custom_completion_providers.python_completion_provider import PythonCompletionProvider
class SourceView(SourceViewControllerMixin, GtkSource.View):
def __init__(self):
super(SourceView, self).__init__()
self._language_manager = GtkSource.LanguageManager()
self._style_scheme_manager = GtkSource.StyleSchemeManager()
self._file_loader = None
self._file_change_watcher = None
self._current_file: Gio.File = None
self._current_filename: str = ""
self._current_filepath: str = None
self._current_filetype: str = "buffer"
self._skip_file_load = False
self._ignore_internal_change = False
self._loading_file = False
self._completion = self.get_completion()
self._px_value = settings.theming.default_zoom
self._multi_insert_marks = []
self.freeze_multi_line_insert = False
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("source-view")
ctx.add_class(f"px{self._px_value}")
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
buffer = self.get_buffer()
self._create_default_tag(buffer)
self.set_buffer_language(buffer)
self.set_buffer_style(buffer)
def _setup_signals(self):
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("scroll-event", self._scroll_event)
buffer = self.get_buffer()
buffer.connect('changed', self._is_modified)
buffer.connect("mark-set", self._on_cursor_move)
buffer.connect('insert-text', self._insert_text)
buffer.connect('modified-changed', self._buffer_modified_changed)
def _subscribe_to_events(self):
...
def _load_widgets(self):
self._set_up_dnd()

View File

@ -1,138 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
# Application imports
from .key_input_controller import KeyInputController
from .source_view_events import SourceViewEvents
class SourceViewControllerMixin(KeyInputController, SourceViewEvents):
def get_current_file(self):
return self._current_file
def get_filetype(self):
return self._current_filetype
def set_buffer_language(self, buffer, language = "python3"):
buffer.set_language( self._language_manager.get_language(language) )
def set_buffer_style(self, buffer, style = settings.theming.syntax_theme):
buffer.set_style_scheme( self._style_scheme_manager.get_scheme(style) )
def go_to_call(self):
buffer = self.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
offset = iter.get_line_offset()
uri = self.get_current_file().get_uri()
event_system.emit("textDocument/definition", (self.get_filetype(), uri, line, offset,))
def update_cursor_position(self, buffer = None):
buffer = self.get_buffer() if not buffer else buffer
iter = buffer.get_iter_at_mark( buffer.get_insert() )
chars = iter.get_offset()
row = iter.get_line() + 1
col = self.get_visual_column(iter) + 1
event_system.emit("set_line_char_label", (f"{row}:{col}",))
def update_labels(self, gfile = None):
if not gfile: return
tab_widget = self.get_parent().get_tab_widget()
tab_widget.set_tab_label(self._current_filename)
self.set_bottom_labels(gfile)
def set_bottom_labels(self, gfile = None):
if not gfile: return
event_system.emit("set_bottom_labels", (gfile, None, self._current_filetype, None))
self.update_cursor_position()
def got_to_line(self, buffer = None, line: int = 0):
buffer = self.get_buffer() if not buffer else buffer
line_itr = buffer.get_iter_at_line(line)
char_iter = buffer.get_iter_at_line_offset(line, line_itr.get_bytes_in_line())
buffer.place_cursor(char_iter)
# Note: scroll_to_iter and scroll_to_mark depend on an idle recalculate of buffers after load to work
GLib.idle_add(self.scroll_to_mark, buffer.get_insert(), 0.1, True, 0.0, 0.1)
def toggle_highlight_line(self, widget = None, eve = None):
self.set_highlight_current_line( not self.get_highlight_current_line() )
def scale_up_text(self, buffer = None, scale_step = 10):
if not buffer:
buffer = self.get_buffer()
ctx = self.get_style_context()
if self._px_value < 99:
self._px_value += 1
ctx.add_class(f"px{self._px_value}")
# NOTE: Hope to bring this or similar back after we decouple scaling issues coupled with the miniview.
# tag_table = buffer.get_tag_table()
# start_itr = buffer.get_start_iter()
# end_itr = buffer.get_end_iter()
# tag = tag_table.lookup('general_style')
#
# tag.set_property('scale', tag.get_property('scale') + scale_step)
# buffer.apply_tag(tag, start_itr, end_itr)
def scale_down_text(self, buffer = None, scale_step = 10):
if not buffer:
buffer = self.get_buffer()
ctx = self.get_style_context()
if self._px_value > 1:
ctx.remove_class(f"px{self._px_value}")
self._px_value -= 1
ctx.add_class(f"px{self._px_value}")
# NOTE: Hope to bring this or similar back after we decouple scaling issues coupled with the miniview.
# tag_table = buffer.get_tag_table()
# start_itr = buffer.get_start_iter()
# end_itr = buffer.get_end_iter()
# tag = tag_table.lookup('general_style')
#
# tag.set_property('scale', tag.get_property('scale') - scale_step)
# buffer.apply_tag(tag, start_itr, end_itr)
def keyboard_undo(self):
buffer = self.get_buffer()
buffer.undo()
def keyboard_redo(self):
buffer = self.get_buffer()
buffer.redo()
def keyboard_move_lines_up(self):
buffer = self.get_buffer()
self.begin_user_action(buffer)
self.emit("move-lines", *(False,))
# unindent_lines
# self.emit("move-words", *(self, 4,))
self.end_user_action(buffer)
def keyboard_move_lines_down(self):
buffer = self.get_buffer()
self.begin_user_action(buffer)
self.emit("move-lines", *(True,))
# self.emit("move-words", *(self, -4,))
self.end_user_action(buffer)

View File

@ -1,102 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
# Application imports
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
from .mixins.source_file_events_mixin import FileEventsMixin
from .mixins.source_mark_events_mixin import MarkEventsMixin
class SourceViewEvents(SourceViewDnDMixin, MarkEventsMixin, FileEventsMixin):
def _create_default_tag(self, buffer):
general_style_tag = buffer.create_tag('general_style')
general_style_tag.set_property('size', 100)
general_style_tag.set_property('scale', 100)
def _is_modified(self, *args):
buffer = self.get_buffer()
file_type = self.get_filetype()
if not self._loading_file:
event_system.emit("buffer_changed", (buffer, ))
event_system.emit("textDocument/didChange", (file_type, buffer, ))
else:
event_system.emit("buffer_changed_first_load", (buffer, ))
self.update_cursor_position(buffer)
def _insert_text(self, buffer, location_itr, text_str, len_int):
if self.freeze_multi_line_insert: return
self.begin_user_action(buffer)
with buffer.freeze_notify():
GLib.idle_add(self._update_multi_line_markers, *(buffer, text_str,))
def _buffer_modified_changed(self, buffer):
tab_widget = self.get_parent().get_tab_widget()
tab_widget.set_status(changed = True if buffer.get_modified() else False)
def _button_press_event(self, widget = None, eve = None, user_data = None):
if eve.type == Gdk.EventType.BUTTON_PRESS and eve.button == 1 : # l-click
if eve.state & Gdk.ModifierType.CONTROL_MASK:
self.button_press_insert_mark(eve)
return True
else:
self.keyboard_clear_marks()
elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
...
def _scroll_event(self, widget, eve):
accel_mask = Gtk.accelerator_get_default_mod_mask()
x, y, z = eve.get_scroll_deltas()
if eve.state & accel_mask == Gdk.ModifierType.CONTROL_MASK:
buffer = self.get_buffer()
if z > 0:
self.scale_down_text(buffer)
else:
self.scale_up_text(buffer)
return True
if eve.state & accel_mask == Gdk.ModifierType.SHIFT_MASK:
adjustment = self.get_hadjustment()
current_val = adjustment.get_value()
step_val = adjustment.get_step_increment()
if z > 0: # NOTE: scroll left
adjustment.set_value(current_val - step_val * 2)
else: # NOTE: scroll right
adjustment.set_value(current_val + step_val * 2)
return True
def _focus_in_event(self, widget, eve = None):
event_system.emit("set_active_src_view", (self,))
self.get_parent().get_parent().is_editor_focused = True
def _on_widget_focus(self, widget, eve = None):
tab_view = self.get_parent().get_parent()
path = self._current_file if self._current_file else ""
event_system.emit('focused_target_changed', (tab_view.NAME,))
event_system.emit("set_path_label", (path,))
event_system.emit("set_encoding_label")
event_system.emit("set_file_type_label", (self._current_filetype,))
return False
def _on_cursor_move(self, buffer, cursor_iter, mark, user_data = None):
if mark != buffer.get_insert(): return
self.update_cursor_position(buffer)
# NOTE: Not sure but this might not be efficient if the map reloads the same view...
event_system.emit(f"set_source_view", (self,))

View File

@ -1,46 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from ..tab_header_widget import TabHeaderWidget
from .sourceview.source_view import SourceView
class SourceViewContainer(Gtk.ScrolledWindow):
def __init__(self, close_tab):
super(SourceViewContainer, self).__init__()
self._close_tab: function = close_tab
self._source_view: SourceView = None
self._tab_widget: TabHeaderWidget = None
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
...
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
self._source_view = SourceView()
self._tab_widget = TabHeaderWidget(self, self._source_view, self._close_tab)
self.add(self._source_view)
def get_tab_widget(self):
return self._tab_widget
def get_source_view(self):
return self._source_view

View File

@ -49,6 +49,10 @@ class AceEditor(WebKit2.WebView):
event_system.subscribe(f"updated_session_{self.INDEX}", self.updated_session) event_system.subscribe(f"updated_session_{self.INDEX}", self.updated_session)
event_system.subscribe(f"close_session_{self.INDEX}", self.close_session) event_system.subscribe(f"close_session_{self.INDEX}", self.close_session)
event_system.subscribe(f"remove_session_{self.INDEX}", self.remove_session) event_system.subscribe(f"remove_session_{self.INDEX}", self.remove_session)
event_system.subscribe(f"keyboard_scale_up_text_{self.INDEX}", self.keyboard_scale_up_text)
event_system.subscribe(f"keyboard_scale_down_text_{self.INDEX}", self.keyboard_scale_down_text)
event_system.subscribe(f"toggle_highlight_line_{self.INDEX}", self.toggle_highlight_line)
event_system.subscribe(f"ui_message_{self.INDEX}", self.ui_message) event_system.subscribe(f"ui_message_{self.INDEX}", self.ui_message)
def _load_settings(self): def _load_settings(self):
@ -104,6 +108,18 @@ class AceEditor(WebKit2.WebView):
command = f"removeSession('{fhash}')" command = f"removeSession('{fhash}')"
self.run_javascript(command, None, None) self.run_javascript(command, None, None)
def keyboard_scale_up_text(self):
command = "zoomIn()"
self.run_javascript(command, None, None)
def keyboard_scale_down_text(self):
command = "zoomOut()"
self.run_javascript(command, None, None)
def toggle_highlight_line(self):
command = "toggleLineHighlight()"
self.run_javascript(command, None, None)
def ui_message(self, message, mtype): def ui_message(self, message, mtype):
command = f"displayMessage('{message}', '{mtype}', '3')" command = f"displayMessage('{message}', '{mtype}', '3')"
self.run_javascript(command, None, None) self.run_javascript(command, None, None)

View File

@ -7,7 +7,6 @@ from gi.repository import Gtk
# Application imports # Application imports
from ....controllers.files_controller import FilesController from ....controllers.files_controller import FilesController
# from ...controls.tab_bar import TabBar
from .fixed_box import FixedBox from .fixed_box import FixedBox
@ -44,5 +43,4 @@ class Editor(Gtk.Box):
FilesController(self.INDEX) FilesController(self.INDEX)
def _load_widgets(self): def _load_widgets(self):
# self.add( TabBar(self.INDEX) )
self.add( FixedBox(self.INDEX) ) self.add( FixedBox(self.INDEX) )

View File

@ -25,6 +25,7 @@ class SaveAsButton(Gtk.Button):
self.set_always_show_image(True) self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1 self.set_image_position(1) # Left - 0, Right = 1
self.set_hexpand(False) self.set_hexpand(False)
self.set_sensitive(False)
def _setup_signals(self): def _setup_signals(self):
self.connect("released", self._emit_save_as_eve) self.connect("released", self._emit_save_as_eve)

View File

@ -20,7 +20,7 @@ class ScaleDownButton(Gtk.Button):
def _setup_styling(self): def _setup_styling(self):
self.set_label("Zoom") self.set_label("Zoom Out")
self.set_image( Gtk.Image.new_from_icon_name("gtk-zoom-out", 4) ) self.set_image( Gtk.Image.new_from_icon_name("gtk-zoom-out", 4) )
self.set_always_show_image(True) self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1 self.set_image_position(1) # Left - 0, Right = 1

View File

@ -20,7 +20,7 @@ class ScaleUpButton(Gtk.Button):
def _setup_styling(self): def _setup_styling(self):
self.set_label("Zoom") self.set_label("Zoom In")
self.set_image( Gtk.Image.new_from_icon_name("gtk-zoom-in", 4) ) self.set_image( Gtk.Image.new_from_icon_name("gtk-zoom-in", 4) )
self.set_always_show_image(True) self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1 self.set_image_position(1) # Left - 0, Right = 1

View File

@ -1,114 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from ..tab_header_widget import TabHeaderWidget
class TabBar(Gtk.Notebook):
def __init__(self, index):
super(TabBar, self).__init__()
self.INDEX = index
self.added_tab = None
self.set_group_name("editor_widget")
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
...
def _setup_signals(self):
self.connect("switch-page", self._switch_page_update)
def _subscribe_to_events(self):
event_system.subscribe(f"add_tab_{self.INDEX}", self.add_tab)
event_system.subscribe(f"add_tab_with_name_{self.INDEX}", self.add_tab)
event_system.subscribe(f"update_tab_{self.INDEX}", self.update_tab)
event_system.subscribe(f"close_tab_{self.INDEX}", self.close_tab)
def _load_widgets(self):
start_box = Gtk.Box()
end_box = Gtk.Box()
add_tab = Gtk.Button(label = "+")
add_tab.connect("released", self._button_add_tab)
end_box.add(add_tab)
start_box.show_all()
end_box.show_all()
self.set_action_widget(start_box, 0)
self.set_action_widget(end_box, 1)
def _button_add_tab(self, widget):
event_system.emit(f"new_session_{self.INDEX}")
def _button_close_tab(self, widget, container):
self._close_tab(widget, container)
event_system.emit(f"remove_session_{self.INDEX}", (container.fhash))
# Note: Need to get parent instead given we pass the close_tab method
# from a potentially former notebook.
def _close_tab(self, widget, container):
notebook = container.get_parent()
if notebook.get_n_pages() < 2: return
page_num = notebook.page_num(container)
notebook.remove_page(page_num)
# container._cancel_current_file_watchers()
def _switch_page_update(self, notebook, container, page_num):
if self.added_tab or self.added_tab == None:
self.added_tab = False
return
event_system.emit(f"switch_session_{self.INDEX}", (container.fhash,))
def add_tab(self, fhash, title = "[BAD TITLE]"):
container = Gtk.EventBox()
header = TabHeaderWidget(container, self._button_close_tab)
page_num = self.append_page(container, header)
container.fhash = fhash
self.added_tab = True
header.label.set_label(title)
self.set_tab_detachable(container, True)
self.set_tab_reorderable(container, True)
self.show_all()
self.set_current_page(page_num)
def update_tab(self, fhash, title = "[BAD TITLE]"):
for child in self.get_children():
if child.fhash == fhash:
target = child
break
label = self.get_tab_label(target).get_children()[0]
label.set_label(title)
def close_tab(self, fhash):
target = None
for child in self.get_children():
if child.fhash == fhash:
target = child
break
self._close_tab(None, target)

View File

@ -73,6 +73,7 @@ class ThemeButton(Gtk.Button):
self.set_image_position(1) # Left - 0, Right = 1 self.set_image_position(1) # Left - 0, Right = 1
self.set_margin_left(5) self.set_margin_left(5)
# self.set_margin_right(5) # self.set_margin_right(5)
self.set_sensitive(False)
def _setup_signals(self): def _setup_signals(self):
self.connect("clicked", self._show_popover) self.connect("clicked", self._show_popover)

View File

@ -26,7 +26,7 @@
<pre id="editor"></pre> <pre id="editor"></pre>
<!-- Buffers modal --> <!-- Buffers modal -->
<div class="modal fade" id="buffers-modal" tabindex="-1" role="dialog" data-bs-theme="dark"> <div class="modal" id="buffers-modal" tabindex="-1" role="dialog" data-bs-theme="dark">
<div class="modal-dialog modal-xl" role="document"> <div class="modal-dialog modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -47,7 +47,6 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" data-bs-dismiss="modal" class="btn btn-danger btn-sm">Close</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,12 +23,16 @@ window.onerror = function(msg, url, line, col, error) {
document.addEventListener("keyup", (eve) => { document.addEventListener("keyup", (eve) => {
switch (eve.key) { switch (eve.key) {
case "ArrowUp": case "ArrowUp":
setLabels();
break; break;
case "ArrowDown": case "ArrowDown":
setLabels();
break; break;
case "ArrowLeft": case "ArrowLeft":
setLabels();
break; break;
case "ArrowRight": case "ArrowRight":
setLabels();
break; break;
case "Enter": case "Enter":
if ( isNotNullOrUndefined(previewSel) ) { if ( isNotNullOrUndefined(previewSel) ) {
@ -40,8 +44,10 @@ document.addEventListener("keyup", (eve) => {
previewSel.dispatchEvent(event); previewSel.dispatchEvent(event);
} }
break
case "Control": case "Control":
isControlDown = false; isControlDown = false;
break;
case "b": case "b":
if (isControlDown) { if (isControlDown) {
if ( isNotNullOrUndefined(previewSel) ) { if ( isNotNullOrUndefined(previewSel) ) {
@ -55,6 +61,7 @@ document.addEventListener("keyup", (eve) => {
} }
break; break;
default: default:
setLabels();
break break
} }
}); });
@ -80,7 +87,18 @@ document.addEventListener("keydown", (eve) => {
break; break;
case "Control": case "Control":
isControlDown = true; isControlDown = true;
break;
default: default:
break break
} }
}); });
// editor.session.selection.on('changeCursor', (eve) => {
// let ftype = aceSessions[fhash]["ftype"];
// let fpath = aceSessions[fhash]["fpath"];
// console.log(eve);
// // sendMessage("set_labels", ftype, "", fpath, "");
// });

View File

@ -2,9 +2,26 @@ const messenger = (window.webkit) ? window.webkit.messageHandlers : (message) =
console.log("Message: " + message); console.log("Message: " + message);
}; };
const editorOpts = {
printMarginColumn: 80,
enableBasicAutocompletion: true,
enableInlineAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
useSoftTabs: true,
tabSize: 4,
tooltipFollowsMouse: true,
useWrapMode: false,
scrollPastEnd: 0.5,
mergeUndoDeltas: false
}
let editor = null; let editor = null;
let previewEditor = null; let previewEditor = null;
let aceSessions = {}; let aceSessions = {};
let currentSession = null; let currentSession = null;
let previewSel = null; let previewSel = null;
let fontSize = 12;
let highlightLine = true;
let isControlDown = false; let isControlDown = false;

View File

@ -45,6 +45,13 @@ const editorCommands = [
closeSession(currentSession); closeSession(currentSession);
}, },
readOnly: true readOnly: true
}, {
name: "toggleLineHighlight",
bindKey: {win: "ctrl-h", mac: "ctrl-h"},
exec: function(editor) {
toggleLineHighlight();
},
readOnly: true
}, { }, {
name: "movelinesUp", name: "movelinesUp",
bindKey: {win: "ctrl-up", mac: "ctrl-up"}, bindKey: {win: "ctrl-up", mac: "ctrl-up"},
@ -59,6 +66,27 @@ const editorCommands = [
editor.execCommand("movelinesdown"); editor.execCommand("movelinesdown");
}, },
readOnly: true readOnly: true
}, {
name: "tgglTopMainMenubar",
bindKey: {win: "ctrl-0", mac: "ctrl-0"},
exec: function(editor) {
sendMessage("tggl_top_main_menubar", "", "", "", "");
},
readOnly: true
}, {
name: "zoomIn",
bindKey: {win: "ctrl-=", mac: "ctrl-="},
exec: function(editor) {
zoomIn();
},
readOnly: true
}, {
name: "zoomOut",
bindKey: {win: "ctrl--", mac: "ctrl--"},
exec: function(editor) {
zoomOut();
},
readOnly: true
}, { }, {
name: "scrollUp", name: "scrollUp",
bindKey: {win: "alt-up", mac: "alt-up"}, bindKey: {win: "alt-up", mac: "alt-up"},

View File

@ -4,20 +4,7 @@ const loadPreviewEditor = () => {
previewEditor = ace.edit("preview-editor"); previewEditor = ace.edit("preview-editor");
// Note: https://github.com/ajaxorg/ace/wiki/Configuring-Ace // Note: https://github.com/ajaxorg/ace/wiki/Configuring-Ace
previewEditor.setOptions({ previewEditor.setOptions(editorOpts);
printMarginColumn: 80,
enableBasicAutocompletion: true,
enableInlineAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
useSoftTabs: true,
tabSize: 4,
tooltipFollowsMouse: true,
useWrapMode: false,
scrollPastEnd: 0.5,
mergeUndoDeltas: false
});
// Note: https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts // Note: https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts
previewEditor.commands.addCommands(editorCommands); previewEditor.commands.addCommands(editorCommands);
@ -30,33 +17,22 @@ const loadEditor = () => {
editor = ace.edit("editor"); editor = ace.edit("editor");
// Note: https://github.com/ajaxorg/ace/wiki/Configuring-Ace // Note: https://github.com/ajaxorg/ace/wiki/Configuring-Ace
editor.setOptions({ editor.setOptions(editorOpts);
printMarginColumn: 80,
enableBasicAutocompletion: true,
enableInlineAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
useSoftTabs: true,
tabSize: 4,
tooltipFollowsMouse: true,
useWrapMode: false,
scrollPastEnd: 0.5,
mergeUndoDeltas: false
});
// Note: https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts // Note: https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts
editor.commands.addCommands(editorCommands); editor.commands.addCommands(editorCommands);
editor.setTheme("ace/theme/one_dark"); editor.setTheme("ace/theme/one_dark");
editor.addEventListener("click", (eve) => {
setLabels();
});
} }
const loadInitialSession = () => { const loadInitialSession = () => {
newSession(null, editor.getSession()); newSession(null, editor.getSession());
} }
const newSession = (eve = null, session = null) => { const newSession = (eve = null, session = null) => {
let ftype = "buffer"; let ftype = "buffer";
let fhash = Date.now().toString(); let fhash = Date.now().toString();
@ -83,6 +59,8 @@ const setSession = (ftype, fhash, session) => {
if (ftype !== "buffer") { if (ftype !== "buffer") {
editor.session.setMode("ace/mode/" + ftype); editor.session.setMode("ace/mode/" + ftype);
} }
setLabels();
} }
const updateSession = (fhash, ftype, fname, fpath) => { const updateSession = (fhash, ftype, fname, fpath) => {
@ -202,4 +180,32 @@ const selectNextPreview = () => {
} }
selectedElm.click(); selectedElm.click();
} }
const setLabels = () => {
let ftype = aceSessions[currentSession]["ftype"];
let fpath = aceSessions[currentSession]["fpath"];
let cursor = editor.selection.getCursor();
let pos = `${cursor.row + 1}:${cursor.column}`;
sendMessage("set_info_labels", ftype, "", fpath, pos);
}
const zoomIn = () => {
fontSize += 1;
document.getElementById('editor').style.fontSize = `${fontSize}px`;
}
const zoomOut = () => {
fontSize -= 1;
document.getElementById('editor').style.fontSize = `${fontSize}px`;
}
const toggleLineHighlight = () => {
highlightLine = !highlightLine;
editor.setHighlightActiveLine(highlightLine);
}