@@ -12,17 +12,17 @@ pub enum CaseChange {
12
12
13
13
#[ derive( Debug , PartialEq , Eq ) ]
14
14
pub enum FormatItem < ' a > {
15
- Text ( & ' a str ) ,
15
+ Text ( Tendril ) ,
16
16
Capture ( usize ) ,
17
17
CaseChange ( usize , CaseChange ) ,
18
18
Conditional ( usize , Option < & ' a str > , Option < & ' a str > ) ,
19
19
}
20
20
21
21
#[ derive( Debug , PartialEq , Eq ) ]
22
22
pub struct Regex < ' a > {
23
- value : & ' a str ,
23
+ value : Tendril ,
24
24
replacement : Vec < FormatItem < ' a > > ,
25
- options : Option < & ' a str > ,
25
+ options : Tendril ,
26
26
}
27
27
28
28
#[ derive( Debug , PartialEq , Eq ) ]
@@ -36,14 +36,14 @@ pub enum SnippetElement<'a> {
36
36
} ,
37
37
Choice {
38
38
tabstop : usize ,
39
- choices : Vec < & ' a str > ,
39
+ choices : Vec < Tendril > ,
40
40
} ,
41
41
Variable {
42
42
name : & ' a str ,
43
43
default : Option < & ' a str > ,
44
44
regex : Option < Regex < ' a > > ,
45
45
} ,
46
- Text ( & ' a str ) ,
46
+ Text ( Tendril ) ,
47
47
}
48
48
49
49
#[ derive( Debug , PartialEq , Eq ) ]
@@ -67,12 +67,12 @@ fn render_elements(
67
67
68
68
for element in snippet_elements {
69
69
match element {
70
- & Text ( text) => {
70
+ Text ( text) => {
71
71
// small optimization to avoid calling replace when it's unnecessary
72
72
let text = if text. contains ( '\n' ) {
73
73
Cow :: Owned ( text. replace ( '\n' , newline_with_offset) )
74
74
} else {
75
- Cow :: Borrowed ( text)
75
+ Cow :: Borrowed ( text. as_str ( ) )
76
76
} ;
77
77
* offset += text. chars ( ) . count ( ) ;
78
78
insert. push_str ( & text) ;
@@ -160,6 +160,7 @@ pub fn render(
160
160
}
161
161
162
162
mod parser {
163
+ use helix_core:: Tendril ;
163
164
use helix_parsec:: * ;
164
165
165
166
use super :: { CaseChange , FormatItem , Regex , Snippet , SnippetElement } ;
@@ -210,8 +211,32 @@ mod parser {
210
211
}
211
212
}
212
213
213
- fn text < ' a , const SIZE : usize > ( cs : [ char ; SIZE ] ) -> impl Parser < ' a , Output = & ' a str > {
214
- take_while ( move |c| cs. into_iter ( ) . all ( |c1| c != c1) )
214
+ const TEXT_ESCAPE_CHARS : & [ char ] = & [ '\\' , '}' , '$' ] ;
215
+ const REPLACE_ESCAPE_CHARS : & [ char ] = & [ '\\' , '}' , '$' , '/' ] ;
216
+ const CHOICE_TEXT_ESCAPE_CHARS : & [ char ] = & [ '\\' , '}' , '$' , '|' , ',' ] ;
217
+
218
+ fn text < ' a > ( escape_chars : & ' static [ char ] ) -> impl Parser < ' a , Output = Tendril > {
219
+ move |input : & ' a str | {
220
+ let mut chars = input. char_indices ( ) ;
221
+ let mut res = Tendril :: new ( ) ;
222
+ while let Some ( ( i, c) ) = chars. next ( ) {
223
+ match c {
224
+ '\\' => {
225
+ if let Some ( ( _, c) ) = chars. next ( ) {
226
+ if escape_chars. contains ( & c) {
227
+ res. push ( c) ;
228
+ continue ;
229
+ }
230
+ }
231
+ return Ok ( ( & input[ i..] , res) ) ;
232
+ }
233
+ c if escape_chars. contains ( & c) => return Ok ( ( & input[ i..] , res) ) ,
234
+ c => res. push ( c) ,
235
+ }
236
+ }
237
+
238
+ Ok ( ( "" , res) )
239
+ }
215
240
}
216
241
217
242
fn digit < ' a > ( ) -> impl Parser < ' a , Output = usize > {
@@ -274,20 +299,18 @@ mod parser {
274
299
}
275
300
276
301
fn regex < ' a > ( ) -> impl Parser < ' a , Output = Regex < ' a > > {
277
- let text = map ( text ( [ '$' , '/' ] ) , FormatItem :: Text ) ;
278
- let replacement = reparse_as (
279
- take_until ( |c| c == '/' ) ,
280
- one_or_more ( choice ! ( format( ) , text) ) ,
281
- ) ;
282
-
283
302
map (
284
303
seq ! (
285
304
"/" ,
286
- take_until( |c| c == '/' ) ,
305
+ // TODO parse as ECMAScript and convert to rust regex
306
+ non_empty( text( & [ '/' , '\\' ] ) ) ,
287
307
"/" ,
288
- replacement,
308
+ one_or_more( choice!(
309
+ format( ) ,
310
+ map( text( REPLACE_ESCAPE_CHARS ) , FormatItem :: Text )
311
+ ) ) ,
289
312
"/" ,
290
- optional ( take_until ( |c| c == '}' ) ) ,
313
+ text ( & [ '}' , '\\' , ] ) ,
291
314
) ,
292
315
|( _, value, _, replacement, _, options) | Regex {
293
316
value,
@@ -308,13 +331,12 @@ mod parser {
308
331
}
309
332
310
333
fn placeholder < ' a > ( ) -> impl Parser < ' a , Output = SnippetElement < ' a > > {
311
- let text = map ( text ( [ '$' , '}' ] ) , SnippetElement :: Text ) ;
312
334
map (
313
335
seq ! (
314
336
"${" ,
315
337
digit( ) ,
316
338
":" ,
317
- one_or_more( choice! ( anything( ) , text ) ) ,
339
+ one_or_more( anything( TEXT_ESCAPE_CHARS ) ) ,
318
340
"}"
319
341
) ,
320
342
|seq| SnippetElement :: Placeholder {
@@ -330,7 +352,7 @@ mod parser {
330
352
"${" ,
331
353
digit( ) ,
332
354
"|" ,
333
- sep( take_until ( |c| c == ',' || c == '|' ) , "," ) ,
355
+ sep( text ( CHOICE_TEXT_ESCAPE_CHARS ) , "," ) ,
334
356
"|}" ,
335
357
) ,
336
358
|seq| SnippetElement :: Choice {
@@ -368,17 +390,21 @@ mod parser {
368
390
)
369
391
}
370
392
371
- fn anything < ' a > ( ) -> impl Parser < ' a , Output = SnippetElement < ' a > > {
372
- // The parser has to be constructed lazily to avoid infinite opaque type recursion
373
- |input : & ' a str | {
374
- let parser = choice ! ( tabstop( ) , placeholder( ) , choice( ) , variable( ) ) ;
393
+ fn anything < ' a > ( escape_chars : & ' static [ char ] ) -> impl Parser < ' a , Output = SnippetElement < ' a > > {
394
+ move |input : & ' a str | {
395
+ let parser = choice ! (
396
+ tabstop( ) ,
397
+ placeholder( ) ,
398
+ choice( ) ,
399
+ variable( ) ,
400
+ map( text( escape_chars) , SnippetElement :: Text )
401
+ ) ;
375
402
parser. parse ( input)
376
403
}
377
404
}
378
405
379
406
fn snippet < ' a > ( ) -> impl Parser < ' a , Output = Snippet < ' a > > {
380
- let text = map ( text ( [ '$' ] ) , SnippetElement :: Text ) ;
381
- map ( one_or_more ( choice ! ( anything( ) , text) ) , |parts| Snippet {
407
+ map ( one_or_more ( anything ( TEXT_ESCAPE_CHARS ) ) , |parts| Snippet {
382
408
elements : parts,
383
409
} )
384
410
}
@@ -392,6 +418,7 @@ mod parser {
392
418
}
393
419
} )
394
420
}
421
+
395
422
#[ cfg( test) ]
396
423
mod test {
397
424
use super :: SnippetElement :: * ;
@@ -407,12 +434,12 @@ mod parser {
407
434
assert_eq ! (
408
435
Ok ( Snippet {
409
436
elements: vec![
410
- Text ( "match(" ) ,
437
+ Text ( "match(" . into ( ) ) ,
411
438
Placeholder {
412
439
tabstop: 1 ,
413
- value: vec!( Text ( "Arg1" ) ) ,
440
+ value: vec!( Text ( "Arg1" . into ( ) ) ) ,
414
441
} ,
415
- Text ( ")" )
442
+ Text ( ")" . into ( ) )
416
443
]
417
444
} ) ,
418
445
parse( "match(${1:Arg1})" )
@@ -446,15 +473,15 @@ mod parser {
446
473
assert_eq ! (
447
474
Ok ( Snippet {
448
475
elements: vec![
449
- Text ( "local " ) ,
476
+ Text ( "local " . into ( ) ) ,
450
477
Placeholder {
451
478
tabstop: 1 ,
452
- value: vec!( Text ( "var" ) ) ,
479
+ value: vec!( Text ( "var" . into ( ) ) ) ,
453
480
} ,
454
- Text ( " = " ) ,
481
+ Text ( " = " . into ( ) ) ,
455
482
Placeholder {
456
483
tabstop: 1 ,
457
- value: vec!( Text ( "value" ) ) ,
484
+ value: vec!( Text ( "value" . into ( ) ) ) ,
458
485
} ,
459
486
]
460
487
} ) ,
@@ -468,7 +495,7 @@ mod parser {
468
495
Ok ( Snippet {
469
496
elements: vec![ Placeholder {
470
497
tabstop: 1 ,
471
- value: vec!( Text ( "var, " ) , Tabstop { tabstop: 2 } , ) ,
498
+ value: vec!( Text ( "var, " . into ( ) ) , Tabstop { tabstop: 2 } , ) ,
472
499
} , ]
473
500
} ) ,
474
501
parse( "${1:var, $2}" )
@@ -482,10 +509,10 @@ mod parser {
482
509
elements: vec![ Placeholder {
483
510
tabstop: 1 ,
484
511
value: vec!(
485
- Text ( "foo " ) ,
512
+ Text ( "foo " . into ( ) ) ,
486
513
Placeholder {
487
514
tabstop: 2 ,
488
- value: vec!( Text ( "bar" ) ) ,
515
+ value: vec!( Text ( "bar" . into ( ) ) ) ,
489
516
} ,
490
517
) ,
491
518
} , ]
@@ -499,27 +526,27 @@ mod parser {
499
526
assert_eq ! (
500
527
Ok ( Snippet {
501
528
elements: vec![
502
- Text ( "hello " ) ,
529
+ Text ( "hello " . into ( ) ) ,
503
530
Tabstop { tabstop: 1 } ,
504
531
Tabstop { tabstop: 2 } ,
505
- Text ( " " ) ,
532
+ Text ( " " . into ( ) ) ,
506
533
Choice {
507
534
tabstop: 1 ,
508
- choices: vec![ "one" , "two" , "three" ]
535
+ choices: vec![ "one" . into ( ) , "two" . into ( ) , "three" . into ( ) ]
509
536
} ,
510
- Text ( " " ) ,
537
+ Text ( " " . into ( ) ) ,
511
538
Variable {
512
539
name: "name" ,
513
540
default : Some ( "foo" ) ,
514
541
regex: None
515
542
} ,
516
- Text ( " " ) ,
543
+ Text ( " " . into ( ) ) ,
517
544
Variable {
518
545
name: "var" ,
519
546
default : None ,
520
547
regex: None
521
548
} ,
522
- Text ( " " ) ,
549
+ Text ( " " . into ( ) ) ,
523
550
Variable {
524
551
name: "TM" ,
525
552
default : None ,
@@ -539,9 +566,9 @@ mod parser {
539
566
name: "TM_FILENAME" ,
540
567
default : None ,
541
568
regex: Some ( Regex {
542
- value: "(.*).+$" ,
569
+ value: "(.*).+$" . into ( ) ,
543
570
replacement: vec![ FormatItem :: Capture ( 1 ) ] ,
544
- options: None ,
571
+ options: Tendril :: new ( ) ,
545
572
} ) ,
546
573
} ]
547
574
} ) ,
0 commit comments