@@ -23,8 +23,8 @@ use helix_core::{
2323 selection, shellwords, surround, textobject,
2424 tree_sitter:: Node ,
2525 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 ,
2828} ;
2929use helix_view:: {
3030 apply_transaction,
@@ -67,6 +67,8 @@ use serde::de::{self, Deserialize, Deserializer};
6767use grep_regex:: RegexMatcherBuilder ;
6868use grep_searcher:: { sinks, BinaryDetection , SearcherBuilder } ;
6969use ignore:: { DirEntry , WalkBuilder , WalkState } ;
70+ use itertools:: FoldWhile :: { Continue , Done } ;
71+ use itertools:: Itertools ;
7072use tokio_stream:: wrappers:: UnboundedReceiverStream ;
7173
7274pub struct Context < ' a > {
@@ -284,6 +286,8 @@ impl MappableCommand {
284286 goto_definition, "Goto definition" ,
285287 add_newline_above, "Add newline above" ,
286288 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" ,
287291 goto_type_definition, "Goto type definition" ,
288292 goto_implementation, "Goto implementation" ,
289293 goto_file_start, "Goto line number <n> else file start" ,
@@ -4781,6 +4785,8 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
47814785
47824786 let changes = selection. into_iter ( ) . map ( |range| {
47834787 let ( start, end) = range. line_range ( slice) ;
4788+
4789+ log:: info!( "Selection: {}, {}" , start, end) ;
47844790 let line = match open {
47854791 Open :: Above => start,
47864792 Open :: Below => end + 1 ,
@@ -4797,6 +4803,168 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
47974803 apply_transaction ( & transaction, doc, view) ;
47984804}
47994805
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+
48004968/// Increment object under cursor by count.
48014969fn increment ( cx : & mut Context ) {
48024970 increment_impl ( cx, cx. count ( ) as i64 ) ;
0 commit comments