@@ -417,6 +417,8 @@ impl MappableCommand {
417
417
goto_declaration, "Goto declaration" ,
418
418
add_newline_above, "Add newline above" ,
419
419
add_newline_below, "Add newline below" ,
420
+ move_selection_above, "Move current line selection up" ,
421
+ move_selection_below, "Move current line selection down" ,
420
422
goto_type_definition, "Goto type definition" ,
421
423
goto_implementation, "Goto implementation" ,
422
424
goto_file_start, "Goto line number <n> else file start" ,
@@ -6253,6 +6255,209 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
6253
6255
doc. apply ( & transaction, view. id ) ;
6254
6256
}
6255
6257
6258
+ #[ derive( Debug , PartialEq , Eq ) ]
6259
+ pub enum MoveSelection {
6260
+ Below ,
6261
+ Above ,
6262
+ }
6263
+
6264
+ #[ derive( Clone ) ]
6265
+ struct ExtendedChange {
6266
+ line_start : usize ,
6267
+ line_end : usize ,
6268
+ line_text : Option < Tendril > ,
6269
+ line_selection : Option < ( usize , usize ) > ,
6270
+ }
6271
+ /// Move line or block of text in specified direction.
6272
+ /// The function respects single line, single selection, multiple lines using
6273
+ /// several cursors and multiple selections.
6274
+ fn move_selection ( cx : & mut Context , direction : MoveSelection ) {
6275
+ let ( view, doc) = current ! ( cx. editor) ;
6276
+ let selection = doc. selection ( view. id ) ;
6277
+ let text = doc. text ( ) ;
6278
+ let slice = text. slice ( ..) ;
6279
+ let mut last_step_changes: Vec < ExtendedChange > = vec ! [ ] ;
6280
+ let mut at_doc_edge = false ;
6281
+ let all_changes = selection. into_iter ( ) . map ( |range| {
6282
+ let ( start, end) = range. line_range ( slice) ;
6283
+ let line_start = text. line_to_char ( start) ;
6284
+ let line_end = line_end_char_index ( & slice, end) ;
6285
+ let line_text = text. slice ( line_start..line_end) . to_string ( ) ;
6286
+ let next_line = match direction {
6287
+ MoveSelection :: Above => start. saturating_sub ( 1 ) ,
6288
+ MoveSelection :: Below => end + 1 ,
6289
+ } ;
6290
+ let rel_pos_anchor = range. anchor - line_start;
6291
+ let rel_pos_head = range. head - line_start;
6292
+ let cursor_rel_pos = ( rel_pos_anchor, rel_pos_head) ;
6293
+ if next_line == start || next_line >= text. len_lines ( ) || at_doc_edge {
6294
+ at_doc_edge = true ;
6295
+ let changes = vec ! [ ExtendedChange {
6296
+ line_start,
6297
+ line_end,
6298
+ line_text: Some ( line_text. into( ) ) ,
6299
+ line_selection: Some ( cursor_rel_pos) ,
6300
+ } ] ;
6301
+ last_step_changes = changes. clone ( ) ;
6302
+ changes
6303
+ } else {
6304
+ let next_line_start = text. line_to_char ( next_line) ;
6305
+ let next_line_end = line_end_char_index ( & slice, next_line) ;
6306
+ let next_line_text = text. slice ( next_line_start..next_line_end) . to_string ( ) ;
6307
+ let changes = match direction {
6308
+ MoveSelection :: Above => vec ! [
6309
+ ExtendedChange {
6310
+ line_start: next_line_start,
6311
+ line_end: next_line_end,
6312
+ line_text: Some ( line_text. into( ) ) ,
6313
+ line_selection: Some ( cursor_rel_pos) ,
6314
+ } ,
6315
+ ExtendedChange {
6316
+ line_start,
6317
+ line_end,
6318
+ line_text: Some ( next_line_text. into( ) ) ,
6319
+ line_selection: None ,
6320
+ } ,
6321
+ ] ,
6322
+ MoveSelection :: Below => vec ! [
6323
+ ExtendedChange {
6324
+ line_start,
6325
+ line_end,
6326
+ line_text: Some ( next_line_text. into( ) ) ,
6327
+ line_selection: None ,
6328
+ } ,
6329
+ ExtendedChange {
6330
+ line_start: next_line_start,
6331
+ line_end: next_line_end,
6332
+ line_text: Some ( line_text. into( ) ) ,
6333
+ line_selection: Some ( cursor_rel_pos) ,
6334
+ } ,
6335
+ ] ,
6336
+ } ;
6337
+ let changes = if last_step_changes. len ( ) > 1 {
6338
+ evaluate_changes ( last_step_changes. clone ( ) , changes, & direction)
6339
+ } else {
6340
+ changes
6341
+ } ;
6342
+ last_step_changes = changes. clone ( ) ;
6343
+ changes
6344
+ }
6345
+ } ) ;
6346
+ /// Merge changes from subsequent cursors
6347
+ fn evaluate_changes (
6348
+ mut last_changes : Vec < ExtendedChange > ,
6349
+ current_changes : Vec < ExtendedChange > ,
6350
+ direction : & MoveSelection ,
6351
+ ) -> Vec < ExtendedChange > {
6352
+ let mut current_it = current_changes. into_iter ( ) ;
6353
+ if let ( Some ( mut last) , Some ( mut current_first) , Some ( current_last) ) =
6354
+ ( last_changes. pop ( ) , current_it. next ( ) , current_it. next ( ) )
6355
+ {
6356
+ if last. line_start == current_first. line_start {
6357
+ match direction {
6358
+ MoveSelection :: Above => {
6359
+ last. line_start = current_last. line_start ;
6360
+ last. line_end = current_last. line_end ;
6361
+ if let Some ( first) = last_changes. pop ( ) {
6362
+ last_changes. push ( first)
6363
+ }
6364
+ last_changes. extend ( vec ! [ current_first, last] ) ;
6365
+ last_changes
6366
+ }
6367
+ MoveSelection :: Below => {
6368
+ current_first. line_start = last_changes[ 0 ] . line_start ;
6369
+ current_first. line_end = last_changes[ 0 ] . line_end ;
6370
+ last_changes[ 0 ] = current_first;
6371
+ last_changes. extend ( vec ! [ last, current_last] ) ;
6372
+ last_changes
6373
+ }
6374
+ }
6375
+ } else {
6376
+ if let Some ( first) = last_changes. pop ( ) {
6377
+ last_changes. push ( first)
6378
+ }
6379
+ last_changes. extend ( vec ! [ last, current_first, current_last] ) ;
6380
+ last_changes
6381
+ }
6382
+ } else {
6383
+ last_changes
6384
+ }
6385
+ }
6386
+ let mut flattened: Vec < Vec < ExtendedChange > > = all_changes. into_iter ( ) . collect ( ) ;
6387
+ let last_changes = flattened. pop ( ) . unwrap_or_default ( ) ;
6388
+ let acc_cursors = get_adjusted_selection ( doc, & last_changes, direction, at_doc_edge) ;
6389
+ let changes = last_changes
6390
+ . into_iter ( )
6391
+ . map ( |change| ( change. line_start , change. line_end , change. line_text ) ) ;
6392
+ let new_sel = Selection :: new ( acc_cursors. into ( ) , 0 ) ;
6393
+ let transaction = Transaction :: change ( doc. text ( ) , changes) ;
6394
+ doc. apply ( & transaction, view. id ) ;
6395
+ doc. set_selection ( view. id , new_sel) ;
6396
+ }
6397
+ /// Returns selection range that is valid for the updated document
6398
+ /// This logic is necessary because it's not possible to apply changes
6399
+ /// to the document first and then set selection.
6400
+ fn get_adjusted_selection (
6401
+ doc : & Document ,
6402
+ last_changes : & [ ExtendedChange ] ,
6403
+ direction : MoveSelection ,
6404
+ at_doc_edge : bool ,
6405
+ ) -> Vec < Range > {
6406
+ let mut first_change_len = 0 ;
6407
+ let mut next_start = 0 ;
6408
+ let mut acc_cursors: Vec < Range > = vec ! [ ] ;
6409
+ for change in last_changes. iter ( ) {
6410
+ let change_len = change. line_text . as_ref ( ) . map_or ( 0 , |x| x. chars ( ) . count ( ) ) ;
6411
+ if let Some ( ( rel_anchor, rel_head) ) = change. line_selection {
6412
+ let ( anchor, head) = if at_doc_edge {
6413
+ let anchor = change. line_start + rel_anchor;
6414
+ let head = change. line_start + rel_head;
6415
+ ( anchor, head)
6416
+ } else {
6417
+ match direction {
6418
+ MoveSelection :: Above => {
6419
+ if next_start == 0 {
6420
+ next_start = change. line_start ;
6421
+ }
6422
+ let anchor = next_start + rel_anchor;
6423
+ let head = next_start + rel_head;
6424
+ // If there is next cursor below, selection position should be adjusted
6425
+ // according to the length of the current line.
6426
+ next_start += change_len + doc. line_ending . len_chars ( ) ;
6427
+ ( anchor, head)
6428
+ }
6429
+ MoveSelection :: Below => {
6430
+ let anchor = change. line_start + first_change_len + rel_anchor - change_len;
6431
+ let head = change. line_start + first_change_len + rel_head - change_len;
6432
+ ( anchor, head)
6433
+ }
6434
+ }
6435
+ } ;
6436
+ let cursor = Range :: new ( anchor, head) ;
6437
+ if let Some ( last) = acc_cursors. pop ( ) {
6438
+ if cursor. overlaps ( & last) {
6439
+ acc_cursors. push ( last) ;
6440
+ } else {
6441
+ acc_cursors. push ( last) ;
6442
+ acc_cursors. push ( cursor) ;
6443
+ } ;
6444
+ } else {
6445
+ acc_cursors. push ( cursor) ;
6446
+ } ;
6447
+ } else {
6448
+ first_change_len = change. line_text . as_ref ( ) . map_or ( 0 , |x| x. chars ( ) . count ( ) ) ;
6449
+ next_start = 0 ;
6450
+ } ;
6451
+ }
6452
+ acc_cursors
6453
+ }
6454
+ fn move_selection_below ( cx : & mut Context ) {
6455
+ move_selection ( cx, MoveSelection :: Below )
6456
+ }
6457
+ fn move_selection_above ( cx : & mut Context ) {
6458
+ move_selection ( cx, MoveSelection :: Above )
6459
+ }
6460
+
6256
6461
enum IncrementDirection {
6257
6462
Increase ,
6258
6463
Decrease ,
0 commit comments