diff options
Diffstat (limited to 'crates/ra_syntax/src/parsing/reparsing.rs')
-rw-r--r-- | crates/ra_syntax/src/parsing/reparsing.rs | 52 |
1 files changed, 33 insertions, 19 deletions
diff --git a/crates/ra_syntax/src/parsing/reparsing.rs b/crates/ra_syntax/src/parsing/reparsing.rs index 3abc09877..ad1a7c855 100644 --- a/crates/ra_syntax/src/parsing/reparsing.rs +++ b/crates/ra_syntax/src/parsing/reparsing.rs | |||
@@ -12,7 +12,7 @@ use ra_text_edit::AtomTextEdit; | |||
12 | use crate::{ | 12 | use crate::{ |
13 | algo, | 13 | algo, |
14 | parsing::{ | 14 | parsing::{ |
15 | lexer::{tokenize, Token}, | 15 | lexer::{single_token, tokenize, ParsedTokens, Token}, |
16 | text_token_source::TextTokenSource, | 16 | text_token_source::TextTokenSource, |
17 | text_tree_sink::TextTreeSink, | 17 | text_tree_sink::TextTreeSink, |
18 | }, | 18 | }, |
@@ -41,36 +41,42 @@ fn reparse_token<'node>( | |||
41 | root: &'node SyntaxNode, | 41 | root: &'node SyntaxNode, |
42 | edit: &AtomTextEdit, | 42 | edit: &AtomTextEdit, |
43 | ) -> Option<(GreenNode, TextRange)> { | 43 | ) -> Option<(GreenNode, TextRange)> { |
44 | let token = algo::find_covering_element(root, edit.delete).as_token()?.clone(); | 44 | let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone(); |
45 | match token.kind() { | 45 | let prev_token_kind = prev_token.kind(); |
46 | match prev_token_kind { | ||
46 | WHITESPACE | COMMENT | IDENT | STRING | RAW_STRING => { | 47 | WHITESPACE | COMMENT | IDENT | STRING | RAW_STRING => { |
47 | if token.kind() == WHITESPACE || token.kind() == COMMENT { | 48 | if prev_token_kind == WHITESPACE || prev_token_kind == COMMENT { |
48 | // removing a new line may extends previous token | 49 | // removing a new line may extends previous token |
49 | if token.text()[edit.delete - token.text_range().start()].contains('\n') { | 50 | let deleted_range = edit.delete - prev_token.text_range().start(); |
51 | if prev_token.text()[deleted_range].contains('\n') { | ||
50 | return None; | 52 | return None; |
51 | } | 53 | } |
52 | } | 54 | } |
53 | 55 | ||
54 | let text = get_text_after_edit(token.clone().into(), &edit); | 56 | let mut new_text = get_text_after_edit(prev_token.clone().into(), &edit); |
55 | let lex_tokens = tokenize(&text); | 57 | let new_token_kind = single_token(&new_text)?.token.kind; |
56 | let lex_token = match lex_tokens[..] { | ||
57 | [lex_token] if lex_token.kind == token.kind() => lex_token, | ||
58 | _ => return None, | ||
59 | }; | ||
60 | 58 | ||
61 | if lex_token.kind == IDENT && is_contextual_kw(&text) { | 59 | if new_token_kind != prev_token_kind |
60 | || (new_token_kind == IDENT && is_contextual_kw(&new_text)) | ||
61 | { | ||
62 | return None; | 62 | return None; |
63 | } | 63 | } |
64 | 64 | ||
65 | if let Some(next_char) = root.text().char_at(token.text_range().end()) { | 65 | // Check that edited token is not a part of the bigger token. |
66 | let tokens_with_next_char = tokenize(&format!("{}{}", text, next_char)); | 66 | // E.g. if for source code `bruh"str"` the user removed `ruh`, then |
67 | if tokens_with_next_char.len() == 1 { | 67 | // `b` no longer remains an identifier, but becomes a part of byte string literal |
68 | if let Some(next_char) = root.text().char_at(prev_token.text_range().end()) { | ||
69 | new_text.push(next_char); | ||
70 | let token_with_next_char = single_token(&new_text); | ||
71 | if token_with_next_char.is_some() { | ||
68 | return None; | 72 | return None; |
69 | } | 73 | } |
74 | new_text.pop(); | ||
70 | } | 75 | } |
71 | 76 | ||
72 | let new_token = GreenToken::new(rowan::SyntaxKind(token.kind().into()), text.into()); | 77 | let new_token = |
73 | Some((token.replace_with(new_token), token.text_range())) | 78 | GreenToken::new(rowan::SyntaxKind(prev_token_kind.into()), new_text.into()); |
79 | Some((prev_token.replace_with(new_token), prev_token.text_range())) | ||
74 | } | 80 | } |
75 | _ => None, | 81 | _ => None, |
76 | } | 82 | } |
@@ -82,12 +88,12 @@ fn reparse_block<'node>( | |||
82 | ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> { | 88 | ) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> { |
83 | let (node, reparser) = find_reparsable_node(root, edit.delete)?; | 89 | let (node, reparser) = find_reparsable_node(root, edit.delete)?; |
84 | let text = get_text_after_edit(node.clone().into(), &edit); | 90 | let text = get_text_after_edit(node.clone().into(), &edit); |
85 | let tokens = tokenize(&text); | 91 | let ParsedTokens { tokens, errors } = tokenize(&text); |
86 | if !is_balanced(&tokens) { | 92 | if !is_balanced(&tokens) { |
87 | return None; | 93 | return None; |
88 | } | 94 | } |
89 | let mut token_source = TextTokenSource::new(&text, &tokens); | 95 | let mut token_source = TextTokenSource::new(&text, &tokens); |
90 | let mut tree_sink = TextTreeSink::new(&text, &tokens); | 96 | let mut tree_sink = TextTreeSink::new(&text, &tokens, errors); |
91 | reparser.parse(&mut token_source, &mut tree_sink); | 97 | reparser.parse(&mut token_source, &mut tree_sink); |
92 | let (green, new_errors) = tree_sink.finish(); | 98 | let (green, new_errors) = tree_sink.finish(); |
93 | Some((node.replace_with(green), new_errors, node.text_range())) | 99 | Some((node.replace_with(green), new_errors, node.text_range())) |
@@ -96,6 +102,9 @@ fn reparse_block<'node>( | |||
96 | fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String { | 102 | fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String { |
97 | let edit = | 103 | let edit = |
98 | AtomTextEdit::replace(edit.delete - element.text_range().start(), edit.insert.clone()); | 104 | AtomTextEdit::replace(edit.delete - element.text_range().start(), edit.insert.clone()); |
105 | |||
106 | // Note: we could move this match to a method or even further: use enum_dispatch crate | ||
107 | // https://crates.io/crates/enum_dispatch | ||
99 | let text = match element { | 108 | let text = match element { |
100 | NodeOrToken::Token(token) => token.text().to_string(), | 109 | NodeOrToken::Token(token) => token.text().to_string(), |
101 | NodeOrToken::Node(node) => node.text().to_string(), | 110 | NodeOrToken::Node(node) => node.text().to_string(), |
@@ -112,6 +121,9 @@ fn is_contextual_kw(text: &str) -> bool { | |||
112 | 121 | ||
113 | fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(SyntaxNode, Reparser)> { | 122 | fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(SyntaxNode, Reparser)> { |
114 | let node = algo::find_covering_element(node, range); | 123 | let node = algo::find_covering_element(node, range); |
124 | |||
125 | // Note: we could move this match to a method or even further: use enum_dispatch crate | ||
126 | // https://crates.io/crates/enum_dispatch | ||
115 | let mut ancestors = match node { | 127 | let mut ancestors = match node { |
116 | NodeOrToken::Token(it) => it.parent().ancestors(), | 128 | NodeOrToken::Token(it) => it.parent().ancestors(), |
117 | NodeOrToken::Node(it) => it.ancestors(), | 129 | NodeOrToken::Node(it) => it.ancestors(), |
@@ -181,6 +193,8 @@ mod tests { | |||
181 | let fully_reparsed = SourceFile::parse(&after); | 193 | let fully_reparsed = SourceFile::parse(&after); |
182 | let incrementally_reparsed: Parse<SourceFile> = { | 194 | let incrementally_reparsed: Parse<SourceFile> = { |
183 | let f = SourceFile::parse(&before); | 195 | let f = SourceFile::parse(&before); |
196 | // FIXME: it seems this initialization statement is unnecessary (see edit in outer scope) | ||
197 | // Investigate whether it should really be removed. | ||
184 | let edit = AtomTextEdit { delete: range, insert: replace_with.to_string() }; | 198 | let edit = AtomTextEdit { delete: range, insert: replace_with.to_string() }; |
185 | let (green, new_errors, range) = | 199 | let (green, new_errors, range) = |
186 | incremental_reparse(f.tree().syntax(), &edit, f.errors.to_vec()).unwrap(); | 200 | incremental_reparse(f.tree().syntax(), &edit, f.errors.to_vec()).unwrap(); |