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