1 //! Implementation of incremental re-parsing.
3 //! We use two simple strategies for this:
4 //! - if the edit modifies only a single token (like changing an identifier's
5 //! letter), we replace only this token.
6 //! - otherwise, we search for the nearest `{}` block which contains the edit
7 //! and try to parse only this block.
14 lexer::{lex_single_syntax_kind, tokenize, Token},
15 text_tree_sink::TextTreeSink,
18 syntax_node::{GreenNode, GreenToken, NodeOrToken, SyntaxElement, SyntaxNode},
21 TextRange, TextSize, T,
24 pub(crate) fn incremental_reparse(
27 errors: Vec<SyntaxError>,
28 ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
29 if let Some((green, new_errors, old_range)) = reparse_token(node, edit) {
30 return Some((green, merge_errors(errors, new_errors, old_range, edit), old_range));
33 if let Some((green, new_errors, old_range)) = reparse_block(node, edit) {
34 return Some((green, merge_errors(errors, new_errors, old_range, edit), old_range));
42 ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
43 let prev_token = root.covering_element(edit.delete).as_token()?.clone();
44 let prev_token_kind = prev_token.kind();
45 match prev_token_kind {
46 WHITESPACE | COMMENT | IDENT | STRING => {
47 if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT {
48 // removing a new line may extends previous token
49 let deleted_range = edit.delete - prev_token.text_range().start();
50 if prev_token.text()[deleted_range].contains('\n') {
55 let mut new_text = get_text_after_edit(prev_token.clone().into(), edit);
56 let (new_token_kind, new_err) = lex_single_syntax_kind(&new_text)?;
58 if new_token_kind != prev_token_kind
59 || (new_token_kind == IDENT && is_contextual_kw(&new_text))
64 // Check that edited token is not a part of the bigger token.
65 // E.g. if for source code `bruh"str"` the user removed `ruh`, then
66 // `b` no longer remains an identifier, but becomes a part of byte string literal
67 if let Some(next_char) = root.text().char_at(prev_token.text_range().end()) {
68 new_text.push(next_char);
69 let token_with_next_char = lex_single_syntax_kind(&new_text);
70 if let Some((_kind, _error)) = token_with_next_char {
76 let new_token = GreenToken::new(rowan::SyntaxKind(prev_token_kind.into()), &new_text);
78 prev_token.replace_with(new_token),
79 new_err.into_iter().collect(),
80 prev_token.text_range(),
90 ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
91 let (node, reparser) = find_reparsable_node(root, edit.delete)?;
92 let text = get_text_after_edit(node.clone().into(), edit);
94 let (lexer_tokens, new_lexer_errors) = tokenize(&text);
95 if !is_balanced(&lexer_tokens) {
98 let parser_tokens = to_parser_tokens(&text, &lexer_tokens);
100 let mut tree_sink = TextTreeSink::new(&text, &lexer_tokens);
101 reparser.parse(&parser_tokens, &mut tree_sink);
103 let (green, mut new_parser_errors) = tree_sink.finish();
104 new_parser_errors.extend(new_lexer_errors);
106 Some((node.replace_with(green), new_parser_errors, node.text_range()))
109 fn get_text_after_edit(element: SyntaxElement, edit: &Indel) -> String {
110 let edit = Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
112 let mut text = match element {
113 NodeOrToken::Token(token) => token.text().to_string(),
114 NodeOrToken::Node(node) => node.text().to_string(),
116 edit.apply(&mut text);
120 fn is_contextual_kw(text: &str) -> bool {
121 matches!(text, "auto" | "default" | "union")
124 fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(SyntaxNode, Reparser)> {
125 let node = node.covering_element(range);
127 node.ancestors().find_map(|node| {
128 let first_child = node.first_child_or_token().map(|it| it.kind());
129 let parent = node.parent().map(|it| it.kind());
130 Reparser::for_node(node.kind(), first_child, parent).map(|r| (node, r))
134 fn is_balanced(tokens: &[Token]) -> bool {
136 || tokens.first().unwrap().kind != T!['{']
137 || tokens.last().unwrap().kind != T!['}']
141 let mut balance = 0usize;
142 for t in &tokens[1..tokens.len() - 1] {
144 T!['{'] => balance += 1,
146 balance = match balance.checked_sub(1) {
148 None => return false,
158 old_errors: Vec<SyntaxError>,
159 new_errors: Vec<SyntaxError>,
160 range_before_reparse: TextRange,
162 ) -> Vec<SyntaxError> {
163 let mut res = Vec::new();
165 for old_err in old_errors {
166 let old_err_range = old_err.range();
167 if old_err_range.end() <= range_before_reparse.start() {
169 } else if old_err_range.start() >= range_before_reparse.end() {
170 let inserted_len = TextSize::of(&edit.insert);
171 res.push(old_err.with_range((old_err_range + inserted_len) - edit.delete.len()));
172 // Note: extra parens are intentional to prevent uint underflow, HWAB (here was a bug)
175 res.extend(new_errors.into_iter().map(|new_err| {
176 // fighting borrow checker with a variable ;)
177 let offseted_range = new_err.range() + range_before_reparse.start();
178 new_err.with_range(offseted_range)
185 use test_utils::{assert_eq_text, extract_range};
188 use crate::{AstNode, Parse, SourceFile};
190 fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
191 let (range, before) = extract_range(before);
192 let edit = Indel::replace(range, replace_with.to_owned());
194 let mut after = before.clone();
195 edit.apply(&mut after);
199 let fully_reparsed = SourceFile::parse(&after);
200 let incrementally_reparsed: Parse<SourceFile> = {
201 let before = SourceFile::parse(&before);
202 let (green, new_errors, range) =
203 incremental_reparse(before.tree().syntax(), &edit, before.errors.to_vec()).unwrap();
204 assert_eq!(range.len(), reparsed_len.into(), "reparsed fragment has wrong length");
205 Parse::new(green, new_errors)
209 &format!("{:#?}", fully_reparsed.tree().syntax()),
210 &format!("{:#?}", incrementally_reparsed.tree().syntax()),
212 assert_eq!(fully_reparsed.errors(), incrementally_reparsed.errors());
215 #[test] // FIXME: some test here actually test token reparsing
216 fn reparse_block_tests() {
220 let x = foo + $0bar$0
229 let x = foo$0 + bar$0
253 31, // FIXME: reparse only int literal here
276 impl IntoIterator<Item=i32> for Foo {
283 do_check(r"use a::b::{foo,$0,bar$0};", "baz", 10);
315 " exit(code: c_int)",
321 fn reparse_token_tests() {
324 fn foo() -> i32 { 1 }
338 fn $0foo$0() -> i32 { 1 }
352 fn foo /* $0$0 */ () {}
381 fn -> &str { "Hello$0$0" }
388 fn -> &str { // "Hello$0$0"
395 fn -> &str { r#"Hello$0$0"#
413 fn reparse_str_token_with_error_unchanged() {
414 do_check(r#""$0Unclosed$0 string literal"#, "Still unclosed", 24);
418 fn reparse_str_token_with_error_fixed() {
419 do_check(r#""unterinated$0$0"#, "\"", 12);
423 fn reparse_block_with_error_in_middle_unchanged() {
437 fn reparse_block_with_error_in_middle_fixed() {