aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src/parsing/reparsing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax/src/parsing/reparsing.rs')
-rw-r--r--crates/ra_syntax/src/parsing/reparsing.rs370
1 files changed, 370 insertions, 0 deletions
diff --git a/crates/ra_syntax/src/parsing/reparsing.rs b/crates/ra_syntax/src/parsing/reparsing.rs
new file mode 100644
index 000000000..994e7e212
--- /dev/null
+++ b/crates/ra_syntax/src/parsing/reparsing.rs
@@ -0,0 +1,370 @@
1use crate::{
2 SyntaxKind::*, TextRange, TextUnit,
3 algo,
4 syntax_node::{GreenNode, SyntaxError, SyntaxNode},
5 parsing::{
6 grammar,
7 parser_impl,
8 builder::GreenBuilder,
9 parser_api::Parser,
10 lexer::{tokenize, Token},
11 }
12};
13
14use ra_text_edit::AtomTextEdit;
15
16pub(crate) fn incremental_reparse(
17 node: &SyntaxNode,
18 edit: &AtomTextEdit,
19 errors: Vec<SyntaxError>,
20) -> Option<(GreenNode, Vec<SyntaxError>)> {
21 let (node, green, new_errors) =
22 reparse_leaf(node, &edit).or_else(|| reparse_block(node, &edit))?;
23 let green_root = node.replace_with(green);
24 let errors = merge_errors(errors, new_errors, node, edit);
25 Some((green_root, errors))
26}
27
28fn reparse_leaf<'node>(
29 node: &'node SyntaxNode,
30 edit: &AtomTextEdit,
31) -> Option<(&'node SyntaxNode, GreenNode, Vec<SyntaxError>)> {
32 let node = algo::find_covering_node(node, edit.delete);
33 match node.kind() {
34 WHITESPACE | COMMENT | IDENT | STRING | RAW_STRING => {
35 let text = get_text_after_edit(node, &edit);
36 let tokens = tokenize(&text);
37 let token = match tokens[..] {
38 [token] if token.kind == node.kind() => token,
39 _ => return None,
40 };
41
42 if token.kind == IDENT && is_contextual_kw(&text) {
43 return None;
44 }
45
46 let green = GreenNode::new_leaf(node.kind(), text.into());
47 let new_errors = vec![];
48 Some((node, green, new_errors))
49 }
50 _ => None,
51 }
52}
53
54fn reparse_block<'node>(
55 node: &'node SyntaxNode,
56 edit: &AtomTextEdit,
57) -> Option<(&'node SyntaxNode, GreenNode, Vec<SyntaxError>)> {
58 let (node, reparser) = find_reparsable_node(node, edit.delete)?;
59 let text = get_text_after_edit(node, &edit);
60 let tokens = tokenize(&text);
61 if !is_balanced(&tokens) {
62 return None;
63 }
64 let (green, new_errors) =
65 parser_impl::parse_with(GreenBuilder::new(), &text, &tokens, reparser);
66 Some((node, green, new_errors))
67}
68
69fn get_text_after_edit(node: &SyntaxNode, edit: &AtomTextEdit) -> String {
70 let edit = AtomTextEdit::replace(edit.delete - node.range().start(), edit.insert.clone());
71 edit.apply(node.text().to_string())
72}
73
74fn is_contextual_kw(text: &str) -> bool {
75 match text {
76 "auto" | "default" | "union" => true,
77 _ => false,
78 }
79}
80
81type ParseFn = fn(&mut Parser);
82fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(&SyntaxNode, ParseFn)> {
83 let node = algo::find_covering_node(node, range);
84 return node.ancestors().filter_map(|node| reparser(node).map(|r| (node, r))).next();
85
86 fn reparser(node: &SyntaxNode) -> Option<ParseFn> {
87 let res = match node.kind() {
88 BLOCK => grammar::block,
89 NAMED_FIELD_DEF_LIST => grammar::named_field_def_list,
90 NAMED_FIELD_LIST => grammar::named_field_list,
91 ENUM_VARIANT_LIST => grammar::enum_variant_list,
92 MATCH_ARM_LIST => grammar::match_arm_list,
93 USE_TREE_LIST => grammar::use_tree_list,
94 EXTERN_ITEM_LIST => grammar::extern_item_list,
95 TOKEN_TREE if node.first_child().unwrap().kind() == L_CURLY => grammar::token_tree,
96 ITEM_LIST => {
97 let parent = node.parent().unwrap();
98 match parent.kind() {
99 IMPL_BLOCK => grammar::impl_item_list,
100 TRAIT_DEF => grammar::trait_item_list,
101 MODULE => grammar::mod_item_list,
102 _ => return None,
103 }
104 }
105 _ => return None,
106 };
107 Some(res)
108 }
109}
110
111fn is_balanced(tokens: &[Token]) -> bool {
112 if tokens.is_empty()
113 || tokens.first().unwrap().kind != L_CURLY
114 || tokens.last().unwrap().kind != R_CURLY
115 {
116 return false;
117 }
118 let mut balance = 0usize;
119 for t in tokens.iter() {
120 match t.kind {
121 L_CURLY => balance += 1,
122 R_CURLY => {
123 balance = match balance.checked_sub(1) {
124 Some(b) => b,
125 None => return false,
126 }
127 }
128 _ => (),
129 }
130 }
131 balance == 0
132}
133
134fn merge_errors(
135 old_errors: Vec<SyntaxError>,
136 new_errors: Vec<SyntaxError>,
137 old_node: &SyntaxNode,
138 edit: &AtomTextEdit,
139) -> Vec<SyntaxError> {
140 let mut res = Vec::new();
141 for e in old_errors {
142 if e.offset() <= old_node.range().start() {
143 res.push(e)
144 } else if e.offset() >= old_node.range().end() {
145 res.push(e.add_offset(TextUnit::of_str(&edit.insert) - edit.delete.len()));
146 }
147 }
148 for e in new_errors {
149 res.push(e.add_offset(old_node.range().start()));
150 }
151 res
152}
153
154#[cfg(test)]
155mod tests {
156 use test_utils::{extract_range, assert_eq_text};
157
158 use crate::{SourceFile, AstNode, utils::dump_tree};
159 use super::*;
160
161 fn do_check<F>(before: &str, replace_with: &str, reparser: F)
162 where
163 for<'a> F: Fn(
164 &'a SyntaxNode,
165 &AtomTextEdit,
166 ) -> Option<(&'a SyntaxNode, GreenNode, Vec<SyntaxError>)>,
167 {
168 let (range, before) = extract_range(before);
169 let edit = AtomTextEdit::replace(range, replace_with.to_owned());
170 let after = edit.apply(before.clone());
171
172 let fully_reparsed = SourceFile::parse(&after);
173 let incrementally_reparsed = {
174 let f = SourceFile::parse(&before);
175 let edit = AtomTextEdit { delete: range, insert: replace_with.to_string() };
176 let (node, green, new_errors) =
177 reparser(f.syntax(), &edit).expect("cannot incrementally reparse");
178 let green_root = node.replace_with(green);
179 let errors = super::merge_errors(f.errors(), new_errors, node, &edit);
180 SourceFile::new(green_root, errors)
181 };
182
183 assert_eq_text!(
184 &dump_tree(fully_reparsed.syntax()),
185 &dump_tree(incrementally_reparsed.syntax()),
186 )
187 }
188
189 #[test]
190 fn reparse_block_tests() {
191 let do_check = |before, replace_to| do_check(before, replace_to, reparse_block);
192
193 do_check(
194 r"
195fn foo() {
196 let x = foo + <|>bar<|>
197}
198",
199 "baz",
200 );
201 do_check(
202 r"
203fn foo() {
204 let x = foo<|> + bar<|>
205}
206",
207 "baz",
208 );
209 do_check(
210 r"
211struct Foo {
212 f: foo<|><|>
213}
214",
215 ",\n g: (),",
216 );
217 do_check(
218 r"
219fn foo {
220 let;
221 1 + 1;
222 <|>92<|>;
223}
224",
225 "62",
226 );
227 do_check(
228 r"
229mod foo {
230 fn <|><|>
231}
232",
233 "bar",
234 );
235 do_check(
236 r"
237trait Foo {
238 type <|>Foo<|>;
239}
240",
241 "Output",
242 );
243 do_check(
244 r"
245impl IntoIterator<Item=i32> for Foo {
246 f<|><|>
247}
248",
249 "n next(",
250 );
251 do_check(
252 r"
253use a::b::{foo,<|>,bar<|>};
254 ",
255 "baz",
256 );
257 do_check(
258 r"
259pub enum A {
260 Foo<|><|>
261}
262",
263 "\nBar;\n",
264 );
265 do_check(
266 r"
267foo!{a, b<|><|> d}
268",
269 ", c[3]",
270 );
271 do_check(
272 r"
273fn foo() {
274 vec![<|><|>]
275}
276",
277 "123",
278 );
279 do_check(
280 r"
281extern {
282 fn<|>;<|>
283}
284",
285 " exit(code: c_int)",
286 );
287 }
288
289 #[test]
290 fn reparse_leaf_tests() {
291 let do_check = |before, replace_to| do_check(before, replace_to, reparse_leaf);
292
293 do_check(
294 r"<|><|>
295fn foo() -> i32 { 1 }
296",
297 "\n\n\n \n",
298 );
299 do_check(
300 r"
301fn foo() -> <|><|> {}
302",
303 " \n",
304 );
305 do_check(
306 r"
307fn <|>foo<|>() -> i32 { 1 }
308",
309 "bar",
310 );
311 do_check(
312 r"
313fn foo<|><|>foo() { }
314",
315 "bar",
316 );
317 do_check(
318 r"
319fn foo /* <|><|> */ () {}
320",
321 "some comment",
322 );
323 do_check(
324 r"
325fn baz <|><|> () {}
326",
327 " \t\t\n\n",
328 );
329 do_check(
330 r"
331fn baz <|><|> () {}
332",
333 " \t\t\n\n",
334 );
335 do_check(
336 r"
337/// foo <|><|>omment
338mod { }
339",
340 "c",
341 );
342 do_check(
343 r#"
344fn -> &str { "Hello<|><|>" }
345"#,
346 ", world",
347 );
348 do_check(
349 r#"
350fn -> &str { // "Hello<|><|>"
351"#,
352 ", world",
353 );
354 do_check(
355 r##"
356fn -> &str { r#"Hello<|><|>"#
357"##,
358 ", world",
359 );
360 do_check(
361 r"
362#[derive(<|>Copy<|>)]
363enum Foo {
364
365}
366",
367 "Clone",
368 );
369 }
370}