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 syntax_node
::{GreenNode, GreenToken, NodeOrToken, SyntaxElement, SyntaxNode}
,
17 TextRange
, TextSize
, T
,
20 pub(crate) fn incremental_reparse(
23 errors
: Vec
<SyntaxError
>,
24 ) -> Option
<(GreenNode
, Vec
<SyntaxError
>, TextRange
)> {
25 if let Some((green
, new_errors
, old_range
)) = reparse_token(node
, edit
) {
26 return Some((green
, merge_errors(errors
, new_errors
, old_range
, edit
), old_range
));
29 if let Some((green
, new_errors
, old_range
)) = reparse_block(node
, edit
) {
30 return Some((green
, merge_errors(errors
, new_errors
, old_range
, edit
), old_range
));
38 ) -> Option
<(GreenNode
, Vec
<SyntaxError
>, TextRange
)> {
39 let prev_token
= root
.covering_element(edit
.delete
).as_token()?
.clone();
40 let prev_token_kind
= prev_token
.kind();
41 match prev_token_kind
{
42 WHITESPACE
| COMMENT
| IDENT
| STRING
=> {
43 if prev_token_kind
== WHITESPACE
|| prev_token_kind
== COMMENT
{
44 // removing a new line may extends previous token
45 let deleted_range
= edit
.delete
- prev_token
.text_range().start();
46 if prev_token
.text()[deleted_range
].contains('
\n'
) {
51 let mut new_text
= get_text_after_edit(prev_token
.clone().into(), edit
);
52 let (new_token_kind
, new_err
) = parser
::LexedStr
::single_token(&new_text
)?
;
54 if new_token_kind
!= prev_token_kind
55 || (new_token_kind
== IDENT
&& is_contextual_kw(&new_text
))
60 // Check that edited token is not a part of the bigger token.
61 // E.g. if for source code `bruh"str"` the user removed `ruh`, then
62 // `b` no longer remains an identifier, but becomes a part of byte string literal
63 if let Some(next_char
) = root
.text().char_at(prev_token
.text_range().end()) {
64 new_text
.push(next_char
);
65 let token_with_next_char
= parser
::LexedStr
::single_token(&new_text
);
66 if let Some((_kind
, _error
)) = token_with_next_char
{
72 let new_token
= GreenToken
::new(rowan
::SyntaxKind(prev_token_kind
.into()), &new_text
);
73 let range
= TextRange
::up_to(TextSize
::of(&new_text
));
75 prev_token
.replace_with(new_token
),
76 new_err
.into_iter().map(|msg
| SyntaxError
::new(msg
, range
)).collect(),
77 prev_token
.text_range(),
87 ) -> Option
<(GreenNode
, Vec
<SyntaxError
>, TextRange
)> {
88 let (node
, reparser
) = find_reparsable_node(root
, edit
.delete
)?
;
89 let text
= get_text_after_edit(node
.clone().into(), edit
);
91 let lexed
= parser
::LexedStr
::new(text
.as_str());
92 let parser_input
= lexed
.to_input();
93 if !is_balanced(&lexed
) {
97 let tree_traversal
= reparser
.parse(&parser_input
);
99 let (green
, new_parser_errors
, _eof
) = build_tree(lexed
, tree_traversal
);
101 Some((node
.replace_with(green
), new_parser_errors
, node
.text_range()))
104 fn get_text_after_edit(element
: SyntaxElement
, edit
: &Indel
) -> String
{
105 let edit
= Indel
::replace(edit
.delete
- element
.text_range().start(), edit
.insert
.clone());
107 let mut text
= match element
{
108 NodeOrToken
::Token(token
) => token
.text().to_string(),
109 NodeOrToken
::Node(node
) => node
.text().to_string(),
111 edit
.apply(&mut text
);
115 fn is_contextual_kw(text
: &str) -> bool
{
116 matches
!(text
, "auto" | "default" | "union")
119 fn find_reparsable_node(node
: &SyntaxNode
, range
: TextRange
) -> Option
<(SyntaxNode
, Reparser
)> {
120 let node
= node
.covering_element(range
);
122 node
.ancestors().find_map(|node
| {
123 let first_child
= node
.first_child_or_token().map(|it
| it
.kind());
124 let parent
= node
.parent().map(|it
| it
.kind());
125 Reparser
::for_node(node
.kind(), first_child
, parent
).map(|r
| (node
, r
))
129 fn is_balanced(lexed
: &parser
::LexedStr
<'_
>) -> bool
{
130 if lexed
.is_empty() || lexed
.kind(0) != T
!['{'] || lexed.kind(lexed.len() - 1) != T!['}'
] {
133 let mut balance
= 0usize
;
134 for i
in 1..lexed
.len() - 1 {
135 match lexed
.kind(i
) {
136 T
!['
{'
] => balance
+= 1,
138 balance
= match balance
.checked_sub(1) {
140 None
=> return false,
150 old_errors
: Vec
<SyntaxError
>,
151 new_errors
: Vec
<SyntaxError
>,
152 range_before_reparse
: TextRange
,
154 ) -> Vec
<SyntaxError
> {
155 let mut res
= Vec
::new();
157 for old_err
in old_errors
{
158 let old_err_range
= old_err
.range();
159 if old_err_range
.end() <= range_before_reparse
.start() {
161 } else if old_err_range
.start() >= range_before_reparse
.end() {
162 let inserted_len
= TextSize
::of(&edit
.insert
);
163 res
.push(old_err
.with_range((old_err_range
+ inserted_len
) - edit
.delete
.len()));
164 // Note: extra parens are intentional to prevent uint underflow, HWAB (here was a bug)
167 res
.extend(new_errors
.into_iter().map(|new_err
| {
168 // fighting borrow checker with a variable ;)
169 let offseted_range
= new_err
.range() + range_before_reparse
.start();
170 new_err
.with_range(offseted_range
)
177 use test_utils
::{assert_eq_text, extract_range}
;
180 use crate::{AstNode, Parse, SourceFile}
;
182 fn do_check(before
: &str, replace_with
: &str, reparsed_len
: u32) {
183 let (range
, before
) = extract_range(before
);
184 let edit
= Indel
::replace(range
, replace_with
.to_owned());
186 let mut after
= before
.clone();
187 edit
.apply(&mut after
);
191 let fully_reparsed
= SourceFile
::parse(&after
);
192 let incrementally_reparsed
: Parse
<SourceFile
> = {
193 let before
= SourceFile
::parse(&before
);
194 let (green
, new_errors
, range
) =
195 incremental_reparse(before
.tree().syntax(), &edit
, before
.errors
.to_vec()).unwrap();
196 assert_eq
!(range
.len(), reparsed_len
.into(), "reparsed fragment has wrong length");
197 Parse
::new(green
, new_errors
)
201 &format
!("{:#?}", fully_reparsed
.tree().syntax()),
202 &format
!("{:#?}", incrementally_reparsed
.tree().syntax()),
204 assert_eq
!(fully_reparsed
.errors(), incrementally_reparsed
.errors());
207 #[test] // FIXME: some test here actually test token reparsing
208 fn reparse_block_tests() {
212 let x = foo + $0bar$0
221 let x = foo$0 + bar$0
245 31, // FIXME: reparse only int literal here
268 impl IntoIterator<Item=i32> for Foo {
275 do_check(r
"use a::b::{foo,$0,bar$0};", "baz", 10);
307 " exit(code: c_int)",
313 fn reparse_token_tests() {
316 fn foo() -> i32 { 1 }
330 fn $0foo$0() -> i32 { 1 }
344 fn foo /* $0$0 */ () {}
373 fn -> &str { "Hello$0$0" }
380 fn -> &str { // "Hello$0$0"
387 fn -> &str { r#"Hello$0$0"#
405 fn reparse_str_token_with_error_unchanged() {
406 do_check(r
#""$0Unclosed$0 string literal"#, "Still unclosed", 24);
410 fn reparse_str_token_with_error_fixed() {
411 do_check(r#""unterinated$0$0"#, "\"", 12);
415 fn reparse_block_with_error_in_middle_unchanged() {
429 fn reparse_block_with_error_in_middle_fixed() {