@@ -9,6 +9,24 @@ use crate::{
9
9
use helix_stdx:: rope:: RopeSliceExt ;
10
10
use std:: borrow:: Cow ;
11
11
12
+ pub const DEFAULT_COMMENT_TOKEN : & str = "//" ;
13
+
14
+ /// Returns the longest matching comment token of the given line (if it exists).
15
+ pub fn get_comment_token < ' a , S : AsRef < str > > (
16
+ text : RopeSlice ,
17
+ tokens : & ' a [ S ] ,
18
+ line_num : usize ,
19
+ ) -> Option < & ' a str > {
20
+ let line = text. line ( line_num) ;
21
+ let start = line. first_non_whitespace_char ( ) ?;
22
+
23
+ tokens
24
+ . iter ( )
25
+ . map ( AsRef :: as_ref)
26
+ . filter ( |token| line. slice ( start..) . starts_with ( token) )
27
+ . max_by_key ( |token| token. len ( ) )
28
+ }
29
+
12
30
/// Given text, a comment token, and a set of line indices, returns the following:
13
31
/// - Whether the given lines should be considered commented
14
32
/// - If any of the lines are uncommented, all lines are considered as such.
@@ -28,21 +46,20 @@ fn find_line_comment(
28
46
let mut min = usize:: MAX ; // minimum col for first_non_whitespace_char
29
47
let mut margin = 1 ;
30
48
let token_len = token. chars ( ) . count ( ) ;
49
+
31
50
for line in lines {
32
51
let line_slice = text. line ( line) ;
33
52
if let Some ( pos) = line_slice. first_non_whitespace_char ( ) {
34
53
let len = line_slice. len_chars ( ) ;
35
54
36
- if pos < min {
37
- min = pos;
38
- }
55
+ min = std:: cmp:: min ( min, pos) ;
39
56
40
57
// line can be shorter than pos + token len
41
58
let fragment = Cow :: from ( line_slice. slice ( pos..std:: cmp:: min ( pos + token. len ( ) , len) ) ) ;
42
59
60
+ // as soon as one of the non-blank lines doesn't have a comment, the whole block is
61
+ // considered uncommented.
43
62
if fragment != token {
44
- // as soon as one of the non-blank lines doesn't have a comment, the whole block is
45
- // considered uncommented.
46
63
commented = false ;
47
64
}
48
65
@@ -56,14 +73,15 @@ fn find_line_comment(
56
73
to_change. push ( line) ;
57
74
}
58
75
}
76
+
59
77
( commented, to_change, min, margin)
60
78
}
61
79
62
80
#[ must_use]
63
81
pub fn toggle_line_comments ( doc : & Rope , selection : & Selection , token : Option < & str > ) -> Transaction {
64
82
let text = doc. slice ( ..) ;
65
83
66
- let token = token. unwrap_or ( "//" ) ;
84
+ let token = token. unwrap_or ( DEFAULT_COMMENT_TOKEN ) ;
67
85
let comment = Tendril :: from ( format ! ( "{} " , token) ) ;
68
86
69
87
let mut lines: Vec < usize > = Vec :: with_capacity ( selection. len ( ) ) ;
@@ -317,56 +335,87 @@ pub fn split_lines_of_selection(text: RopeSlice, selection: &Selection) -> Selec
317
335
mod test {
318
336
use super :: * ;
319
337
320
- #[ test]
321
- fn test_find_line_comment ( ) {
322
- // four lines, two space indented, except for line 1 which is blank.
323
- let mut doc = Rope :: from ( " 1\n \n 2\n 3" ) ;
324
- // select whole document
325
- let mut selection = Selection :: single ( 0 , doc. len_chars ( ) - 1 ) ;
338
+ mod find_line_comment {
339
+ use super :: * ;
326
340
327
- let text = doc. slice ( ..) ;
341
+ #[ test]
342
+ fn not_commented ( ) {
343
+ // four lines, two space indented, except for line 1 which is blank.
344
+ let doc = Rope :: from ( " 1\n \n 2\n 3" ) ;
328
345
329
- let res = find_line_comment ( "//" , text, 0 ..3 ) ;
330
- // (commented = true, to_change = [line 0, line 2], min = col 2, margin = 0)
331
- assert_eq ! ( res, ( false , vec![ 0 , 2 ] , 2 , 0 ) ) ;
346
+ let text = doc. slice ( ..) ;
332
347
333
- // comment
334
- let transaction = toggle_line_comments ( & doc , & selection , None ) ;
335
- transaction . apply ( & mut doc ) ;
336
- selection = selection . map ( transaction . changes ( ) ) ;
348
+ let res = find_line_comment ( "//" , text , 0 .. 3 ) ;
349
+ // (commented = false, to_change = [line 0, line 2], min = col 2, margin = 0)
350
+ assert_eq ! ( res , ( false , vec! [ 0 , 2 ] , 2 , 0 ) ) ;
351
+ }
337
352
338
- assert_eq ! ( doc, " // 1\n \n // 2\n // 3" ) ;
353
+ #[ test]
354
+ fn is_commented ( ) {
355
+ // three lines where the second line is empty.
356
+ let doc = Rope :: from ( "// hello\n \n // there" ) ;
339
357
340
- // uncomment
341
- let transaction = toggle_line_comments ( & doc, & selection, None ) ;
342
- transaction. apply ( & mut doc) ;
343
- selection = selection. map ( transaction. changes ( ) ) ;
344
- assert_eq ! ( doc, " 1\n \n 2\n 3" ) ;
345
- assert ! ( selection. len( ) == 1 ) ; // to ignore the selection unused warning
358
+ let res = find_line_comment ( "//" , doc. slice ( ..) , 0 ..3 ) ;
346
359
347
- // 0 margin comments
348
- doc = Rope :: from ( " //1 \n \n //2 \n //3" ) ;
349
- // reset the selection.
350
- selection = Selection :: single ( 0 , doc . len_chars ( ) - 1 ) ;
360
+ // (commented = true, to_change = [line 0, line 2], min = col 0, margin = 1)
361
+ assert_eq ! ( res , ( true , vec! [ 0 , 2 ] , 0 , 1 ) ) ;
362
+ }
363
+ }
351
364
352
- let transaction = toggle_line_comments ( & doc, & selection, None ) ;
353
- transaction. apply ( & mut doc) ;
354
- selection = selection. map ( transaction. changes ( ) ) ;
355
- assert_eq ! ( doc, " 1\n \n 2\n 3" ) ;
356
- assert ! ( selection. len( ) == 1 ) ; // to ignore the selection unused warning
365
+ // TODO: account for uncommenting with uneven comment indentation
366
+ mod toggle_line_comment {
367
+ use super :: * ;
357
368
358
- // 0 margin comments, with no space
359
- doc = Rope :: from ( "//" ) ;
360
- // reset the selection.
361
- selection = Selection :: single ( 0 , doc. len_chars ( ) - 1 ) ;
369
+ #[ test]
370
+ fn comment ( ) {
371
+ // four lines, two space indented, except for line 1 which is blank.
372
+ let mut doc = Rope :: from ( " 1\n \n 2\n 3" ) ;
373
+ // select whole document
374
+ let selection = Selection :: single ( 0 , doc. len_chars ( ) - 1 ) ;
362
375
363
- let transaction = toggle_line_comments ( & doc, & selection, None ) ;
364
- transaction. apply ( & mut doc) ;
365
- selection = selection . map ( transaction . changes ( ) ) ;
366
- assert_eq ! ( doc, "" ) ;
367
- assert ! ( selection . len ( ) == 1 ) ; // to ignore the selection unused warning
376
+ let transaction = toggle_line_comments ( & doc, & selection, None ) ;
377
+ transaction. apply ( & mut doc) ;
378
+
379
+ assert_eq ! ( doc, " // 1 \n \n // 2 \n // 3 " ) ;
380
+ }
368
381
369
- // TODO: account for uncommenting with uneven comment indentation
382
+ #[ test]
383
+ fn uncomment ( ) {
384
+ let mut doc = Rope :: from ( " // 1\n \n // 2\n // 3" ) ;
385
+ let mut selection = Selection :: single ( 0 , doc. len_chars ( ) - 1 ) ;
386
+
387
+ let transaction = toggle_line_comments ( & doc, & selection, None ) ;
388
+ transaction. apply ( & mut doc) ;
389
+ selection = selection. map ( transaction. changes ( ) ) ;
390
+
391
+ assert_eq ! ( doc, " 1\n \n 2\n 3" ) ;
392
+ assert ! ( selection. len( ) == 1 ) ; // to ignore the selection unused warning
393
+ }
394
+
395
+ #[ test]
396
+ fn uncomment_0_margin_comments ( ) {
397
+ let mut doc = Rope :: from ( " //1\n \n //2\n //3" ) ;
398
+ let mut selection = Selection :: single ( 0 , doc. len_chars ( ) - 1 ) ;
399
+
400
+ let transaction = toggle_line_comments ( & doc, & selection, None ) ;
401
+ transaction. apply ( & mut doc) ;
402
+ selection = selection. map ( transaction. changes ( ) ) ;
403
+
404
+ assert_eq ! ( doc, " 1\n \n 2\n 3" ) ;
405
+ assert ! ( selection. len( ) == 1 ) ; // to ignore the selection unused warning
406
+ }
407
+
408
+ #[ test]
409
+ fn uncomment_0_margin_comments_with_no_space ( ) {
410
+ let mut doc = Rope :: from ( "//" ) ;
411
+ let mut selection = Selection :: single ( 0 , doc. len_chars ( ) - 1 ) ;
412
+
413
+ let transaction = toggle_line_comments ( & doc, & selection, None ) ;
414
+ transaction. apply ( & mut doc) ;
415
+ selection = selection. map ( transaction. changes ( ) ) ;
416
+ assert_eq ! ( doc, "" ) ;
417
+ assert ! ( selection. len( ) == 1 ) ; // to ignore the selection unused warning
418
+ }
370
419
}
371
420
372
421
#[ test]
@@ -413,4 +462,32 @@ mod test {
413
462
transaction. apply ( & mut doc) ;
414
463
assert_eq ! ( doc, "" ) ;
415
464
}
465
+
466
+ /// Test, if `get_comment_tokens` works, even if the content of the file includes chars, whose
467
+ /// byte size unequal the amount of chars
468
+ #[ test]
469
+ fn test_get_comment_with_char_boundaries ( ) {
470
+ let rope = Rope :: from ( "··" ) ;
471
+ let tokens = [ "//" , "///" ] ;
472
+
473
+ assert_eq ! (
474
+ super :: get_comment_token( rope. slice( ..) , tokens. as_slice( ) , 0 ) ,
475
+ None
476
+ ) ;
477
+ }
478
+
479
+ /// Test for `get_comment_token`.
480
+ ///
481
+ /// Assuming the comment tokens are stored as `["///", "//"]`, `get_comment_token` should still
482
+ /// return `///` instead of `//` if the user is in a doc-comment section.
483
+ #[ test]
484
+ fn test_use_longest_comment ( ) {
485
+ let text = Rope :: from ( " /// amogus" ) ;
486
+ let tokens = [ "///" , "//" ] ;
487
+
488
+ assert_eq ! (
489
+ super :: get_comment_token( text. slice( ..) , tokens. as_slice( ) , 0 ) ,
490
+ Some ( "///" )
491
+ ) ;
492
+ }
416
493
}
0 commit comments