aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src
diff options
context:
space:
mode:
authorVille Penttinen <[email protected]>2019-03-04 06:54:54 +0000
committerVille Penttinen <[email protected]>2019-03-04 07:02:01 +0000
commit16ecd276f036de9b5dccdbcce55b25a2a5699385 (patch)
tree9e8e9ea50064a2987ca17a0e4bd5a75c80e2ab3e /crates/ra_ide_api/src
parent0db95fc812d2c839e847527b774dfda170266cec (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.rs11
-rw-r--r--crates/ra_ide_api/src/syntax_tree.rs85
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;
32mod impls; 32mod impls;
33mod assists; 33mod assists;
34mod diagnostics; 34mod diagnostics;
35mod syntax_tree;
35 36
36#[cfg(test)] 37#[cfg(test)]
37mod marks; 38mod marks;
38 39
39use std::sync::Arc; 40use std::sync::Arc;
40 41
41use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit, AstNode, algo}; 42use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit};
42use ra_text_edit::TextEdit; 43use ra_text_edit::TextEdit;
43use ra_db::{ 44use 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 @@
1use ra_db::SourceDatabase;
2use crate::db::RootDatabase;
3use ra_syntax::{
4 SourceFile, SyntaxNode, TextRange, AstNode,
5 algo::{self, visit::{visitor, Visitor}}, ast::{self, AstToken}
6};
7
8pub use ra_db::FileId;
9
10pub(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
31fn 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
41fn 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}