From 7ab919f925d25cead4356f28599b7dd32cf76f57 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 15 Apr 2026 23:05:05 -0500 Subject: [PATCH] Fix line-based editing behavior for cut and duplicate commands - Cut to temp buffer: - Respect selections by expanding to full line boundaries - Normalize end iterator to include full line + newline - Ensure consistent newline handling for last line - Preserve cursor position after delete - Prevent line merging when accumulating cut buffer - Duplicate line: - Simplify logic using line-based iter APIs - Fix incorrect selection handling and off-by-one issues - Ensure full-line duplication for both selection and cursor cases - Correct cursor/selection restoration after duplication --- .../nanoesq_temp_buffer/cut_to_temp_buffer.py | 41 ++++++++---- .../command_system/commands/duplicate_line.py | 66 +++++++++---------- src/core/widgets/code/source_file.py | 15 ++++- 3 files changed, 74 insertions(+), 48 deletions(-) diff --git a/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py b/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py index 15cf79e..4d7352d 100644 --- a/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py +++ b/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py @@ -14,31 +14,44 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed class Handler: @staticmethod - def execute( - view: GtkSource.View, - *args, - **kwargs - ): + def execute(view: GtkSource.View, *args, **kwargs): logger.debug("Command: Cut to Temp Buffer") clear_temp_cut_buffer_delayed(view) buffer = view.get_buffer() - itr = buffer.get_iter_at_mark(buffer.get_insert()) - start_itr = itr.copy() - start_itr.set_line_offset(0) + if buffer.get_has_selection(): + start_itr, end_itr = buffer.get_selection_bounds() - end_itr = start_itr.copy() - if not end_itr.forward_line(): - end_itr = buffer.get_end_iter() + start_itr.set_line_offset(0) + + if not end_itr.ends_line(): + end_itr.forward_to_line_end() + + if not end_itr.is_end(): + end_itr.forward_char() + else: + itr = buffer.get_iter_at_mark(buffer.get_insert()) + + start_itr = itr.copy() + start_itr.set_line_offset(0) + + end_itr = start_itr.copy() + if not end_itr.forward_line(): + end_itr = buffer.get_end_iter() if not hasattr(view, "_cut_buffer"): view._cut_buffer = "" - line_str = buffer.get_text(start_itr, end_itr, True) - view._cut_buffer += line_str + text = buffer.get_text(start_itr, end_itr, True) + + if not text.endswith("\n"): + text += "\n" + + view._cut_buffer += text buffer.delete(start_itr, end_itr) + buffer.place_cursor(start_itr) - set_temp_cut_buffer_delayed(view) + set_temp_cut_buffer_delayed(view) \ No newline at end of file diff --git a/src/core/widgets/code/command_system/commands/duplicate_line.py b/src/core/widgets/code/command_system/commands/duplicate_line.py index 59fc3a6..43230d1 100644 --- a/src/core/widgets/code/command_system/commands/duplicate_line.py +++ b/src/core/widgets/code/command_system/commands/duplicate_line.py @@ -11,45 +11,45 @@ from gi.repository import GtkSource -def execute( - view: GtkSource.View, - *args, - **kwargs -): +def execute(view: GtkSource.View, *args, **kwargs): logger.debug("Command: Duplicate Line") buffer = view.get_buffer() - if not buffer.get_has_selection(): - had_selection = False - itr = buffer.get_iter_at_mark( buffer.get_insert() ) - start_itr = itr.copy() - end_itr = itr.copy() - start_line = itr.get_line() + 1 - start_char = itr.get_line_offset() + if buffer.get_has_selection(): + start_itr, \ + end_itr = buffer.get_selection_bounds() + start_line = start_itr.get_line() + end_line = end_itr.get_line() + scol = start_itr.get_line_offset() + ecol = end_itr.get_line_offset() else: - had_selection = True - start_itr, end_itr = buffer.get_selection_bounds() - sline = start_itr.get_line() - eline = end_itr.get_line() - start_line = eline + 1 - start_char = start_itr.get_line_offset() - end_char = end_itr.get_line_offset() - range_line_size = eline - sline + itr = buffer.get_iter_at_mark(buffer.get_insert()) + start_line = end_line = itr.get_line() + col = itr.get_line_offset() - start_itr.backward_visible_line() - start_itr.forward_line() - end_itr.forward_line() - end_itr.backward_char() + start_itr = buffer.get_iter_at_line(start_line) + end_itr = buffer.get_iter_at_line(end_line) - line_str = buffer.get_slice(start_itr, end_itr, True) - end_itr.forward_char() - buffer.insert(end_itr, f"{line_str}\n", -1) + if not end_itr.ends_line(): + end_itr.forward_to_line_end() - if not had_selection: - new_itr = buffer.get_iter_at_line_offset(start_line, start_char) - buffer.place_cursor(new_itr) + if not end_itr.is_end(): + end_itr.forward_char() + + text = buffer.get_text(start_itr, end_itr, True) + insert_itr = buffer.get_iter_at_line(end_line) + + insert_itr.forward_to_line_end() + if not insert_itr.is_end(): + insert_itr.forward_char() + + buffer.insert(insert_itr, text) + + if buffer.get_has_selection(): + new_start = buffer.get_iter_at_line_offset(end_line + 1, scol) + new_end = buffer.get_iter_at_line_offset(end_line + 1 + (end_line - start_line), ecol) + buffer.select_range(new_start, new_end) else: - new_itr = buffer.get_iter_at_line_offset(start_line, start_char) - new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char) - buffer.select_range(new_itr, new_end_itr) + new_start = buffer.get_iter_at_line_offset(end_line + 1, col) + buffer.place_cursor(new_start) diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py index 4dd129e..f80f3a8 100644 --- a/src/core/widgets/code/source_file.py +++ b/src/core/widgets/code/source_file.py @@ -61,7 +61,20 @@ class SourceFile(GtkSource.File): location: Gtk.TextIter, text: str, length: int ): - ... + event = Event_Factory.create_event( + "text_insert", + file = self, + buffer = self.buffer, + location = location, + text = text, + length = length + ) + + # Note: 'idle_add' needed b/c markers don't get thir positions + # updated relative to the initial insert. + # If not used, seg faults galor during multi insert. + # GLib.idle_add(self.emit, event) + self.emit(event) def _after_insert_text( self,