1
1
use std:: fmt:: Display ;
2
2
3
+ use crate :: line_ending:: line_end_char_index;
3
4
use crate :: { movement:: Direction , search, Range , Selection } ;
4
5
use ropey:: RopeSlice ;
5
6
@@ -146,10 +147,8 @@ pub fn find_nth_pairs_pos(
146
147
// we should be searching on.
147
148
return Err ( Error :: CursorOnAmbiguousPair ) ;
148
149
}
149
- (
150
- search:: find_nth_prev ( text, open, pos, n) ,
151
- search:: find_nth_next ( text, close, pos, n) ,
152
- )
150
+ // Pairs with the same open and close char are only detected in the current line
151
+ find_nth_surrounding_char_pair_in_line ( & text, n, open, pos)
153
152
} else {
154
153
(
155
154
find_nth_open_pair ( text, open, close, pos, n) ,
@@ -160,6 +159,80 @@ pub fn find_nth_pairs_pos(
160
159
Option :: zip ( open, close) . ok_or ( Error :: PairNotFound )
161
160
}
162
161
162
+ /// Find the position of surround pairs of `ch` - either ones that surround the cursor, or ones
163
+ /// that appear later in the text. pairs with the same open and close char are only detected in
164
+ /// the current line to avoid ambiguity (e.g. closing a pair of quotes in one line and opening
165
+ /// another pair of quotes in the next line).
166
+ pub fn find_nth_textobject_pairs_pos (
167
+ text : RopeSlice ,
168
+ ch : char ,
169
+ range : Range ,
170
+ n : usize ,
171
+ ) -> Result < ( usize , usize ) > {
172
+ match find_nth_pairs_pos ( text, ch, range, n) {
173
+ Ok ( pair) => Ok ( pair) ,
174
+ Err ( Error :: PairNotFound ) if n == 1 => {
175
+ // No surrounding pair found, we try to find the next pair of `ch` in the text.
176
+ // n > 1 makes no sense in this context. (see test_one_surround_two_next)
177
+ let ( open, close) = get_pair ( ch) ;
178
+ let pos = range. cursor ( text) ;
179
+
180
+ let ( open, close) = if open == close {
181
+ // The cursor is not surrounded by a pair of `ch` in the current line. Search
182
+ // for the nth next pair of `ch` in the current line
183
+ find_nth_next_char_pair_in_line ( & text, n, close, pos)
184
+ } else {
185
+ // The cursor is not surrounded by the pair we are looking for. Search for the
186
+ // nth next pair in the rest of the text
187
+ let nth_next_open = search:: find_nth_next ( text, open, pos, n) ;
188
+ (
189
+ nth_next_open,
190
+ nth_next_open. and_then ( |next_open_pos| {
191
+ find_nth_close_pair ( text, open, close, next_open_pos, n)
192
+ } ) ,
193
+ )
194
+ } ;
195
+
196
+ Option :: zip ( open, close) . ok_or ( Error :: PairNotFound )
197
+ }
198
+ Err ( e) => Err ( e) ,
199
+ }
200
+ }
201
+
202
+ /// Find the position of surround pairs of `ch` in that is after `pos` but still in the same line
203
+ fn find_nth_next_char_pair_in_line (
204
+ text : & RopeSlice ,
205
+ n : usize ,
206
+ ch : char ,
207
+ pos : usize ,
208
+ ) -> ( Option < usize > , Option < usize > ) {
209
+ let line_idx = text. char_to_line ( pos) ;
210
+ let line_end = line_end_char_index ( text, line_idx) ;
211
+ let lookup_slice = text. slice ( pos..line_end) ;
212
+ (
213
+ search:: find_nth_next ( lookup_slice, ch, 0 , n) . map ( |p| p + pos) ,
214
+ search:: find_nth_next ( lookup_slice, ch, 0 , n + 1 ) . map ( |p| p + pos) ,
215
+ )
216
+ }
217
+
218
+ /// Find the position of the nth surround pair of `ch` that surrounds the cursor in the current line
219
+ fn find_nth_surrounding_char_pair_in_line (
220
+ text : & RopeSlice ,
221
+ n : usize ,
222
+ ch : char ,
223
+ pos : usize ,
224
+ ) -> ( Option < usize > , Option < usize > ) {
225
+ let line_idx = text. char_to_line ( pos) ;
226
+ let line_start = text. line_to_char ( line_idx) ;
227
+ let line_end = line_end_char_index ( text, line_idx) ;
228
+ let inline_pos = pos - line_start;
229
+ let lookup_slice = text. slice ( line_start..line_end) ;
230
+ (
231
+ search:: find_nth_prev ( lookup_slice, ch, inline_pos, n) . map ( |p| p + line_start) ,
232
+ search:: find_nth_next ( lookup_slice, ch, inline_pos, n) . map ( |p| p + line_start) ,
233
+ )
234
+ }
235
+
163
236
fn find_nth_open_pair (
164
237
text : RopeSlice ,
165
238
open : char ,
@@ -382,6 +455,117 @@ mod test {
382
455
)
383
456
}
384
457
458
+ #[ test]
459
+ fn test_find_only_same_line_quote ( ) {
460
+ #[ rustfmt:: skip]
461
+ let ( doc, selection, expectations) =
462
+ // We want to find 'value2', not '\nkey2='
463
+ rope_with_selections_and_expectations (
464
+ "key='value'\n key2='value2'" ,
465
+ " \n ^ _ _" ,
466
+ ) ;
467
+
468
+ assert_eq ! (
469
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '\'' , selection. primary( ) , 1 )
470
+ . expect( "find should succeed" ) ,
471
+ ( expectations[ 0 ] , expectations[ 1 ] )
472
+ )
473
+ }
474
+
475
+ #[ test]
476
+ fn test_find_multiline_parentheses_block ( ) {
477
+ #[ rustfmt:: skip]
478
+ let ( doc, selection, expectations) =
479
+ rope_with_selections_and_expectations (
480
+ "some (parentheses \n content) here" ,
481
+ " _ \n ^ _ " ,
482
+ ) ;
483
+
484
+ assert_eq ! (
485
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '(' , selection. primary( ) , 1 )
486
+ . expect( "find should succeed" ) ,
487
+ ( expectations[ 0 ] , expectations[ 1 ] )
488
+ )
489
+ }
490
+
491
+ #[ test]
492
+ fn test_find_pair_after_cursor ( ) {
493
+ #[ rustfmt:: skip]
494
+ let ( doc, selection, expectations) =
495
+ rope_with_selections_and_expectations (
496
+ "some (parentheses content) here" ,
497
+ "^ _ _ " ,
498
+ ) ;
499
+
500
+ assert_eq ! (
501
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '(' , selection. primary( ) , 1 )
502
+ . expect( "find should succeed" ) ,
503
+ ( expectations[ 0 ] , expectations[ 1 ] )
504
+ )
505
+ }
506
+
507
+ #[ test]
508
+ fn test_find_quotes_after_cursor ( ) {
509
+ #[ rustfmt:: skip]
510
+ let ( doc, selection, expectations) =
511
+ rope_with_selections_and_expectations (
512
+ "some 'quoted text'" ,
513
+ "^ _ _" ,
514
+ ) ;
515
+
516
+ assert_eq ! (
517
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '\'' , selection. primary( ) , 1 )
518
+ . expect( "find should succeed" ) ,
519
+ ( expectations[ 0 ] , expectations[ 1 ] )
520
+ )
521
+ }
522
+
523
+ #[ test]
524
+ fn test_do_not_find_next_line_quote ( ) {
525
+ #[ rustfmt:: skip]
526
+ let ( doc, selection, _) =
527
+ rope_with_selections_and_expectations (
528
+ "this line has no quotes\n 'this line does'" ,
529
+ "^ \n " ,
530
+ ) ;
531
+
532
+ assert_eq ! (
533
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '\'' , selection. primary( ) , 1 ) ,
534
+ Err ( Error :: PairNotFound )
535
+ )
536
+ }
537
+
538
+ #[ test]
539
+ fn test_find_next_line_parentheses ( ) {
540
+ #[ rustfmt:: skip]
541
+ let ( doc, selection, expectations) =
542
+ rope_with_selections_and_expectations (
543
+ "this line has no parentheses\n (this line does)" ,
544
+ "^ \n _ _" ,
545
+ ) ;
546
+
547
+ assert_eq ! (
548
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '(' , selection. primary( ) , 1 )
549
+ . expect( "find should succeed" ) ,
550
+ ( expectations[ 0 ] , expectations[ 1 ] )
551
+ )
552
+ }
553
+
554
+ #[ test]
555
+ fn test_one_surround_two_next ( ) {
556
+ #[ rustfmt:: skip]
557
+ let ( doc, selection, _) =
558
+ rope_with_selections_and_expectations (
559
+ "(hello) ((world))" ,
560
+ " ^ " ,
561
+ ) ;
562
+
563
+ assert_eq ! (
564
+ find_nth_textobject_pairs_pos( doc. slice( ..) , '(' , selection. primary( ) , 2 ) ,
565
+ Err ( Error :: PairNotFound )
566
+ )
567
+ }
568
+
385
569
// Create a Rope and a matching Selection using a specification language.
386
570
// ^ is a single-point selection.
387
571
// _ is an expected index. These are returned as a Vec<usize> for use in assertions.
0 commit comments