1 use base_db::{FileId, SourceDatabase};
2 use ide_db::RootDatabase;
4 algo, AstNode, NodeOrToken, SourceFile,
5 SyntaxKind::{RAW_STRING, STRING},
6 SyntaxToken, TextRange, TextSize,
9 // Feature: Show Syntax Tree
11 // Shows the parse tree of the current file. It exists mostly for debugging
12 // rust-analyzer itself.
15 // | Editor | Action Name
17 // | VS Code | **Rust Analyzer: Show Syntax Tree**
19 pub(crate) fn syntax_tree(
22 text_range: Option<TextRange>,
24 let parse = db.parse(file_id);
25 if let Some(text_range) = text_range {
26 let node = match algo::find_covering_element(parse.tree().syntax(), text_range) {
27 NodeOrToken::Node(node) => node,
28 NodeOrToken::Token(token) => {
29 if let Some(tree) = syntax_tree_for_string(&token, text_range) {
36 format!("{:#?}", node)
38 format!("{:#?}", parse.tree().syntax())
42 /// Attempts parsing the selected contents of a string literal
43 /// as rust syntax and returns its syntax tree
44 fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
45 // When the range is inside a string
46 // we'll attempt parsing it as rust syntax
47 // to provide the syntax tree of the contents of the string
49 STRING | RAW_STRING => syntax_tree_for_token(token, text_range),
54 fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
55 // Range of the full node
56 let node_range = node.text_range();
57 let text = node.text().to_string();
59 // We start at some point inside the node
60 // Either we have selected the whole string
61 // or our selection is inside it
62 let start = text_range.start() - node_range.start();
64 // how many characters we have selected
65 let len = text_range.len();
67 let node_len = node_range.len();
71 // We want to cap our length
72 let len = len.min(node_len);
74 // Ensure our slice is inside the actual string
76 if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
78 let text = &text[TextRange::new(start, end)];
80 // Remove possible extra string quotes from the start
81 // and the end of the string
83 .trim_start_matches('r')
84 .trim_start_matches('#')
85 .trim_start_matches('"')
86 .trim_end_matches('#')
87 .trim_end_matches('"')
89 // Remove custom markers
92 let parsed = SourceFile::parse(&text);
94 // If the "file" parsed without errors,
96 if parsed.errors().is_empty() {
97 return Some(format!("{:#?}", parsed.tree().syntax()));
105 use test_utils::assert_eq_text;
107 use crate::mock_analysis::{analysis_and_range, single_file};
110 fn test_syntax_tree_without_range() {
112 let (analysis, file_id) = single_file(r#"fn foo() {}"#);
113 let syn = analysis.syntax_tree(file_id, None).unwrap();
135 let (analysis, file_id) = single_file(
145 let syn = analysis.syntax_tree(file_id, None).unwrap();
162 WHITESPACE@11..16 "\n "
168 IDENT@16..22 "assert"
172 STRING@24..52 "\"\n fn foo() {\n ..."
174 WHITESPACE@53..54 " "
178 WHITESPACE@58..59 "\n"
186 fn test_syntax_tree_with_range() {
187 let (analysis, range) = analysis_and_range(r#"<|>fn foo() {}<|>"#.trim());
188 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
209 let (analysis, range) = analysis_and_range(
218 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
228 IDENT@16..22 "assert"
232 STRING@24..52 "\"\n fn foo() {\n ..."
234 WHITESPACE@53..54 " "
244 fn test_syntax_tree_inside_string() {
245 let (analysis, range) = analysis_and_range(
256 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
272 WHITESPACE@10..11 "\n"
279 let (analysis, range) = analysis_and_range(
290 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
306 WHITESPACE@10..11 "\n"
313 let (analysis, range) = analysis_and_range(
323 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
339 WHITESPACE@10..11 "\n"
341 WHITESPACE@12..13 "\n"
344 WHITESPACE@15..16 " "
350 WHITESPACE@21..22 " "
353 WHITESPACE@23..24 "\n"