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.rs52
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;
12use crate::{ 12use 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>(
96fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String { 102fn 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
113fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(SyntaxNode, Reparser)> { 122fn 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();