on master help sign in
prevent selection collapse when inserting a newline (#2414)

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.

commite0b5cdfb477108a5cbd40afd9384a96b50d46fbb authorMichael Davis <mcarsondavis@gmail.com> dateTue 10 May 2022, 19:53 (4 years ago) parent40647f0
1 file changed+23−9
helix-term/src/commands.rs+23−9
helix-term/src/commands.rs+23−9
@@ -2777,14 +2777,14 @@ pub mod insert {
27772777 let text = doc.text().slice(..);
27782778
27792779 let contents = doc.text();
2780− let selection = doc.selection(view.id).clone().cursors(text);
2780+ let selection = doc.selection(view.id).clone();
27812781 let mut ranges = SmallVec::with_capacity(selection.len());
27822782
27832783 // TODO: this is annoying, but we need to do it to properly calculate pos after edits
2784− let mut offs = 0;
2784+ let mut global_offs = 0;
27852785
27862786 let mut transaction = Transaction::change_by_selection(contents, &selection, |range| {
2787− let pos = range.head;
2787+ let pos = range.cursor(text);
27882788
27892789 let prev = if pos == 0 {
27902790 ' '
@@ -2814,27 +2814,41 @@ pub mod insert {
28142814 .and_then(|pair| if pair.close == curr { Some(pair) } else { None })
28152815 .is_some();
28162816
2817− let new_head_pos = if on_auto_pair {
2817+ let local_offs = if on_auto_pair {
28182818 let inner_indent = indent.clone() + doc.indent_style.as_str();
28192819 text.reserve_exact(2 + indent.len() + inner_indent.len());
28202820 text.push_str(doc.line_ending.as_str());
28212821 text.push_str(&inner_indent);
2822− let new_head_pos = pos + offs + text.chars().count();
2822+ let local_offs = text.chars().count();
28232823 text.push_str(doc.line_ending.as_str());
28242824 text.push_str(&indent);
2825− new_head_pos
2825+ local_offs
28262826 } else {
28272827 text.reserve_exact(1 + indent.len());
28282828 text.push_str(doc.line_ending.as_str());
28292829 text.push_str(&indent);
2830− pos + offs + text.chars().count()
2830+ text.chars().count()
2831+ };
2832+
2833+ let new_range = if doc.restore_cursor {
2834+ // when appending, extend the range by local_offs
2835+ Range::new(
2836+ range.anchor + global_offs,
2837+ range.head + local_offs + global_offs,
2838+ )
2839+ } else {
2840+ // when inserting, slide the range by local_offs
2841+ Range::new(
2842+ range.anchor + local_offs + global_offs,
2843+ range.head + local_offs + global_offs,
2844+ )
28312845 };
28322846
28332847 // TODO: range replace or extend
28342848 // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
28352849 // can be used with cx.mode to do replace or extend on most changes
2836− ranges.push(Range::new(new_head_pos, new_head_pos));
2837− offs += text.chars().count();
2850+ ranges.push(new_range);
2851+ global_offs += text.chars().count();
28382852
28392853 (pos, pos, Some(text.into()))
28402854 });