on master help sign in
revamped snippet text element parsing
90348b8Pascal Kuthe
helix-lsp/src/snippet.rs+72−45
@@ -12,7 +12,7 @@ pub enum CaseChange {
12
12
13#[derive(Debug, PartialEq, Eq)]
13#[derive(Debug, PartialEq, Eq)]
14pub enum FormatItem<'a> {
14pub enum FormatItem<'a> {
15− Text(&'a str),
15+ Text(Tendril),
16 Capture(usize),
16 Capture(usize),
17 CaseChange(usize, CaseChange),
17 CaseChange(usize, CaseChange),
18 Conditional(usize, Option<&'a str>, Option<&'a str>),
18 Conditional(usize, Option<&'a str>, Option<&'a str>),
@@ -20,9 +20,9 @@ pub enum FormatItem<'a> {
20
20
21#[derive(Debug, PartialEq, Eq)]
21#[derive(Debug, PartialEq, Eq)]
22pub struct Regex<'a> {
22pub struct Regex<'a> {
23− value: &'a str,
23+ value: Tendril,
24 replacement: Vec<FormatItem<'a>>,
24 replacement: Vec<FormatItem<'a>>,
25− options: Option<&'a str>,
25+ options: Tendril,
26}
26}
27
27
28#[derive(Debug, PartialEq, Eq)]
28#[derive(Debug, PartialEq, Eq)]
@@ -36,14 +36,14 @@ pub enum SnippetElement<'a> {
36 },
36 },
37 Choice {
37 Choice {
38 tabstop: usize,
38 tabstop: usize,
39− choices: Vec<&'a str>,
39+ choices: Vec<Tendril>,
40 },
40 },
41 Variable {
41 Variable {
42 name: &'a str,
42 name: &'a str,
43 default: Option<&'a str>,
43 default: Option<&'a str>,
44 regex: Option<Regex<'a>>,
44 regex: Option<Regex<'a>>,
45 },
45 },
46− Text(&'a str),
46+ Text(Tendril),
47}
47}
48
48
49#[derive(Debug, PartialEq, Eq)]
49#[derive(Debug, PartialEq, Eq)]
@@ -67,12 +67,12 @@ fn render_elements(
67
67
68 for element in snippet_elements {
68 for element in snippet_elements {
69 match element {
69 match element {
70− &Text(text) => {
70+ Text(text) => {
71 // small optimization to avoid calling replace when it's unnecessary
71 // small optimization to avoid calling replace when it's unnecessary
72 let text = if text.contains('\n') {
72 let text = if text.contains('\n') {
73 Cow::Owned(text.replace('\n', newline_with_offset))
73 Cow::Owned(text.replace('\n', newline_with_offset))
74 } else {
74 } else {
75− Cow::Borrowed(text)
75+ Cow::Borrowed(text.as_str())
76 };
76 };
77 *offset += text.chars().count();
77 *offset += text.chars().count();
78 insert.push_str(&text);
78 insert.push_str(&text);
@@ -160,6 +160,7 @@ pub fn render(
160}
160}
161
161
162mod parser {
162mod parser {
163+ use helix_core::Tendril;
163 use helix_parsec::*;
164 use helix_parsec::*;
164
165
165 use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement};
166 use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement};
@@ -210,8 +211,32 @@ mod parser {
210 }
211 }
211 }
212 }
212
213
214+ const TEXT_ESCAPE_CHARS: &[char] = &['\\', '}', '$'];
215+ const REPLACE_ESCAPE_CHARS: &[char] = &['\\', '}', '$', '/'];
216+ const CHOICE_TEXT_ESCAPE_CHARS: &[char] = &['\\', '}', '$', '|', ','];
217+
213− fn text<'a, const SIZE: usize>(cs: [char; SIZE]) -> impl Parser<'a, Output = &'a str> {
218+ fn text<'a>(escape_chars: &'static [char]) -> impl Parser<'a, Output = Tendril> {
214− take_while(move |c| cs.into_iter().all(|c1| c != c1))
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 fn digit<'a>() -> impl Parser<'a, Output = usize> {
242 fn digit<'a>() -> impl Parser<'a, Output = usize> {
@@ -274,20 +299,18 @@ mod parser {
274 }
299 }
275
300
276 fn regex<'a>() -> impl Parser<'a, Output = Regex<'a>> {
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 map(
302 map(
284 seq!(
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 |(_, value, _, replacement, _, options)| Regex {
315 |(_, value, _, replacement, _, options)| Regex {
293 value,
316 value,
@@ -308,13 +331,12 @@ mod parser {
308 }
331 }
309
332
310 fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
333 fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
311− let text = map(text(['$', '}']), SnippetElement::Text);
312 map(
334 map(
313 seq!(
335 seq!(
314 "${",
336 "${",
315 digit(),
337 digit(),
316 ":",
338 ":",
317− one_or_more(choice!(anything(), text)),
339+ one_or_more(anything(TEXT_ESCAPE_CHARS)),
318 "}"
340 "}"
319 ),
341 ),
320 |seq| SnippetElement::Placeholder {
342 |seq| SnippetElement::Placeholder {
@@ -330,7 +352,7 @@ mod parser {
330 "${",
352 "${",
331 digit(),
353 digit(),
332 "|",
354 "|",
333− sep(take_until(|c| c == ',' || c == '|'), ","),
355+ sep(text(CHOICE_TEXT_ESCAPE_CHARS), ","),
334 "|}",
356 "|}",
335 ),
357 ),
336 |seq| SnippetElement::Choice {
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>> {
393+ fn anything<'a>(escape_chars: &'static [char]) -> impl Parser<'a, Output = SnippetElement<'a>> {
372− // The parser has to be constructed lazily to avoid infinite opaque type recursion
373− |input: &'a str| {
394+ move |input: &'a str| {
374− let parser = choice!(tabstop(), placeholder(), choice(), variable());
395+ let parser = choice!(
396+ tabstop(),
397+ placeholder(),
398+ choice(),
399+ variable(),
400+ map(text(escape_chars), SnippetElement::Text)
401+ );
375 parser.parse(input)
402 parser.parse(input)
376 }
403 }
377 }
404 }
378
405
379 fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
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 elements: parts,
408 elements: parts,
383 })
409 })
384 }
410 }
@@ -392,6 +418,7 @@ mod parser {
392 }
418 }
393 })
419 })
394 }
420 }
421+
395 #[cfg(test)]
422 #[cfg(test)]
396 mod test {
423 mod test {
397 use super::SnippetElement::*;
424 use super::SnippetElement::*;
@@ -407,12 +434,12 @@ mod parser {
407 assert_eq!(
434 assert_eq!(
408 Ok(Snippet {
435 Ok(Snippet {
409 elements: vec![
436 elements: vec![
410− Text("match("),
437+ Text("match(".into()),
411 Placeholder {
438 Placeholder {
412 tabstop: 1,
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 parse("match(${1:Arg1})")
445 parse("match(${1:Arg1})")
@@ -446,15 +473,15 @@ mod parser {
446 assert_eq!(
473 assert_eq!(
447 Ok(Snippet {
474 Ok(Snippet {
448 elements: vec![
475 elements: vec![
449− Text("local "),
476+ Text("local ".into()),
450 Placeholder {
477 Placeholder {
451 tabstop: 1,
478 tabstop: 1,
452− value: vec!(Text("var")),
479+ value: vec!(Text("var".into())),
453 },
480 },
454− Text(" = "),
481+ Text(" = ".into()),
455 Placeholder {
482 Placeholder {
456 tabstop: 1,
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 Ok(Snippet {
495 Ok(Snippet {
469 elements: vec![Placeholder {
496 elements: vec![Placeholder {
470 tabstop: 1,
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 parse("${1:var, $2}")
501 parse("${1:var, $2}")
@@ -482,10 +509,10 @@ mod parser {
482 elements: vec![Placeholder {
509 elements: vec![Placeholder {
483 tabstop: 1,
510 tabstop: 1,
484 value: vec!(
511 value: vec!(
485− Text("foo "),
512+ Text("foo ".into()),
486 Placeholder {
513 Placeholder {
487 tabstop: 2,
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 assert_eq!(
526 assert_eq!(
500 Ok(Snippet {
527 Ok(Snippet {
501 elements: vec![
528 elements: vec![
502− Text("hello "),
529+ Text("hello ".into()),
503 Tabstop { tabstop: 1 },
530 Tabstop { tabstop: 1 },
504 Tabstop { tabstop: 2 },
531 Tabstop { tabstop: 2 },
505− Text(" "),
532+ Text(" ".into()),
506 Choice {
533 Choice {
507 tabstop: 1,
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 Variable {
538 Variable {
512 name: "name",
539 name: "name",
513 default: Some("foo"),
540 default: Some("foo"),
514 regex: None
541 regex: None
515 },
542 },
516− Text(" "),
543+ Text(" ".into()),
517 Variable {
544 Variable {
518 name: "var",
545 name: "var",
519 default: None,
546 default: None,
520 regex: None
547 regex: None
521 },
548 },
522− Text(" "),
549+ Text(" ".into()),
523 Variable {
550 Variable {
524 name: "TM",
551 name: "TM",
525 default: None,
552 default: None,
@@ -539,9 +566,9 @@ mod parser {
539 name: "TM_FILENAME",
566 name: "TM_FILENAME",
540 default: None,
567 default: None,
541 regex: Some(Regex {
568 regex: Some(Regex {
542− value: "(.*).+$",
569+ value: "(.*).+$".into(),
543 replacement: vec![FormatItem::Capture(1)],
570 replacement: vec![FormatItem::Capture(1)],
544− options: None,
571+ options: Tendril::new(),
545 }),
572 }),
546 }]
573 }]
547 }),
574 }),
helix-parsec/src/lib.rs+13
@@ -459,6 +459,7 @@ pub fn zero_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
459where
459where
460 P: Parser<'a, Output = T>,
460 P: Parser<'a, Output = T>,
461{
461{
462+ let parser = non_empty(parser);
462 move |mut input| {
463 move |mut input| {
463 let mut values = Vec::new();
464 let mut values = Vec::new();
464
465
@@ -491,6 +492,7 @@ pub fn one_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
491where
492where
492 P: Parser<'a, Output = T>,
493 P: Parser<'a, Output = T>,
493{
494{
495+ let parser = non_empty(parser);
494 move |mut input| {
496 move |mut input| {
495 let mut values = Vec::new();
497 let mut values = Vec::new();
496
498
@@ -559,3 +561,14 @@ where
559 Ok((input, values))
561 Ok((input, values))
560 }
562 }
561}
563}
564+
565+pub fn non_empty<'a, T>(p: impl Parser<'a, Output = T>) -> impl Parser<'a, Output = T> {
566+ move |input| {
567+ let (new_input, res) = p.parse(input)?;
568+ if new_input.len() == input.len() {
569+ Err(input)
570+ } else {
571+ Ok((new_input, res))
572+ }
573+ }
574+}