aboutsummaryrefslogtreecommitdiff
path: root/helix-lsp
diff options
context:
space:
mode:
authorAndrii Grynenko2023-02-21 06:04:24 +0000
committerBlaž Hrastnik2023-03-08 01:48:35 +0000
commit0d924255e4ea3d5d5c4be9b11a337f4316550e32 (patch)
tree05e91328468d1352c9c43869a78998e7fe22a105 /helix-lsp
parent1866b43cd355ff6d41d579b4b710a0f602aa79d1 (diff)
Add nested placeholder parsing for LSP snippets
And fix `text` over-parsing, inspired by https://github.com/neovim/neovim/blob/d18f8d5c2d82b209093b2feaa8921a4792b71d59/runtime/lua/vim/lsp/_snippet.lua
Diffstat (limited to 'helix-lsp')
-rw-r--r--helix-lsp/src/snippet.rs70
1 files changed, 49 insertions, 21 deletions
diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs
index 63054cdb..b27077e7 100644
--- a/helix-lsp/src/snippet.rs
+++ b/helix-lsp/src/snippet.rs
@@ -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> {
@@ -440,6 +449,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!(
Ok(Snippet {