on master help sign in
Optimize LSP snippet parsing
e973b71Urgau
Cargo.lock−1
@@ -1141,7 +1141,6 @@ dependencies = [
1141 "helix-loader",
1141 "helix-loader",
1142 "log",
1142 "log",
1143 "lsp-types",
1143 "lsp-types",
1144− "once_cell",
1145 "serde",
1144 "serde",
1146 "serde_json",
1145 "serde_json",
1147 "thiserror",
1146 "thiserror",
helix-lsp/Cargo.toml−1
@@ -27,4 +27,3 @@ thiserror = "1.0"
27tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
27tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
28tokio-stream = "0.1.12"
28tokio-stream = "0.1.12"
29which = "4.4"
29which = "4.4"
30−once_cell = "1.15"
helix-lsp/src/snippet.rs+28−17
@@ -50,14 +50,11 @@ pub struct Snippet<'a> {
50 elements: Vec<SnippetElement<'a>>,
50 elements: Vec<SnippetElement<'a>>,
51}
51}
52
52
53−pub fn parse<'a>(s: &'a str) -> Result<Snippet<'a>> {
53+pub fn parse(s: &str) -> Result<Snippet<'_>> {
54 parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
54 parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
55}
55}
56
56
57mod parser {
57mod parser {
58− use helix_core::regex;
59− use once_cell::sync::Lazy;
60−
61 use helix_parsec::*;
58 use helix_parsec::*;
62
59
63 use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement};
60 use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement};
@@ -86,17 +83,34 @@ mod parser {
86 else ::= text
83 else ::= text
87 */
84 */
88
85
89− static DIGIT: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"^[0-9]+").unwrap());
90− static VARIABLE: Lazy<regex::Regex> =
91− Lazy::new(|| regex::Regex::new(r"^[_a-zA-Z][_a-zA-Z0-9]*").unwrap());
92− static TEXT: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"^[^\$]+").unwrap());
93−
94 fn var<'a>() -> impl Parser<'a, Output = &'a str> {
86 fn var<'a>() -> impl Parser<'a, Output = &'a str> {
95− pattern(&VARIABLE)
87+ // var = [_a-zA-Z][_a-zA-Z0-9]*
88+ move |input: &'a str| match input
89+ .char_indices()
90+ .take_while(|(p, c)| {
91+ *c == '_'
92+ || if *p == 0 {
93+ c.is_ascii_alphabetic()
94+ } else {
95+ c.is_ascii_alphanumeric()
96+ }
97+ })
98+ .last()
99+ {
100+ Some((index, c)) if index >= 1 => {
101+ let index = index + c.len_utf8();
102+ Ok((&input[index..], &input[0..index]))
103+ }
104+ _ => Err(input),
105+ }
106+ }
107+
108+ fn text<'a>() -> impl Parser<'a, Output = &'a str> {
109+ take_while(|c| c != '$')
96 }
110 }
97
111
98 fn digit<'a>() -> impl Parser<'a, Output = usize> {
112 fn digit<'a>() -> impl Parser<'a, Output = usize> {
99− filter_map(pattern(&DIGIT), |s| s.parse().ok())
113+ filter_map(take_while(|c| c.is_ascii_digit()), |s| s.parse().ok())
100 }
114 }
101
115
102 fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> {
116 fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> {
@@ -152,7 +166,7 @@ mod parser {
152 |seq| { Conditional(seq.1, None, Some(seq.4)) }
166 |seq| { Conditional(seq.1, None, Some(seq.4)) }
153 ),
167 ),
154 // Any text
168 // Any text
155− map(pattern(&TEXT), Text),
169+ map(text(), Text),
156 )
170 )
157 }
171 }
158
172
@@ -245,12 +259,9 @@ mod parser {
245 )
259 )
246 }
260 }
247
261
248− fn text<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
249− map(pattern(&TEXT), SnippetElement::Text)
250− }
251−
252 fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
262 fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
263+ let text = map(text(), SnippetElement::Text);
253− choice!(tabstop(), placeholder(), choice(), variable(), text())
264+ choice!(tabstop(), placeholder(), choice(), variable(), text)
254 }
265 }
255
266
256 fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
267 fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
helix-parsec/src/lib.rs+29
@@ -157,6 +157,35 @@ where
157 }
157 }
158}
158}
159
159
160+/// A parser which matches all values until the specified pattern no longer match.
161+///
162+/// This parser only ever fails if the input has a length of zero.
163+///
164+/// # Examples
165+///
166+/// ```
167+/// use helix_parsec::{take_while, Parser};
168+/// let parser = take_while(|c| c == '1');
169+/// assert_eq!(Ok(("2", "11")), parser.parse("112"));
170+/// assert_eq!(Err("22"), parser.parse("22"));
171+/// ```
172+pub fn take_while<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str>
173+where
174+ F: Fn(char) -> bool,
175+{
176+ move |input: &'a str| match input
177+ .char_indices()
178+ .take_while(|(_p, c)| pattern(*c))
179+ .last()
180+ {
181+ Some((index, c)) => {
182+ let index = index + c.len_utf8();
183+ Ok((&input[index..], &input[0..index]))
184+ }
185+ _ => Err(input),
186+ }
187+}
188+
160// Variadic parser combinators
189// Variadic parser combinators
161
190
162/// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion.
191/// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion.