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
This commit is contained in:
2026-04-15 23:05:05 -05:00
parent 383db1270e
commit 7ab919f925
3 changed files with 74 additions and 48 deletions

View File

@@ -14,16 +14,24 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
class Handler: class Handler:
@staticmethod @staticmethod
def execute( def execute(view: GtkSource.View, *args, **kwargs):
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Cut to Temp Buffer") logger.debug("Command: Cut to Temp Buffer")
clear_temp_cut_buffer_delayed(view) clear_temp_cut_buffer_delayed(view)
buffer = view.get_buffer() buffer = view.get_buffer()
if buffer.get_has_selection():
start_itr, end_itr = buffer.get_selection_bounds()
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()) itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy() start_itr = itr.copy()
@@ -36,9 +44,14 @@ class Handler:
if not hasattr(view, "_cut_buffer"): if not hasattr(view, "_cut_buffer"):
view._cut_buffer = "" view._cut_buffer = ""
line_str = buffer.get_text(start_itr, end_itr, True) text = buffer.get_text(start_itr, end_itr, True)
view._cut_buffer += line_str
if not text.endswith("\n"):
text += "\n"
view._cut_buffer += text
buffer.delete(start_itr, end_itr) buffer.delete(start_itr, end_itr)
buffer.place_cursor(start_itr)
set_temp_cut_buffer_delayed(view) set_temp_cut_buffer_delayed(view)

View File

@@ -11,45 +11,45 @@ from gi.repository import GtkSource
def execute( def execute(view: GtkSource.View, *args, **kwargs):
view: GtkSource.View,
*args,
**kwargs
):
logger.debug("Command: Duplicate Line") logger.debug("Command: Duplicate Line")
buffer = view.get_buffer() buffer = view.get_buffer()
if not buffer.get_has_selection(): if buffer.get_has_selection():
had_selection = False 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:
itr = buffer.get_iter_at_mark(buffer.get_insert()) itr = buffer.get_iter_at_mark(buffer.get_insert())
start_itr = itr.copy() start_line = end_line = itr.get_line()
end_itr = itr.copy() col = itr.get_line_offset()
start_line = itr.get_line() + 1
start_char = 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
start_itr.backward_visible_line() start_itr = buffer.get_iter_at_line(start_line)
start_itr.forward_line() end_itr = buffer.get_iter_at_line(end_line)
end_itr.forward_line()
end_itr.backward_char()
line_str = buffer.get_slice(start_itr, end_itr, True) if not end_itr.ends_line():
end_itr.forward_to_line_end()
if not end_itr.is_end():
end_itr.forward_char() end_itr.forward_char()
buffer.insert(end_itr, f"{line_str}\n", -1)
if not had_selection: text = buffer.get_text(start_itr, end_itr, True)
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) insert_itr = buffer.get_iter_at_line(end_line)
buffer.place_cursor(new_itr)
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: else:
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) new_start = buffer.get_iter_at_line_offset(end_line + 1, col)
new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char) buffer.place_cursor(new_start)
buffer.select_range(new_itr, new_end_itr)

View File

@@ -61,7 +61,20 @@ class SourceFile(GtkSource.File):
location: Gtk.TextIter, location: Gtk.TextIter,
text: str, length: int 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( def _after_insert_text(
self, self,