@@ -23,8 +23,8 @@ use helix_core::{
23
23
selection, shellwords, surround, textobject,
24
24
tree_sitter:: Node ,
25
25
unicode:: width:: UnicodeWidthChar ,
26
- visual_coords_at_pos, LineEnding , Position , Range , Rope , RopeGraphemes , RopeSlice , Selection ,
27
- SmallVec , Tendril , Transaction ,
26
+ visual_coords_at_pos, Change , LineEnding , Position , Range , Rope , RopeGraphemes , RopeSlice ,
27
+ Selection , SmallVec , Tendril , Transaction ,
28
28
} ;
29
29
use helix_view:: {
30
30
apply_transaction,
@@ -67,6 +67,8 @@ use serde::de::{self, Deserialize, Deserializer};
67
67
use grep_regex:: RegexMatcherBuilder ;
68
68
use grep_searcher:: { sinks, BinaryDetection , SearcherBuilder } ;
69
69
use ignore:: { DirEntry , WalkBuilder , WalkState } ;
70
+ use itertools:: FoldWhile :: { Continue , Done } ;
71
+ use itertools:: Itertools ;
70
72
use tokio_stream:: wrappers:: UnboundedReceiverStream ;
71
73
72
74
pub struct Context < ' a > {
@@ -284,6 +286,8 @@ impl MappableCommand {
284
286
goto_definition, "Goto definition" ,
285
287
add_newline_above, "Add newline above" ,
286
288
add_newline_below, "Add newline below" ,
289
+ move_selection_above, "Move current line or selection up" ,
290
+ move_selection_below, "Move current line or selection down" ,
287
291
goto_type_definition, "Goto type definition" ,
288
292
goto_implementation, "Goto implementation" ,
289
293
goto_file_start, "Goto line number <n> else file start" ,
@@ -4781,6 +4785,8 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
4781
4785
4782
4786
let changes = selection. into_iter ( ) . map ( |range| {
4783
4787
let ( start, end) = range. line_range ( slice) ;
4788
+
4789
+ log:: info!( "Selection: {}, {}" , start, end) ;
4784
4790
let line = match open {
4785
4791
Open :: Above => start,
4786
4792
Open :: Below => end + 1 ,
@@ -4797,6 +4803,168 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
4797
4803
apply_transaction ( & transaction, doc, view) ;
4798
4804
}
4799
4805
4806
+ #[ derive( Debug , PartialEq , Eq ) ]
4807
+ pub enum MoveSelection {
4808
+ Below ,
4809
+ Above ,
4810
+ }
4811
+
4812
+ /// Predict where selection cursor should be after moving the code block up or down.
4813
+ /// This function makes it look like the selection didn't change relative
4814
+ /// to the text that have been moved.
4815
+ fn get_adjusted_selection_pos (
4816
+ doc : & Document ,
4817
+ // text: &Rope,
4818
+ range : Range ,
4819
+ pos : usize ,
4820
+ direction : & MoveSelection ,
4821
+ ) -> usize {
4822
+ let text = doc. text ( ) ;
4823
+ let slice = text. slice ( ..) ;
4824
+ let ( selection_start_line, selection_end_line) = range. line_range ( slice) ;
4825
+ let next_line = match direction {
4826
+ MoveSelection :: Above => selection_start_line. saturating_sub ( 1 ) ,
4827
+ MoveSelection :: Below => selection_end_line + 1 ,
4828
+ } ;
4829
+ if next_line == selection_start_line || next_line >= text. len_lines ( ) {
4830
+ pos
4831
+ } else {
4832
+ let next_line_len = {
4833
+ // This omits the next line (above or below) when counting the future position of head/anchor
4834
+ let line_start = text. line_to_char ( next_line) ;
4835
+ let line_end = line_end_char_index ( & slice, next_line) ;
4836
+ line_end. saturating_sub ( line_start)
4837
+ } ;
4838
+
4839
+ let cursor = coords_at_pos ( slice, pos) ;
4840
+ let pos_line = text. char_to_line ( pos) ;
4841
+ let start_line_pos = text. line_to_char ( pos_line) ;
4842
+ let ending_len = doc. line_ending . len_chars ( ) ;
4843
+ match direction {
4844
+ MoveSelection :: Above => start_line_pos + cursor. col - next_line_len - ending_len,
4845
+ MoveSelection :: Below => start_line_pos + cursor. col + next_line_len + ending_len,
4846
+ }
4847
+ }
4848
+ }
4849
+
4850
+ /// Move line or block of text in specified direction.
4851
+ /// The function respects single line, single selection, multiple lines using
4852
+ /// several cursors and multiple selections.
4853
+ fn move_selection ( cx : & mut Context , direction : MoveSelection ) {
4854
+ let ( view, doc) = current ! ( cx. editor) ;
4855
+ let selection = doc. selection ( view. id ) ;
4856
+ let text = doc. text ( ) ;
4857
+ let slice = text. slice ( ..) ;
4858
+ let all_changes = selection. into_iter ( ) . map ( |range| {
4859
+ let ( start, end) = range. line_range ( slice) ;
4860
+ let line_start = text. line_to_char ( start) ;
4861
+ let line_end = line_end_char_index ( & slice, end) ;
4862
+ let line = text. slice ( line_start..line_end) . to_string ( ) ;
4863
+
4864
+ let next_line = match direction {
4865
+ MoveSelection :: Above => start. saturating_sub ( 1 ) ,
4866
+ MoveSelection :: Below => end + 1 ,
4867
+ } ;
4868
+
4869
+ if next_line == start || next_line >= text. len_lines ( ) {
4870
+ vec ! [ ( line_start, line_end, Some ( line. into( ) ) ) ]
4871
+ } else {
4872
+ let next_line_start = text. line_to_char ( next_line) ;
4873
+ let next_line_end = line_end_char_index ( & slice, next_line) ;
4874
+
4875
+ let next_line_text = text. slice ( next_line_start..next_line_end) . to_string ( ) ;
4876
+
4877
+ match direction {
4878
+ MoveSelection :: Above => vec ! [
4879
+ ( next_line_start, next_line_end, Some ( line. into( ) ) ) ,
4880
+ ( line_start, line_end, Some ( next_line_text. into( ) ) ) ,
4881
+ ] ,
4882
+ MoveSelection :: Below => vec ! [
4883
+ ( line_start, line_end, Some ( next_line_text. into( ) ) ) ,
4884
+ ( next_line_start, next_line_end, Some ( line. into( ) ) ) ,
4885
+ ] ,
4886
+ }
4887
+ }
4888
+ } ) ;
4889
+
4890
+ // Conflicts might arise when two cursors are pointing to adjacent lines.
4891
+ // The resulting change vector would contain two changes referring the same lines,
4892
+ // which would make the transaction to panic.
4893
+ // Conflicts are resolved by picking only the top change in such case.
4894
+ fn remove_conflicts ( changes : Vec < Change > ) -> Vec < Change > {
4895
+ if changes. len ( ) > 2 {
4896
+ changes
4897
+ . into_iter ( )
4898
+ . fold_while ( vec ! [ ] , |mut acc : Vec < Change > , change| {
4899
+ if let Some ( last_change) = acc. pop ( ) {
4900
+ if last_change. 0 >= change. 0 || last_change. 1 >= change. 1 {
4901
+ acc. push ( last_change) ;
4902
+ Done ( acc)
4903
+ } else {
4904
+ acc. push ( last_change) ;
4905
+ acc. push ( change) ;
4906
+ Continue ( acc)
4907
+ }
4908
+ } else {
4909
+ acc. push ( change) ;
4910
+ Continue ( acc)
4911
+ }
4912
+ } )
4913
+ . into_inner ( )
4914
+ } else {
4915
+ changes
4916
+ }
4917
+ }
4918
+ let flat: Vec < Change > = all_changes. into_iter ( ) . flatten ( ) . unique ( ) . collect ( ) ;
4919
+ let filtered = remove_conflicts ( flat) ;
4920
+
4921
+ let new_selection = selection. clone ( ) . transform ( |range| {
4922
+ let anchor_pos = get_adjusted_selection_pos ( doc, range, range. anchor , & direction) ;
4923
+ let head_pos = get_adjusted_selection_pos ( doc, range, range. head , & direction) ;
4924
+
4925
+ Range :: new ( anchor_pos, head_pos)
4926
+ } ) ;
4927
+ let transaction = Transaction :: change ( doc. text ( ) , filtered. into_iter ( ) ) ;
4928
+
4929
+ // Analogically to the conflicting line changes, selections can also panic
4930
+ // in case the ranges would overlap.
4931
+ // Only one selection is returned to prevent that.
4932
+ let selections_collide = || -> bool {
4933
+ let mut last: Option < Range > = None ;
4934
+ for range in new_selection. iter ( ) {
4935
+ let line = range. cursor_line ( slice) ;
4936
+ match last {
4937
+ Some ( last_r) => {
4938
+ let last_line = last_r. cursor_line ( slice) ;
4939
+ if range. overlaps ( & last_r) || last_line + 1 == line || last_line == line {
4940
+ return true ;
4941
+ } else {
4942
+ last = Some ( * range) ;
4943
+ } ;
4944
+ }
4945
+ None => last = Some ( * range) ,
4946
+ } ;
4947
+ }
4948
+ false
4949
+ } ;
4950
+ let cleaned_selection = if new_selection. len ( ) > 1 && selections_collide ( ) {
4951
+ new_selection. into_single ( )
4952
+ } else {
4953
+ new_selection
4954
+ } ;
4955
+
4956
+ apply_transaction ( & transaction, doc, view) ;
4957
+ doc. set_selection ( view. id , cleaned_selection) ;
4958
+ }
4959
+
4960
+ fn move_selection_below ( cx : & mut Context ) {
4961
+ move_selection ( cx, MoveSelection :: Below )
4962
+ }
4963
+
4964
+ fn move_selection_above ( cx : & mut Context ) {
4965
+ move_selection ( cx, MoveSelection :: Above )
4966
+ }
4967
+
4800
4968
/// Increment object under cursor by count.
4801
4969
fn increment ( cx : & mut Context ) {
4802
4970
increment_impl ( cx, cx. count ( ) as i64 ) ;
0 commit comments