Restructuring buffer access to provide better separation for future work

This commit is contained in:
itdominator 2023-10-21 15:46:17 -05:00
parent c88fec7a27
commit 7a343e39e8
8 changed files with 153 additions and 103 deletions

View File

@ -0,0 +1,23 @@
# Python imports
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports
# NOTE: GtkSource 5 allows for smart indent action by allowing us to override the default auto indent logic...
# In the long run this will be better because we can check not only for :, ;, { or other things but apply per language such as bash where
# there isn't a special char but words...
# class AutoIndenter(GtkSource.Indenter):
# def __init__(self):
# ...
#
# def indent(self, view, iter):
# ...
#
# def is_trigger(self, view, iter, modifier, keyval):
# print(iter.get_char())

View File

@ -105,4 +105,4 @@ class PythonCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
try: try:
return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0) return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
except: except:
return None return None

View File

@ -64,14 +64,14 @@ class FileEventsMixin:
self.update_labels(gfile) self.update_labels(gfile)
return return
file = GtkSource.File() file = GtkSource.File()
buffer = self.get_buffer()
file.set_location(gfile) file.set_location(gfile)
self._file_loader = GtkSource.FileLoader.new(self._buffer, file) self._file_loader = GtkSource.FileLoader.new(buffer, file)
def finish_load_callback(obj, res, user_data=None): def finish_load_callback(obj, res, user_data = None):
self._file_loader.load_finish(res) self._file_loader.load_finish(res)
self._document_loaded() self._document_loaded(line)
self.got_to_line(line)
self.update_labels(gfile) self.update_labels(gfile)
self._loading_file = False self._loading_file = False
@ -97,7 +97,8 @@ class FileEventsMixin:
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.RENAMED,
Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
self._buffer.set_modified(True) buffer = self.get_buffer()
buffer.set_modified(True)
if eve_type in [ Gio.FileMonitorEvent.CHANGES_DONE_HINT ]: if eve_type in [ Gio.FileMonitorEvent.CHANGES_DONE_HINT ]:
if self._ignore_internal_change: if self._ignore_internal_change:
@ -116,16 +117,16 @@ class FileEventsMixin:
def _write_file(self, gfile, save_as = False): def _write_file(self, gfile, save_as = False):
if not gfile: return if not gfile: return
buffer = self.get_buffer()
with open(gfile.get_path(), 'w') as f: with open(gfile.get_path(), 'w') as f:
if not save_as: if not save_as:
self._ignore_internal_change = True self._ignore_internal_change = True
start_itr = self._buffer.get_start_iter() start_itr = buffer.get_start_iter()
end_itr = self._buffer.get_end_iter() end_itr = buffer.get_end_iter()
text = self._buffer.get_text(start_itr, end_itr, True) text = buffer.get_text(start_itr, end_itr, True)
f.write(text) f.write(text)
f.close()
self._buffer.set_modified(False) buffer.set_modified(False)
return gfile return gfile

View File

@ -13,8 +13,10 @@ from gi.repository import Gtk
class MarkEventsMixin: class MarkEventsMixin:
def keyboard_insert_mark(self, target_iter = None, is_keyboard_insert = True): def keyboard_insert_mark(self, target_iter = None, is_keyboard_insert = True):
buffer = self.get_buffer()
if not target_iter: if not target_iter:
target_iter = self._buffer.get_iter_at_mark( self._buffer.get_insert() ) target_iter = buffer.get_iter_at_mark( buffer.get_insert() )
found_mark = self.check_for_insert_marks(target_iter, is_keyboard_insert) found_mark = self.check_for_insert_marks(target_iter, is_keyboard_insert)
if not found_mark: if not found_mark:
@ -22,7 +24,7 @@ class MarkEventsMixin:
hash = "%032x" % random_bits hash = "%032x" % random_bits
mark = Gtk.TextMark.new(name = f"multi_insert_{hash}", left_gravity = False) mark = Gtk.TextMark.new(name = f"multi_insert_{hash}", left_gravity = False)
self._buffer.add_mark(mark, target_iter) buffer.add_mark(mark, target_iter)
self._multi_insert_marks.append(mark) self._multi_insert_marks.append(mark)
mark.set_visible(True) mark.set_visible(True)
@ -38,13 +40,15 @@ class MarkEventsMixin:
self.keyboard_insert_mark(target_iter, is_keyboard_insert = False) self.keyboard_insert_mark(target_iter, is_keyboard_insert = False)
def check_for_insert_marks(self, target_iter, is_keyboard_insert): def check_for_insert_marks(self, target_iter, is_keyboard_insert):
marks = target_iter.get_marks() marks = target_iter.get_marks()
buffer = self.get_buffer()
found_mark = False found_mark = False
for mark in marks: for mark in marks:
for _mark in self._multi_insert_marks: for _mark in self._multi_insert_marks:
if _mark == mark: if _mark == mark:
mark.set_visible(False) mark.set_visible(False)
self._buffer.delete_mark(mark) buffer.delete_mark(mark)
found_mark = True found_mark = True
break break
@ -60,39 +64,41 @@ class MarkEventsMixin:
return found_mark return found_mark
def keyboard_clear_marks(self): def keyboard_clear_marks(self):
self._buffer.begin_user_action() buffer = self.get_buffer()
buffer.begin_user_action()
for mark in self._multi_insert_marks: for mark in self._multi_insert_marks:
mark.set_visible(False) mark.set_visible(False)
self._buffer.delete_mark(mark) buffer.delete_mark(mark)
self._multi_insert_marks.clear() self._multi_insert_marks.clear()
self._buffer.end_user_action() buffer.end_user_action()
def _update_multi_line_markers(self, text_str): def _update_multi_line_markers(self, buffer, text_str):
for mark in self._multi_insert_marks: for mark in self._multi_insert_marks:
iter = self._buffer.get_iter_at_mark(mark) iter = buffer.get_iter_at_mark(mark)
self._buffer.insert(iter, text_str, -1) buffer.insert(iter, text_str, -1)
self.end_user_action() self.end_user_action(buffer)
def _delete_on_multi_line_markers(self): def _delete_on_multi_line_markers(self, buffer):
iter = self._buffer.get_iter_at_mark( self._buffer.get_insert() ) iter = buffer.get_iter_at_mark( buffer.get_insert() )
self._buffer.backspace(iter, interactive = True, default_editable = True) buffer.backspace(iter, interactive = True, default_editable = True)
for mark in self._multi_insert_marks: for mark in self._multi_insert_marks:
iter = self._buffer.get_iter_at_mark(mark) iter = buffer.get_iter_at_mark(mark)
self._buffer.backspace(iter, interactive = True, default_editable = True) buffer.backspace(iter, interactive = True, default_editable = True)
self.end_user_action() self.end_user_action(buffer)
def begin_user_action(self): def begin_user_action(self, buffer):
if len(self._multi_insert_marks) > 0: if len(self._multi_insert_marks) > 0:
self._buffer.begin_user_action() buffer.begin_user_action()
self.freeze_multi_line_insert = True self.freeze_multi_line_insert = True
def end_user_action(self): def end_user_action(self, buffer):
if len(self._multi_insert_marks) > 0: if len(self._multi_insert_marks) > 0:
self._buffer.end_user_action() buffer.end_user_action()
self.freeze_multi_line_insert = False self.freeze_multi_line_insert = False

View File

@ -12,6 +12,7 @@ from gi.repository import Gio
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
# from .auto_indenter import AutoIndenter
from .source_view_events import SourceViewEventsMixin from .source_view_events import SourceViewEventsMixin
from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider
from .custom_completion_providers.python_completion_provider import PythonCompletionProvider from .custom_completion_providers.python_completion_provider import PythonCompletionProvider
@ -36,7 +37,6 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
self._skip_file_load = False self._skip_file_load = False
self._ignore_internal_change = False self._ignore_internal_change = False
self._loading_file = False self._loading_file = False
self._buffer = self.get_buffer()
self._completion = self.get_completion() self._completion = self.get_completion()
self._px_value = settings.theming.default_zoom self._px_value = settings.theming.default_zoom
@ -55,6 +55,7 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
ctx.add_class("source-view") ctx.add_class("source-view")
ctx.add_class(f"px{self._px_value}") ctx.add_class(f"px{self._px_value}")
self.set_vexpand(True)
self.set_show_line_marks(True) self.set_show_line_marks(True)
self.set_show_line_numbers(True) self.set_show_line_numbers(True)
@ -67,12 +68,13 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
self.set_show_right_margin(True) self.set_show_right_margin(True)
self.set_right_margin_position(80) self.set_right_margin_position(80)
self.set_background_pattern(0) # 0 = None, 1 = Grid self.set_background_pattern(0) # 0 = None, 1 = Grid
# NOTE: Add back once we move to Gtk 4 and use GtkSource 5
# self.set_indenter( AutoIndenter() )
self._create_default_tag() buffer = self.get_buffer()
self.set_buffer_language() self._create_default_tag(buffer)
self.set_buffer_style() self.set_buffer_language(buffer)
self.set_buffer_style(buffer)
self.set_vexpand(True)
def _setup_signals(self): def _setup_signals(self):
@ -84,10 +86,12 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
self.connect("button-press-event", self._button_press_event) self.connect("button-press-event", self._button_press_event)
self.connect("scroll-event", self._scroll_event) self.connect("scroll-event", self._scroll_event)
self._buffer.connect('changed', self._is_modified) buffer = self.get_buffer()
self._buffer.connect("mark-set", self._on_cursor_move) buffer.connect('changed', self._is_modified)
self._buffer.connect('insert-text', self._insert_text) buffer.connect("mark-set", self._on_cursor_move)
self._buffer.connect('modified-changed', self._buffer_modified_changed) buffer.connect('insert-text', self._insert_text)
buffer.connect('modified-changed', self._buffer_modified_changed)
def _subscribe_to_events(self): def _subscribe_to_events(self):
... ...
@ -96,14 +100,15 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
... ...
def _document_loaded(self): def _document_loaded(self, line: int = 0):
for provider in self._completion.get_providers(): for provider in self._completion.get_providers():
self._completion.remove_provider(provider) self._completion.remove_provider(provider)
# TODO: actually load a meaningful provider based on file type... # TODO: actually load a meaningful provider based on file type...
file = self._current_file.get_path() file = self._current_file.get_path()
buffer = self.get_buffer()
word_completion = GtkSource.CompletionWords.new("word_completion") word_completion = GtkSource.CompletionWords.new("word_completion")
word_completion.register(self._buffer) word_completion.register(buffer)
self._completion.add_provider(word_completion) self._completion.add_provider(word_completion)
# example_completion_provider = ExampleCompletionProvider() # example_completion_provider = ExampleCompletionProvider()
@ -111,27 +116,30 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
# py_completion_provider = PythonCompletionProvider(file) # py_completion_provider = PythonCompletionProvider(file)
# self._completion.add_provider(py_completion_provider) # self._completion.add_provider(py_completion_provider)
self.got_to_line(buffer, line)
def _create_default_tag(self): def _create_default_tag(self, buffer):
general_style_tag = self._buffer.create_tag('general_style') general_style_tag = buffer.create_tag('general_style')
general_style_tag.set_property('size', 100) general_style_tag.set_property('size', 100)
general_style_tag.set_property('scale', 100) general_style_tag.set_property('scale', 100)
def _is_modified(self, *args): def _is_modified(self, *args):
buffer = self.get_buffer()
if not self._loading_file: if not self._loading_file:
event_system.emit("buffer_changed", (self._buffer, )) event_system.emit("buffer_changed", (buffer, ))
else: else:
event_system.emit("buffer_changed_first_load", (self._buffer, )) event_system.emit("buffer_changed_first_load", (buffer, ))
self.update_cursor_position() self.update_cursor_position(buffer)
def _insert_text(self, text_buffer, location_itr, text_str, len_int): def _insert_text(self, buffer, location_itr, text_str, len_int):
if self.freeze_multi_line_insert: return if self.freeze_multi_line_insert: return
self.begin_user_action() self.begin_user_action(buffer)
with self._buffer.freeze_notify(): with buffer.freeze_notify():
GLib.idle_add(self._update_multi_line_markers, *(text_str,)) GLib.idle_add(self._update_multi_line_markers, *(buffer, text_str,))
def _buffer_modified_changed(self, buffer): def _buffer_modified_changed(self, buffer):
tab_widget = self.get_parent().get_tab_widget() tab_widget = self.get_parent().get_tab_widget()
@ -144,6 +152,7 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
buffer = self.get_buffer()
try: try:
is_alt = True if modifiers & Gdk.ModifierType.ALT_MASK else False is_alt = True if modifiers & Gdk.ModifierType.ALT_MASK else False
@ -156,7 +165,7 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
if is_shift: if is_shift:
if keyname in [ "z", "Up", "Down", "Left", "Right" ]: if keyname in [ "z", "Up", "Down", "Left", "Right" ]:
# NOTE: For now do like so for completion sake above. # NOTE: For now do like so for completion sake above.
if keyname in ["Left", "Right"]: if keyname in ["Left", "Right"]:
return False return False
@ -168,9 +177,9 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
if keyname == "BackSpace": if keyname == "BackSpace":
if len(self._multi_insert_marks) > 0: if len(self._multi_insert_marks) > 0:
self.begin_user_action() self.begin_user_action(buffer)
with self._buffer.freeze_notify(): with buffer.freeze_notify():
GLib.idle_add(self._delete_on_multi_line_markers) GLib.idle_add(self._delete_on_multi_line_markers, *(buffer,))
return True return True
@ -190,12 +199,13 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
def _scroll_event(self, widget, eve): def _scroll_event(self, widget, eve):
accel_mask = Gtk.accelerator_get_default_mod_mask() accel_mask = Gtk.accelerator_get_default_mod_mask()
x, y, z = eve.get_scroll_deltas() x, y, z = eve.get_scroll_deltas()
if eve.state & accel_mask == Gdk.ModifierType.CONTROL_MASK: if eve.state & accel_mask == Gdk.ModifierType.CONTROL_MASK:
buffer = self.get_buffer()
if z > 0: if z > 0:
self.scale_down_text() self.scale_down_text(buffer)
else: else:
self.scale_up_text() self.scale_up_text(buffer)
return True return True
@ -226,10 +236,10 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
return False return False
def _on_cursor_move(self, buf, cursor_iter, mark, user_data = None): def _on_cursor_move(self, buffer, cursor_iter, mark, user_data = None):
if mark != buf.get_insert(): return if mark != buffer.get_insert(): return
self.update_cursor_position() self.update_cursor_position(buffer)
# NOTE: Not sure but this might not be efficient if the map reloads the same view... # NOTE: Not sure but this might not be efficient if the map reloads the same view...
event_system.emit(f"set_source_view", (self,)) event_system.emit(f"set_source_view", (self,))
@ -246,12 +256,13 @@ class SourceView(SourceViewEventsMixin, GtkSource.View):
if info == 70: return if info == 70: return
if info == 80: if info == 80:
uris = data.get_uris() buffer = self.get_buffer()
uris = data.get_uris()
if len(uris) == 0: if len(uris) == 0:
uris = data.get_text().split("\n") uris = data.get_text().split("\n")
if not self._current_file and not self._buffer.get_modified(): if not self._current_file and not buffer.get_modified():
gfile = Gio.File.new_for_uri(uris[0]) gfile = Gio.File.new_for_uri(uris[0])
self.open_file(gfile) self.open_file(gfile)
uris.pop(0) uris.pop(0)

View File

@ -13,16 +13,16 @@ from .source_marks_events_mixin import MarkEventsMixin
class SourceViewEventsMixin(MarkEventsMixin, FileEventsMixin): class SourceViewEventsMixin(MarkEventsMixin, FileEventsMixin):
def set_buffer_language(self, language = "python3"): def set_buffer_language(self, buffer, language = "python3"):
self._buffer.set_language( self._language_manager.get_language(language) ) buffer.set_language( self._language_manager.get_language(language) )
def set_buffer_style(self, style = settings.theming.syntax_theme): def set_buffer_style(self, buffer, style = settings.theming.syntax_theme):
self._buffer.set_style_scheme( self._style_scheme_manager.get_scheme(style) ) buffer.set_style_scheme( self._style_scheme_manager.get_scheme(style) )
def toggle_highlight_line(self, widget = None, eve = None): def toggle_highlight_line(self, widget = None, eve = None):
self.set_highlight_current_line( not self.get_highlight_current_line() ) self.set_highlight_current_line( not self.get_highlight_current_line() )
def scale_up_text(self, scale_step = 10): def scale_up_text(self, buffer, scale_step = 10):
ctx = self.get_style_context() ctx = self.get_style_context()
if self._px_value < 99: if self._px_value < 99:
@ -30,15 +30,15 @@ class SourceViewEventsMixin(MarkEventsMixin, FileEventsMixin):
ctx.add_class(f"px{self._px_value}") 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. # NOTE: Hope to bring this or similar back after we decouple scaling issues coupled with the miniview.
# tag_table = self._buffer.get_tag_table() # tag_table = buffer.get_tag_table()
# start_itr = self._buffer.get_start_iter() # start_itr = buffer.get_start_iter()
# end_itr = self._buffer.get_end_iter() # end_itr = buffer.get_end_iter()
# tag = tag_table.lookup('general_style') # tag = tag_table.lookup('general_style')
# #
# tag.set_property('scale', tag.get_property('scale') + scale_step) # tag.set_property('scale', tag.get_property('scale') + scale_step)
# self._buffer.apply_tag(tag, start_itr, end_itr) # buffer.apply_tag(tag, start_itr, end_itr)
def scale_down_text(self, scale_step = 10): def scale_down_text(self, buffer, scale_step = 10):
ctx = self.get_style_context() ctx = self.get_style_context()
if self._px_value > 1: if self._px_value > 1:
@ -47,52 +47,61 @@ class SourceViewEventsMixin(MarkEventsMixin, FileEventsMixin):
ctx.add_class(f"px{self._px_value}") 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. # NOTE: Hope to bring this or similar back after we decouple scaling issues coupled with the miniview.
# tag_table = self._buffer.get_tag_table() # tag_table = buffer.get_tag_table()
# start_itr = self._buffer.get_start_iter() # start_itr = buffer.get_start_iter()
# end_itr = self._buffer.get_end_iter() # end_itr = buffer.get_end_iter()
# tag = tag_table.lookup('general_style') # tag = tag_table.lookup('general_style')
# #
# tag.set_property('scale', tag.get_property('scale') - scale_step) # tag.set_property('scale', tag.get_property('scale') - scale_step)
# self._buffer.apply_tag(tag, start_itr, end_itr) # buffer.apply_tag(tag, start_itr, end_itr)
def update_cursor_position(self): def update_cursor_position(self, buffer = None):
iter = self._buffer.get_iter_at_mark( self._buffer.get_insert() ) buffer = self.get_buffer() if not buffer else buffer
chars = iter.get_offset() iter = buffer.get_iter_at_mark( buffer.get_insert() )
row = iter.get_line() + 1 chars = iter.get_offset()
col = self.get_visual_column(iter) + 1 row = iter.get_line() + 1
col = self.get_visual_column(iter) + 1
event_system.emit("set_line_char_label", (f"{row}:{col}",)) event_system.emit("set_line_char_label", (f"{row}:{col}",))
def got_to_line(self, line: int = 0): def got_to_line(self, buffer = None, line: int = 0):
index = line buffer = self.get_buffer() if not buffer else buffer
buffer = self.get_buffer() line_itr = buffer.get_iter_at_line(line)
line_itr = buffer.get_iter_at_line(index) char_iter = buffer.get_iter_at_line_offset(line, line_itr.get_bytes_in_line())
char_iter = buffer.get_iter_at_line_offset(index, line_itr.get_bytes_in_line())
buffer.place_cursor(char_iter) buffer.place_cursor(char_iter)
if not buffer.get_mark("starting_cursor"): if not buffer.get_mark("starting_cursor"):
buffer.create_mark("starting_cursor", char_iter, True) buffer.create_mark("starting_cursor", char_iter, True)
self.scroll_to_mark( buffer.get_mark("starting_cursor"), 0.0, True, 0.0, 0.0 ) self.scroll_to_mark( buffer.get_mark("starting_cursor"), 0.0, True, 0.0, 0.0 )
# https://github.com/ptomato/inform7-ide/blob/main/src/actions.c
def action_uncomment_selection(self):
...
def action_comment_out_selection(self):
...
def keyboard_undo(self): def keyboard_undo(self):
self._buffer.undo() buffer = self.get_buffer()
buffer.undo()
def keyboard_redo(self): def keyboard_redo(self):
self._buffer.redo() buffer = self.get_buffer()
buffer.redo()
def keyboard_move_lines_up(self): def keyboard_move_lines_up(self):
buffer = self.get_buffer()
self.begin_user_action(buffer)
self.emit("move-lines", *(False,)) self.emit("move-lines", *(False,))
# unindent_lines
# self.emit("move-words", *(self, 4,))
self.end_user_action(buffer)
def keyboard_move_lines_down(self): def keyboard_move_lines_down(self):
buffer = self.get_buffer()
self.begin_user_action(buffer)
self.emit("move-lines", *(True,)) self.emit("move-lines", *(True,))
# self.emit("move-words", *(self, -4,))
self.end_user_action(buffer)
def update_labels(self, gfile = None): def update_labels(self, gfile = None):
if not gfile: return if not gfile: return

View File

@ -82,4 +82,4 @@ class ThemeButton(Gtk.Button):
def _show_popover(self, widget, eve = None): def _show_popover(self, widget, eve = None):
event_system.emit("show_theme_popup") event_system.emit("show_theme_popup")

View File

@ -37,4 +37,4 @@ class MiniViewWidget(Map):
... ...
def set_source_view(self, source_view): def set_source_view(self, source_view):
self.set_view(source_view) self.set_view(source_view)