From 0c135a707e7955d31e0d714babeac6817a86d6f0 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 5 May 2022 08:33:31 -0500 Subject: [PATCH] prevent selection collapse when inserting a newline Inserting a newline currently collapses any connected selections when inserting or appending. It's happening because we're reducing the selections down to their cursors (`let selection = ..` line) and then computing the new selection based on the cursor. We're discarding the original head and anchor information which are necessary to emulate Kakoune's behavior. In Kakoune, inserting a newline retains the existing selection and _slides_ it (moves head and anchor by the same amount) forward by the newline and indentation amount. Appending a newline extends the selection to include the newline and any new indentation. With the implementation of insert_newline here, we slide by adding the global and local offsets to both head and anchor. We extend by adding the global offset to both head and anchor but the local offset only to the head. --- helix-term/src/commands.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 07d805cad3f2..c2bc04e02d1b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2777,14 +2777,14 @@ pub mod insert { let text = doc.text().slice(..); let contents = doc.text(); - let selection = doc.selection(view.id).clone().cursors(text); + let selection = doc.selection(view.id).clone(); let mut ranges = SmallVec::with_capacity(selection.len()); // TODO: this is annoying, but we need to do it to properly calculate pos after edits - let mut offs = 0; + let mut global_offs = 0; let mut transaction = Transaction::change_by_selection(contents, &selection, |range| { - let pos = range.head; + let pos = range.cursor(text); let prev = if pos == 0 { ' ' @@ -2814,27 +2814,41 @@ pub mod insert { .and_then(|pair| if pair.close == curr { Some(pair) } else { None }) .is_some(); - let new_head_pos = if on_auto_pair { + let local_offs = if on_auto_pair { let inner_indent = indent.clone() + doc.indent_style.as_str(); text.reserve_exact(2 + indent.len() + inner_indent.len()); text.push_str(doc.line_ending.as_str()); text.push_str(&inner_indent); - let new_head_pos = pos + offs + text.chars().count(); + let local_offs = text.chars().count(); text.push_str(doc.line_ending.as_str()); text.push_str(&indent); - new_head_pos + local_offs } else { text.reserve_exact(1 + indent.len()); text.push_str(doc.line_ending.as_str()); text.push_str(&indent); - pos + offs + text.chars().count() + text.chars().count() + }; + + let new_range = if doc.restore_cursor { + // when appending, extend the range by local_offs + Range::new( + range.anchor + global_offs, + range.head + local_offs + global_offs, + ) + } else { + // when inserting, slide the range by local_offs + Range::new( + range.anchor + local_offs + global_offs, + range.head + local_offs + global_offs, + ) }; // TODO: range replace or extend // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos // can be used with cx.mode to do replace or extend on most changes - ranges.push(Range::new(new_head_pos, new_head_pos)); - offs += text.chars().count(); + ranges.push(new_range); + global_offs += text.chars().count(); (pos, pos, Some(text.into())) });