diff options
author | Ville Penttinen <[email protected]> | 2019-03-04 06:54:54 +0000 |
---|---|---|
committer | Ville Penttinen <[email protected]> | 2019-03-04 07:02:01 +0000 |
commit | 16ecd276f036de9b5dccdbcce55b25a2a5699385 (patch) | |
tree | 9e8e9ea50064a2987ca17a0e4bd5a75c80e2ab3e /crates/ra_ide_api/src | |
parent | 0db95fc812d2c839e847527b774dfda170266cec (diff) |
Implement syntax tree support for syntax inside string
This allows us to select a string or portions of it and try parsing it as rust
syntax. This is mostly helpful when developing tests where the test
itself contains some rust syntax as a string.
Diffstat (limited to 'crates/ra_ide_api/src')
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 11 | ||||
-rw-r--r-- | crates/ra_ide_api/src/syntax_tree.rs | 85 |
2 files changed, 88 insertions, 8 deletions
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 3e7cfbb54..b8a4adbce 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -32,13 +32,14 @@ mod references; | |||
32 | mod impls; | 32 | mod impls; |
33 | mod assists; | 33 | mod assists; |
34 | mod diagnostics; | 34 | mod diagnostics; |
35 | mod syntax_tree; | ||
35 | 36 | ||
36 | #[cfg(test)] | 37 | #[cfg(test)] |
37 | mod marks; | 38 | mod marks; |
38 | 39 | ||
39 | use std::sync::Arc; | 40 | use std::sync::Arc; |
40 | 41 | ||
41 | use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit, AstNode, algo}; | 42 | use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit}; |
42 | use ra_text_edit::TextEdit; | 43 | use ra_text_edit::TextEdit; |
43 | use ra_db::{ | 44 | use ra_db::{ |
44 | SourceDatabase, CheckCanceled, | 45 | SourceDatabase, CheckCanceled, |
@@ -246,13 +247,7 @@ impl Analysis { | |||
246 | /// Returns a syntax tree represented as `String`, for debug purposes. | 247 | /// Returns a syntax tree represented as `String`, for debug purposes. |
247 | // FIXME: use a better name here. | 248 | // FIXME: use a better name here. |
248 | pub fn syntax_tree(&self, file_id: FileId, text_range: Option<TextRange>) -> String { | 249 | pub fn syntax_tree(&self, file_id: FileId, text_range: Option<TextRange>) -> String { |
249 | if let Some(text_range) = text_range { | 250 | syntax_tree::syntax_tree(&self.db, file_id, text_range) |
250 | let file = self.db.parse(file_id); | ||
251 | let node = algo::find_covering_node(file.syntax(), text_range); | ||
252 | node.debug_dump() | ||
253 | } else { | ||
254 | self.db.parse(file_id).syntax().debug_dump() | ||
255 | } | ||
256 | } | 251 | } |
257 | 252 | ||
258 | /// Returns an edit to remove all newlines in the range, cleaning up minor | 253 | /// Returns an edit to remove all newlines in the range, cleaning up minor |
diff --git a/crates/ra_ide_api/src/syntax_tree.rs b/crates/ra_ide_api/src/syntax_tree.rs new file mode 100644 index 000000000..cdee63d59 --- /dev/null +++ b/crates/ra_ide_api/src/syntax_tree.rs | |||
@@ -0,0 +1,85 @@ | |||
1 | use ra_db::SourceDatabase; | ||
2 | use crate::db::RootDatabase; | ||
3 | use ra_syntax::{ | ||
4 | SourceFile, SyntaxNode, TextRange, AstNode, | ||
5 | algo::{self, visit::{visitor, Visitor}}, ast::{self, AstToken} | ||
6 | }; | ||
7 | |||
8 | pub use ra_db::FileId; | ||
9 | |||
10 | pub(crate) fn syntax_tree( | ||
11 | db: &RootDatabase, | ||
12 | file_id: FileId, | ||
13 | text_range: Option<TextRange>, | ||
14 | ) -> String { | ||
15 | if let Some(text_range) = text_range { | ||
16 | let file = db.parse(file_id); | ||
17 | let node = algo::find_covering_node(file.syntax(), text_range); | ||
18 | |||
19 | if let Some(tree) = syntax_tree_for_string(node, text_range) { | ||
20 | return tree; | ||
21 | } | ||
22 | |||
23 | node.debug_dump() | ||
24 | } else { | ||
25 | db.parse(file_id).syntax().debug_dump() | ||
26 | } | ||
27 | } | ||
28 | |||
29 | /// Attempts parsing the selected contents of a string literal | ||
30 | /// as rust syntax and returns its syntax tree | ||
31 | fn syntax_tree_for_string(node: &SyntaxNode, text_range: TextRange) -> Option<String> { | ||
32 | // When the range is inside a string | ||
33 | // we'll attempt parsing it as rust syntax | ||
34 | // to provide the syntax tree of the contents of the string | ||
35 | visitor() | ||
36 | .visit(|node: &ast::String| syntax_tree_for_token(node, text_range)) | ||
37 | .visit(|node: &ast::RawString| syntax_tree_for_token(node, text_range)) | ||
38 | .accept(node)? | ||
39 | } | ||
40 | |||
41 | fn syntax_tree_for_token<T: AstToken>(node: &T, text_range: TextRange) -> Option<String> { | ||
42 | // Range of the full node | ||
43 | let node_range = node.syntax().range(); | ||
44 | let text = node.text().to_string(); | ||
45 | |||
46 | // We start at some point inside the node | ||
47 | // Either we have selected the whole string | ||
48 | // or our selection is inside it | ||
49 | let start = text_range.start() - node_range.start(); | ||
50 | |||
51 | // how many characters we have selected | ||
52 | let len = text_range.len().to_usize(); | ||
53 | |||
54 | let node_len = node_range.len().to_usize(); | ||
55 | |||
56 | let start = start.to_usize(); | ||
57 | |||
58 | // We want to cap our length | ||
59 | let len = len.min(node_len); | ||
60 | |||
61 | // Ensure our slice is inside the actual string | ||
62 | let end = if start + len < text.len() { start + len } else { text.len() - start }; | ||
63 | |||
64 | let text = &text[start..end]; | ||
65 | |||
66 | // Remove possible extra string quotes from the start | ||
67 | // and the end of the string | ||
68 | let text = text | ||
69 | .trim_start_matches('r') | ||
70 | .trim_start_matches('#') | ||
71 | .trim_start_matches('"') | ||
72 | .trim_end_matches('#') | ||
73 | .trim_end_matches('"') | ||
74 | .trim(); | ||
75 | |||
76 | let parsed = SourceFile::parse(&text); | ||
77 | |||
78 | // If the "file" parsed without errors, | ||
79 | // return its syntax | ||
80 | if parsed.errors().is_empty() { | ||
81 | return Some(parsed.syntax().debug_dump()); | ||
82 | } | ||
83 | |||
84 | None | ||
85 | } | ||