@@ -28,11 +28,10 @@ use helix_core::{
28
28
textobject,
29
29
tree_sitter:: Node ,
30
30
unicode:: width:: UnicodeWidthChar ,
31
- visual_offset_from_block, Deletion , LineEnding , Position , Range , Rope , RopeGraphemes ,
31
+ visual_offset_from_block, Change , Deletion , LineEnding , Position , Range , Rope , RopeGraphemes ,
32
32
RopeSlice , Selection , SmallVec , Tendril , Transaction ,
33
33
} ;
34
34
use helix_view:: {
35
- apply_transaction,
36
35
clipboard:: ClipboardType ,
37
36
document:: { FormatterError , Mode , SCRATCH_BUFFER_NAME } ,
38
37
editor:: { Action , CompleteAction , Motion } ,
@@ -77,8 +76,6 @@ use serde::de::{self, Deserialize, Deserializer};
77
76
use grep_regex:: RegexMatcherBuilder ;
78
77
use grep_searcher:: { sinks, BinaryDetection , SearcherBuilder } ;
79
78
use ignore:: { DirEntry , WalkBuilder , WalkState } ;
80
- use itertools:: FoldWhile :: { Continue , Done } ;
81
- use itertools:: Itertools ;
82
79
use tokio_stream:: wrappers:: UnboundedReceiverStream ;
83
80
84
81
pub type OnKeyCallback = Box < dyn FnOnce ( & mut Context , KeyEvent ) > ;
@@ -5470,43 +5467,7 @@ pub enum MoveSelection {
5470
5467
Above ,
5471
5468
}
5472
5469
5473
- /// Predict where selection cursor should be after moving the code block up or down.
5474
- /// This function makes it look like the selection didn't change relative
5475
- /// to the text that have been moved.
5476
- fn get_adjusted_selection_pos (
5477
- doc : & Document ,
5478
- // text: &Rope,
5479
- range : Range ,
5480
- pos : usize ,
5481
- direction : & MoveSelection ,
5482
- ) -> usize {
5483
- let text = doc. text ( ) ;
5484
- let slice = text. slice ( ..) ;
5485
- let ( selection_start_line, selection_end_line) = range. line_range ( slice) ;
5486
- let next_line = match direction {
5487
- MoveSelection :: Above => selection_start_line. saturating_sub ( 1 ) ,
5488
- MoveSelection :: Below => selection_end_line + 1 ,
5489
- } ;
5490
- if next_line == selection_start_line || next_line >= text. len_lines ( ) {
5491
- pos
5492
- } else {
5493
- let next_line_len = {
5494
- // This omits the next line (above or below) when counting the future position of head/anchor
5495
- let line_start = text. line_to_char ( next_line) ;
5496
- let line_end = line_end_char_index ( & slice, next_line) ;
5497
- line_end. saturating_sub ( line_start)
5498
- } ;
5499
-
5500
- let cursor = coords_at_pos ( slice, pos) ;
5501
- let pos_line = text. char_to_line ( pos) ;
5502
- let start_line_pos = text. line_to_char ( pos_line) ;
5503
- let ending_len = doc. line_ending . len_chars ( ) ;
5504
- match direction {
5505
- MoveSelection :: Above => start_line_pos + cursor. col - next_line_len - ending_len,
5506
- MoveSelection :: Below => start_line_pos + cursor. col + next_line_len + ending_len,
5507
- }
5508
- }
5509
- }
5470
+ type ExtendedChange = ( usize , usize , Option < Tendril > , Option < ( usize , usize ) > ) ;
5510
5471
5511
5472
/// Move line or block of text in specified direction.
5512
5473
/// The function respects single line, single selection, multiple lines using
@@ -5516,6 +5477,8 @@ fn move_selection(cx: &mut Context, direction: MoveSelection) {
5516
5477
let selection = doc. selection ( view. id ) ;
5517
5478
let text = doc. text ( ) ;
5518
5479
let slice = text. slice ( ..) ;
5480
+ let mut last_step_changes: Vec < ExtendedChange > = vec ! [ ] ;
5481
+ let mut at_doc_edge = false ;
5519
5482
let all_changes = selection. into_iter ( ) . map ( |range| {
5520
5483
let ( start, end) = range. line_range ( slice) ;
5521
5484
let line_start = text. line_to_char ( start) ;
@@ -5527,95 +5490,176 @@ fn move_selection(cx: &mut Context, direction: MoveSelection) {
5527
5490
MoveSelection :: Below => end + 1 ,
5528
5491
} ;
5529
5492
5530
- if next_line == start || next_line >= text. len_lines ( ) {
5531
- vec ! [ ( line_start, line_end, Some ( line. into( ) ) ) ]
5493
+ let rel_pos_anchor = range. anchor - line_start;
5494
+ let rel_pos_head = range. head - line_start;
5495
+
5496
+ if next_line == start || next_line >= text. len_lines ( ) || at_doc_edge {
5497
+ at_doc_edge = true ;
5498
+ let cursor_rel_pos = ( rel_pos_anchor, rel_pos_head) ;
5499
+ let changes = vec ! [ (
5500
+ line_start,
5501
+ line_end,
5502
+ Some ( line. into( ) ) ,
5503
+ Some ( cursor_rel_pos) ,
5504
+ ) ] ;
5505
+ last_step_changes = changes. clone ( ) ;
5506
+ changes
5532
5507
} else {
5533
5508
let next_line_start = text. line_to_char ( next_line) ;
5534
5509
let next_line_end = line_end_char_index ( & slice, next_line) ;
5535
-
5536
5510
let next_line_text = text. slice ( next_line_start..next_line_end) . to_string ( ) ;
5537
5511
5538
- match direction {
5512
+ let cursor_rel_pos = ( rel_pos_anchor, rel_pos_head) ;
5513
+ let changes = match direction {
5539
5514
MoveSelection :: Above => vec ! [
5540
- ( next_line_start, next_line_end, Some ( line. into( ) ) ) ,
5541
- ( line_start, line_end, Some ( next_line_text. into( ) ) ) ,
5515
+ (
5516
+ next_line_start,
5517
+ next_line_end,
5518
+ Some ( line. into( ) ) ,
5519
+ Some ( cursor_rel_pos) ,
5520
+ ) ,
5521
+ ( line_start, line_end, Some ( next_line_text. into( ) ) , None ) ,
5542
5522
] ,
5543
5523
MoveSelection :: Below => vec ! [
5544
- ( line_start, line_end, Some ( next_line_text. into( ) ) ) ,
5545
- ( next_line_start, next_line_end, Some ( line. into( ) ) ) ,
5524
+ ( line_start, line_end, Some ( next_line_text. into( ) ) , None ) ,
5525
+ (
5526
+ next_line_start,
5527
+ next_line_end,
5528
+ Some ( line. into( ) ) ,
5529
+ Some ( cursor_rel_pos) ,
5530
+ ) ,
5546
5531
] ,
5547
- }
5532
+ } ;
5533
+
5534
+ let changes = if last_step_changes. len ( ) > 1 {
5535
+ evaluate_changes ( last_step_changes. clone ( ) , changes. clone ( ) , & direction)
5536
+ } else {
5537
+ changes
5538
+ } ;
5539
+ last_step_changes = changes. clone ( ) ;
5540
+ changes
5548
5541
}
5549
5542
} ) ;
5550
5543
5551
- // Conflicts might arise when two cursors are pointing to adjacent lines.
5552
- // The resulting change vector would contain two changes referring the same lines,
5553
- // which would make the transaction to panic.
5554
- // Conflicts are resolved by picking only the top change in such case.
5555
- fn remove_conflicts ( changes : Vec < Change > ) -> Vec < Change > {
5556
- if changes. len ( ) > 2 {
5557
- changes
5558
- . into_iter ( )
5559
- . fold_while ( vec ! [ ] , |mut acc : Vec < Change > , change| {
5560
- if let Some ( last_change) = acc. pop ( ) {
5561
- if last_change. 0 >= change. 0 || last_change. 1 >= change. 1 {
5562
- acc. push ( last_change) ;
5563
- Done ( acc)
5564
- } else {
5565
- acc. push ( last_change) ;
5566
- acc. push ( change) ;
5567
- Continue ( acc)
5544
+ /// Merge changes from subsequent cursors
5545
+ fn evaluate_changes (
5546
+ mut last_changes : Vec < ExtendedChange > ,
5547
+ current_changes : Vec < ExtendedChange > ,
5548
+ direction : & MoveSelection ,
5549
+ ) -> Vec < ExtendedChange > {
5550
+ let mut current_it = current_changes. into_iter ( ) ;
5551
+
5552
+ if let ( Some ( mut last) , Some ( mut current_first) , Some ( current_last) ) =
5553
+ ( last_changes. pop ( ) , current_it. next ( ) , current_it. next ( ) )
5554
+ {
5555
+ if last. 0 == current_first. 0 {
5556
+ match direction {
5557
+ MoveSelection :: Above => {
5558
+ last. 0 = current_last. 0 ;
5559
+ last. 1 = current_last. 1 ;
5560
+ if let Some ( first) = last_changes. pop ( ) {
5561
+ last_changes. push ( first)
5568
5562
}
5569
- } else {
5570
- acc. push ( change) ;
5571
- Continue ( acc)
5563
+ last_changes. extend ( vec ! [ current_first, last. to_owned( ) ] ) ;
5564
+ last_changes
5572
5565
}
5573
- } )
5574
- . into_inner ( )
5566
+ MoveSelection :: Below => {
5567
+ current_first. 0 = last_changes[ 0 ] . 0 ;
5568
+ current_first. 1 = last_changes[ 0 ] . 1 ;
5569
+ last_changes[ 0 ] = current_first;
5570
+ last_changes. extend ( vec ! [ last. to_owned( ) , current_last] ) ;
5571
+ last_changes
5572
+ }
5573
+ }
5574
+ } else {
5575
+ if let Some ( first) = last_changes. pop ( ) {
5576
+ last_changes. push ( first)
5577
+ }
5578
+ last_changes. extend ( vec ! [ last. to_owned( ) , current_first, current_last] ) ;
5579
+ last_changes
5580
+ }
5575
5581
} else {
5576
- changes
5582
+ last_changes
5577
5583
}
5578
5584
}
5579
- let flat: Vec < Change > = all_changes. into_iter ( ) . flatten ( ) . unique ( ) . collect ( ) ;
5580
- let filtered = remove_conflicts ( flat) ;
5581
5585
5582
- let new_selection = selection. clone ( ) . transform ( |range| {
5583
- let anchor_pos = get_adjusted_selection_pos ( doc, range, range. anchor , & direction) ;
5584
- let head_pos = get_adjusted_selection_pos ( doc, range, range. head , & direction) ;
5586
+ let mut flattened: Vec < Vec < ExtendedChange > > = all_changes. into_iter ( ) . collect ( ) ;
5587
+ let last_changes = flattened. pop ( ) . unwrap_or ( vec ! [ ] ) ;
5585
5588
5586
- Range :: new ( anchor_pos, head_pos)
5587
- } ) ;
5588
- let transaction = Transaction :: change ( doc. text ( ) , filtered. into_iter ( ) ) ;
5589
-
5590
- // Analogically to the conflicting line changes, selections can also panic
5591
- // in case the ranges would overlap.
5592
- // Only one selection is returned to prevent that.
5593
- let selections_collide = || -> bool {
5594
- let mut last: Option < Range > = None ;
5595
- for range in new_selection. iter ( ) {
5596
- let line = range. cursor_line ( slice) ;
5597
- match last {
5598
- Some ( last_r) => {
5599
- let last_line = last_r. cursor_line ( slice) ;
5600
- if range. overlaps ( & last_r) || last_line + 1 == line || last_line == line {
5601
- return true ;
5602
- } else {
5603
- last = Some ( * range) ;
5604
- } ;
5589
+ let acc_cursors = get_adjusted_selection ( & doc, & last_changes, direction, at_doc_edge) ;
5590
+
5591
+ let changes: Vec < Change > = last_changes
5592
+ . into_iter ( )
5593
+ . map ( |change| ( change. 0 , change. 1 , change. 2 . to_owned ( ) ) )
5594
+ . collect ( ) ;
5595
+
5596
+ let new_sel = Selection :: new ( acc_cursors. into ( ) , 0 ) ;
5597
+ let transaction = Transaction :: change ( doc. text ( ) , changes. into_iter ( ) ) ;
5598
+
5599
+ doc. apply ( & transaction, view. id ) ;
5600
+ doc. set_selection ( view. id , new_sel) ;
5601
+ }
5602
+
5603
+ /// Returns selection range that is valid for the updated document
5604
+ /// This logic is necessary because it's not possible to apply changes
5605
+ /// to the document first and then set selection.
5606
+ fn get_adjusted_selection (
5607
+ doc : & Document ,
5608
+ last_changes : & Vec < ExtendedChange > ,
5609
+ direction : MoveSelection ,
5610
+ at_doc_edge : bool ,
5611
+ ) -> Vec < Range > {
5612
+ let mut first_change_len = 0 ;
5613
+ let mut next_start = 0 ;
5614
+ let mut acc_cursors: Vec < Range > = vec ! [ ] ;
5615
+
5616
+ for change in last_changes. iter ( ) {
5617
+ let change_len = change. 2 . as_ref ( ) . map_or ( 0 , |x| x. len ( ) ) ;
5618
+
5619
+ if let Some ( ( rel_anchor, rel_head) ) = change. 3 {
5620
+ let ( anchor, head) = if at_doc_edge {
5621
+ let anchor = change. 0 + rel_anchor;
5622
+ let head = change. 0 + rel_head;
5623
+ ( anchor, head)
5624
+ } else {
5625
+ match direction {
5626
+ MoveSelection :: Above => {
5627
+ if next_start == 0 {
5628
+ next_start = change. 0 ;
5629
+ }
5630
+ let anchor = next_start + rel_anchor;
5631
+ let head = next_start + rel_head;
5632
+
5633
+ // If there is next cursor below, selection position should be adjusted
5634
+ // according to the length of the current line.
5635
+ next_start += change_len + doc. line_ending . len_chars ( ) ;
5636
+ ( anchor, head)
5637
+ }
5638
+ MoveSelection :: Below => {
5639
+ let anchor = change. 0 + first_change_len + rel_anchor - change_len;
5640
+ let head = change. 0 + first_change_len + rel_head - change_len;
5641
+ ( anchor, head)
5642
+ }
5605
5643
}
5606
- None => last = Some ( * range) ,
5607
5644
} ;
5608
- }
5609
- false
5610
- } ;
5611
- let cleaned_selection = if new_selection. len ( ) > 1 && selections_collide ( ) {
5612
- new_selection. into_single ( )
5613
- } else {
5614
- new_selection
5615
- } ;
5616
5645
5617
- apply_transaction ( & transaction, doc, view) ;
5618
- doc. set_selection ( view. id , cleaned_selection) ;
5646
+ let cursor = Range :: new ( anchor, head) ;
5647
+ if let Some ( last) = acc_cursors. pop ( ) {
5648
+ if cursor. overlaps ( & last) {
5649
+ acc_cursors. push ( last) ;
5650
+ } else {
5651
+ acc_cursors. push ( last) ;
5652
+ acc_cursors. push ( cursor) ;
5653
+ } ;
5654
+ } else {
5655
+ acc_cursors. push ( cursor) ;
5656
+ } ;
5657
+ } else {
5658
+ first_change_len = change. 2 . as_ref ( ) . map_or ( 0 , |x| x. len ( ) ) ;
5659
+ next_start = 0 ;
5660
+ } ;
5661
+ }
5662
+ acc_cursors
5619
5663
}
5620
5664
5621
5665
fn move_selection_below ( cx : & mut Context ) {
0 commit comments