Add nested placeholder parsing for LSP snippets

And fix `text` over-parsing, inspired by
d18f8d5c2d/runtime/lua/vim/lsp/_snippet.lua
This commit is contained in:
Andrii Grynenko 2023-02-20 22:04:24 -08:00 committed by Blaž Hrastnik
parent 1866b43cd3
commit 0d924255e4

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use anyhow::{anyhow, Result};
use helix_core::{SmallVec, smallvec};
use helix_core::{smallvec, SmallVec};
#[derive(Debug, PartialEq, Eq)]
pub enum CaseChange {
@ -210,8 +210,8 @@ mod parser {
}
}
fn text<'a>() -> impl Parser<'a, Output = &'a str> {
take_while(|c| c != '$')
fn text<'a, const SIZE: usize>(cs: [char; SIZE]) -> impl Parser<'a, Output = &'a str> {
take_while(move |c| cs.into_iter().all(|c1| c != c1))
}
fn digit<'a>() -> impl Parser<'a, Output = usize> {
@ -270,13 +270,15 @@ mod parser {
),
|seq| { Conditional(seq.1, None, Some(seq.4)) }
),
// Any text
map(text(), Text),
)
}
fn regex<'a>() -> impl Parser<'a, Output = Regex<'a>> {
let replacement = reparse_as(take_until(|c| c == '/'), one_or_more(format()));
let text = map(text(['$', '/']), FormatItem::Text);
let replacement = reparse_as(
take_until(|c| c == '/'),
one_or_more(choice!(format(), text)),
);
map(
seq!(
@ -306,19 +308,20 @@ mod parser {
}
fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
// TODO: why doesn't parse_as work?
// let value = reparse_as(take_until(|c| c == '}'), anything());
// TODO: fix this to parse nested placeholders (take until terminates too early)
let value = filter_map(take_until(|c| c == '}'), |s| {
snippet().parse(s).map(|parse_result| parse_result.1).ok()
});
map(seq!("${", digit(), ":", value, "}"), |seq| {
SnippetElement::Placeholder {
let text = map(text(['$', '}']), SnippetElement::Text);
map(
seq!(
"${",
digit(),
":",
one_or_more(choice!(anything(), text)),
"}"
),
|seq| SnippetElement::Placeholder {
tabstop: seq.1,
value: seq.3.elements,
}
})
value: seq.3,
},
)
}
fn choice<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
@ -366,12 +369,18 @@ mod parser {
}
fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
let text = map(text(), SnippetElement::Text);
choice!(tabstop(), placeholder(), choice(), variable(), text)
// The parser has to be constructed lazily to avoid infinite opaque type recursion
|input: &'a str| {
let parser = choice!(tabstop(), placeholder(), choice(), variable());
parser.parse(input)
}
}
fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
map(one_or_more(anything()), |parts| Snippet { elements: parts })
let text = map(text(['$']), SnippetElement::Text);
map(one_or_more(choice!(anything(), text)), |parts| Snippet {
elements: parts,
})
}
pub fn parse(s: &str) -> Result<Snippet, &str> {
@ -439,6 +448,25 @@ mod parser {
)
}
#[test]
fn parse_placeholder_nested_in_placeholder() {
assert_eq!(
Ok(Snippet {
elements: vec![Placeholder {
tabstop: 1,
value: vec!(
Text("foo "),
Placeholder {
tabstop: 2,
value: vec!(Text("bar")),
},
),
},]
}),
parse("${1:foo ${2:bar}}")
)
}
#[test]
fn parse_all() {
assert_eq!(