mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-25 19:35:06 +00:00
Merge pull request #18813 from Giga-Bowser/syntax-tree-view
feat: Add a new and improved syntax tree view
This commit is contained in:
commit
d133136bc4
15 changed files with 812 additions and 701 deletions
|
@ -48,7 +48,6 @@ mod ssr;
|
|||
mod static_index;
|
||||
mod status;
|
||||
mod syntax_highlighting;
|
||||
mod syntax_tree;
|
||||
mod test_explorer;
|
||||
mod typing;
|
||||
mod view_crate_graph;
|
||||
|
@ -56,6 +55,7 @@ mod view_hir;
|
|||
mod view_item_tree;
|
||||
mod view_memory_layout;
|
||||
mod view_mir;
|
||||
mod view_syntax_tree;
|
||||
|
||||
use std::{iter, panic::UnwindSafe};
|
||||
|
||||
|
@ -329,14 +329,8 @@ impl Analysis {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns a syntax tree represented as `String`, for debug purposes.
|
||||
// FIXME: use a better name here.
|
||||
pub fn syntax_tree(
|
||||
&self,
|
||||
file_id: FileId,
|
||||
text_range: Option<TextRange>,
|
||||
) -> Cancellable<String> {
|
||||
self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range))
|
||||
pub fn view_syntax_tree(&self, file_id: FileId) -> Cancellable<String> {
|
||||
self.with_db(|db| view_syntax_tree::view_syntax_tree(db, file_id))
|
||||
}
|
||||
|
||||
pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> {
|
||||
|
|
|
@ -1,338 +0,0 @@
|
|||
use hir::Semantics;
|
||||
use ide_db::{FileId, RootDatabase};
|
||||
use syntax::{
|
||||
AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
|
||||
};
|
||||
|
||||
// Feature: Show Syntax Tree
|
||||
//
|
||||
// Shows the parse tree of the current file. It exists mostly for debugging
|
||||
// rust-analyzer itself.
|
||||
//
|
||||
// |===
|
||||
// | Editor | Action Name
|
||||
//
|
||||
// | VS Code | **rust-analyzer: Show Syntax Tree**
|
||||
// |===
|
||||
// image::https://user-images.githubusercontent.com/48062697/113065586-068bdb80-91b1-11eb-9507-fee67f9f45a0.gif[]
|
||||
pub(crate) fn syntax_tree(
|
||||
db: &RootDatabase,
|
||||
file_id: FileId,
|
||||
text_range: Option<TextRange>,
|
||||
) -> String {
|
||||
let sema = Semantics::new(db);
|
||||
let parse = sema.parse_guess_edition(file_id);
|
||||
if let Some(text_range) = text_range {
|
||||
let node = match parse.syntax().covering_element(text_range) {
|
||||
NodeOrToken::Node(node) => node,
|
||||
NodeOrToken::Token(token) => {
|
||||
if let Some(tree) = syntax_tree_for_string(&token, text_range) {
|
||||
return tree;
|
||||
}
|
||||
token.parent().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
format!("{node:#?}")
|
||||
} else {
|
||||
format!("{:#?}", parse.syntax())
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts parsing the selected contents of a string literal
|
||||
/// as rust syntax and returns its syntax tree
|
||||
fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
|
||||
// When the range is inside a string
|
||||
// we'll attempt parsing it as rust syntax
|
||||
// to provide the syntax tree of the contents of the string
|
||||
match token.kind() {
|
||||
STRING => syntax_tree_for_token(token, text_range),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
|
||||
// Range of the full node
|
||||
let node_range = node.text_range();
|
||||
let text = node.text().to_owned();
|
||||
|
||||
// We start at some point inside the node
|
||||
// Either we have selected the whole string
|
||||
// or our selection is inside it
|
||||
let start = text_range.start() - node_range.start();
|
||||
|
||||
// how many characters we have selected
|
||||
let len = text_range.len();
|
||||
|
||||
let node_len = node_range.len();
|
||||
|
||||
// We want to cap our length
|
||||
let len = len.min(node_len);
|
||||
|
||||
// Ensure our slice is inside the actual string
|
||||
let end =
|
||||
if start + len < TextSize::of(&text) { start + len } else { TextSize::of(&text) - start };
|
||||
|
||||
let text = &text[TextRange::new(start, end)];
|
||||
|
||||
// Remove possible extra string quotes from the start
|
||||
// and the end of the string
|
||||
let text = text
|
||||
.trim_start_matches('r')
|
||||
.trim_start_matches('#')
|
||||
.trim_start_matches('"')
|
||||
.trim_end_matches('#')
|
||||
.trim_end_matches('"')
|
||||
.trim()
|
||||
// Remove custom markers
|
||||
.replace("$0", "");
|
||||
|
||||
let parsed = SourceFile::parse(&text, span::Edition::CURRENT_FIXME);
|
||||
|
||||
// If the "file" parsed without errors,
|
||||
// return its syntax
|
||||
if parsed.errors().is_empty() {
|
||||
return Some(format!("{:#?}", parsed.tree().syntax()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::fixture;
|
||||
|
||||
fn check(ra_fixture: &str, expect: expect_test::Expect) {
|
||||
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||
let syn = analysis.syntax_tree(file_id, None).unwrap();
|
||||
expect.assert_eq(&syn)
|
||||
}
|
||||
fn check_range(ra_fixture: &str, expect: expect_test::Expect) {
|
||||
let (analysis, frange) = fixture::range(ra_fixture);
|
||||
let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap();
|
||||
expect.assert_eq(&syn)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syntax_tree_without_range() {
|
||||
// Basic syntax
|
||||
check(
|
||||
r#"fn foo() {}"#,
|
||||
expect![[r#"
|
||||
SOURCE_FILE@0..11
|
||||
FN@0..11
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..6
|
||||
IDENT@3..6 "foo"
|
||||
PARAM_LIST@6..8
|
||||
L_PAREN@6..7 "("
|
||||
R_PAREN@7..8 ")"
|
||||
WHITESPACE@8..9 " "
|
||||
BLOCK_EXPR@9..11
|
||||
STMT_LIST@9..11
|
||||
L_CURLY@9..10 "{"
|
||||
R_CURLY@10..11 "}"
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
fn test() {
|
||||
assert!("
|
||||
fn foo() {
|
||||
}
|
||||
", "");
|
||||
}"#,
|
||||
expect![[r#"
|
||||
SOURCE_FILE@0..60
|
||||
FN@0..60
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..7
|
||||
IDENT@3..7 "test"
|
||||
PARAM_LIST@7..9
|
||||
L_PAREN@7..8 "("
|
||||
R_PAREN@8..9 ")"
|
||||
WHITESPACE@9..10 " "
|
||||
BLOCK_EXPR@10..60
|
||||
STMT_LIST@10..60
|
||||
L_CURLY@10..11 "{"
|
||||
WHITESPACE@11..16 "\n "
|
||||
EXPR_STMT@16..58
|
||||
MACRO_EXPR@16..57
|
||||
MACRO_CALL@16..57
|
||||
PATH@16..22
|
||||
PATH_SEGMENT@16..22
|
||||
NAME_REF@16..22
|
||||
IDENT@16..22 "assert"
|
||||
BANG@22..23 "!"
|
||||
TOKEN_TREE@23..57
|
||||
L_PAREN@23..24 "("
|
||||
STRING@24..52 "\"\n fn foo() {\n ..."
|
||||
COMMA@52..53 ","
|
||||
WHITESPACE@53..54 " "
|
||||
STRING@54..56 "\"\""
|
||||
R_PAREN@56..57 ")"
|
||||
SEMICOLON@57..58 ";"
|
||||
WHITESPACE@58..59 "\n"
|
||||
R_CURLY@59..60 "}"
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syntax_tree_with_range() {
|
||||
check_range(
|
||||
r#"$0fn foo() {}$0"#,
|
||||
expect![[r#"
|
||||
FN@0..11
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..6
|
||||
IDENT@3..6 "foo"
|
||||
PARAM_LIST@6..8
|
||||
L_PAREN@6..7 "("
|
||||
R_PAREN@7..8 ")"
|
||||
WHITESPACE@8..9 " "
|
||||
BLOCK_EXPR@9..11
|
||||
STMT_LIST@9..11
|
||||
L_CURLY@9..10 "{"
|
||||
R_CURLY@10..11 "}"
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_range(
|
||||
r#"
|
||||
fn test() {
|
||||
$0assert!("
|
||||
fn foo() {
|
||||
}
|
||||
", "");$0
|
||||
}"#,
|
||||
expect![[r#"
|
||||
EXPR_STMT@16..58
|
||||
MACRO_EXPR@16..57
|
||||
MACRO_CALL@16..57
|
||||
PATH@16..22
|
||||
PATH_SEGMENT@16..22
|
||||
NAME_REF@16..22
|
||||
IDENT@16..22 "assert"
|
||||
BANG@22..23 "!"
|
||||
TOKEN_TREE@23..57
|
||||
L_PAREN@23..24 "("
|
||||
STRING@24..52 "\"\n fn foo() {\n ..."
|
||||
COMMA@52..53 ","
|
||||
WHITESPACE@53..54 " "
|
||||
STRING@54..56 "\"\""
|
||||
R_PAREN@56..57 ")"
|
||||
SEMICOLON@57..58 ";"
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syntax_tree_inside_string() {
|
||||
check_range(
|
||||
r#"fn test() {
|
||||
assert!("
|
||||
$0fn foo() {
|
||||
}$0
|
||||
fn bar() {
|
||||
}
|
||||
", "");
|
||||
}"#,
|
||||
expect![[r#"
|
||||
SOURCE_FILE@0..12
|
||||
FN@0..12
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..6
|
||||
IDENT@3..6 "foo"
|
||||
PARAM_LIST@6..8
|
||||
L_PAREN@6..7 "("
|
||||
R_PAREN@7..8 ")"
|
||||
WHITESPACE@8..9 " "
|
||||
BLOCK_EXPR@9..12
|
||||
STMT_LIST@9..12
|
||||
L_CURLY@9..10 "{"
|
||||
WHITESPACE@10..11 "\n"
|
||||
R_CURLY@11..12 "}"
|
||||
"#]],
|
||||
);
|
||||
|
||||
// With a raw string
|
||||
check_range(
|
||||
r###"fn test() {
|
||||
assert!(r#"
|
||||
$0fn foo() {
|
||||
}$0
|
||||
fn bar() {
|
||||
}
|
||||
"#, "");
|
||||
}"###,
|
||||
expect![[r#"
|
||||
SOURCE_FILE@0..12
|
||||
FN@0..12
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..6
|
||||
IDENT@3..6 "foo"
|
||||
PARAM_LIST@6..8
|
||||
L_PAREN@6..7 "("
|
||||
R_PAREN@7..8 ")"
|
||||
WHITESPACE@8..9 " "
|
||||
BLOCK_EXPR@9..12
|
||||
STMT_LIST@9..12
|
||||
L_CURLY@9..10 "{"
|
||||
WHITESPACE@10..11 "\n"
|
||||
R_CURLY@11..12 "}"
|
||||
"#]],
|
||||
);
|
||||
|
||||
// With a raw string
|
||||
check_range(
|
||||
r###"fn test() {
|
||||
assert!(r$0#"
|
||||
fn foo() {
|
||||
}
|
||||
fn bar() {
|
||||
}"$0#, "");
|
||||
}"###,
|
||||
expect![[r#"
|
||||
SOURCE_FILE@0..25
|
||||
FN@0..12
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..6
|
||||
IDENT@3..6 "foo"
|
||||
PARAM_LIST@6..8
|
||||
L_PAREN@6..7 "("
|
||||
R_PAREN@7..8 ")"
|
||||
WHITESPACE@8..9 " "
|
||||
BLOCK_EXPR@9..12
|
||||
STMT_LIST@9..12
|
||||
L_CURLY@9..10 "{"
|
||||
WHITESPACE@10..11 "\n"
|
||||
R_CURLY@11..12 "}"
|
||||
WHITESPACE@12..13 "\n"
|
||||
FN@13..25
|
||||
FN_KW@13..15 "fn"
|
||||
WHITESPACE@15..16 " "
|
||||
NAME@16..19
|
||||
IDENT@16..19 "bar"
|
||||
PARAM_LIST@19..21
|
||||
L_PAREN@19..20 "("
|
||||
R_PAREN@20..21 ")"
|
||||
WHITESPACE@21..22 " "
|
||||
BLOCK_EXPR@22..25
|
||||
STMT_LIST@22..25
|
||||
L_CURLY@22..23 "{"
|
||||
WHITESPACE@23..24 "\n"
|
||||
R_CURLY@24..25 "}"
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
226
crates/ide/src/view_syntax_tree.rs
Normal file
226
crates/ide/src/view_syntax_tree.rs
Normal file
|
@ -0,0 +1,226 @@
|
|||
use hir::Semantics;
|
||||
use ide_db::{FileId, RootDatabase};
|
||||
use span::TextRange;
|
||||
use stdx::format_to;
|
||||
use syntax::{
|
||||
ast::{self, IsString},
|
||||
AstNode, AstToken, NodeOrToken, SourceFile, SyntaxNode, SyntaxToken, WalkEvent,
|
||||
};
|
||||
|
||||
// Feature: Show Syntax Tree
|
||||
//
|
||||
// Shows a tree view with the syntax tree of the current file
|
||||
//
|
||||
// |===
|
||||
// | Editor | Panel Name
|
||||
//
|
||||
// | VS Code | **Rust Syntax Tree**
|
||||
// |===
|
||||
pub(crate) fn view_syntax_tree(db: &RootDatabase, file_id: FileId) -> String {
|
||||
let sema = Semantics::new(db);
|
||||
let parse = sema.parse_guess_edition(file_id);
|
||||
syntax_node_to_json(parse.syntax(), None)
|
||||
}
|
||||
|
||||
fn syntax_node_to_json(node: &SyntaxNode, ctx: Option<InStringCtx>) -> String {
|
||||
let mut result = String::new();
|
||||
for event in node.preorder_with_tokens() {
|
||||
match event {
|
||||
WalkEvent::Enter(it) => {
|
||||
let kind = it.kind();
|
||||
let (text_range, inner_range_str) = match &ctx {
|
||||
Some(ctx) => {
|
||||
let inner_start: u32 = it.text_range().start().into();
|
||||
let inner_end: u32 = it.text_range().end().into();
|
||||
|
||||
let mut true_start = inner_start + ctx.offset;
|
||||
let mut true_end = inner_end + ctx.offset;
|
||||
for pos in &ctx.marker_positions {
|
||||
if *pos >= inner_end {
|
||||
break;
|
||||
}
|
||||
|
||||
// We conditionally add to true_start in case
|
||||
// the marker is between the start and end.
|
||||
true_start += 2 * (*pos < inner_start) as u32;
|
||||
true_end += 2;
|
||||
}
|
||||
|
||||
let true_range = TextRange::new(true_start.into(), true_end.into());
|
||||
|
||||
(
|
||||
true_range,
|
||||
format!(
|
||||
r#","istart":{:?},"iend":{:?}"#,
|
||||
it.text_range().start(),
|
||||
it.text_range().end()
|
||||
),
|
||||
)
|
||||
}
|
||||
None => (it.text_range(), "".to_owned()),
|
||||
};
|
||||
let start = text_range.start();
|
||||
let end = text_range.end();
|
||||
|
||||
match it {
|
||||
NodeOrToken::Node(_) => {
|
||||
format_to!(
|
||||
result,
|
||||
r#"{{"type":"Node","kind":"{kind:?}","start":{start:?},"end":{end:?}{inner_range_str},"children":["#
|
||||
);
|
||||
}
|
||||
NodeOrToken::Token(token) => {
|
||||
let comma = if token.next_sibling_or_token().is_some() { "," } else { "" };
|
||||
match parse_rust_string(token) {
|
||||
Some(parsed) => {
|
||||
format_to!(
|
||||
result,
|
||||
r#"{{"type":"Node","kind":"{kind:?}","start":{start:?},"end":{end:?}{inner_range_str},"children":[{parsed}]}}{comma}"#
|
||||
);
|
||||
}
|
||||
None => format_to!(
|
||||
result,
|
||||
r#"{{"type":"Token","kind":"{kind:?}","start":{start:?},"end":{end:?}{inner_range_str}}}{comma}"#
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WalkEvent::Leave(it) => match it {
|
||||
NodeOrToken::Node(node) => {
|
||||
let comma = if node.next_sibling_or_token().is_some() { "," } else { "" };
|
||||
format_to!(result, "]}}{comma}")
|
||||
}
|
||||
NodeOrToken::Token(_) => (),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_rust_string(token: SyntaxToken) -> Option<String> {
|
||||
let string_node = ast::String::cast(token)?;
|
||||
let text = string_node.value().ok()?;
|
||||
|
||||
let mut trim_result = String::new();
|
||||
let mut marker_positions = Vec::new();
|
||||
let mut skipped = 0;
|
||||
let mut last_end = 0;
|
||||
for (start, part) in text.match_indices("$0") {
|
||||
marker_positions.push((start - skipped) as u32);
|
||||
trim_result.push_str(&text[last_end..start]);
|
||||
skipped += part.len();
|
||||
last_end = start + part.len();
|
||||
}
|
||||
trim_result.push_str(&text[last_end..text.len()]);
|
||||
|
||||
let parsed = SourceFile::parse(&trim_result, span::Edition::CURRENT);
|
||||
|
||||
if !parsed.errors().is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let node: &SyntaxNode = &parsed.syntax_node();
|
||||
|
||||
if node.children().count() == 0 {
|
||||
// C'mon, you should have at least one node other than SOURCE_FILE
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(syntax_node_to_json(
|
||||
node,
|
||||
Some(InStringCtx {
|
||||
offset: string_node.text_range_between_quotes()?.start().into(),
|
||||
marker_positions,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
struct InStringCtx {
|
||||
offset: u32,
|
||||
marker_positions: Vec<u32>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::fixture;
|
||||
|
||||
fn check(ra_fixture: &str, expect: expect_test::Expect) {
|
||||
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||
let syn = analysis.view_syntax_tree(file_id).unwrap();
|
||||
expect.assert_eq(&syn)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_syntax_tree() {
|
||||
// Basic syntax
|
||||
check(
|
||||
r#"fn foo() {}"#,
|
||||
expect![[
|
||||
r#"{"type":"Node","kind":"SOURCE_FILE","start":0,"end":11,"children":[{"type":"Node","kind":"FN","start":0,"end":11,"children":[{"type":"Token","kind":"FN_KW","start":0,"end":2},{"type":"Token","kind":"WHITESPACE","start":2,"end":3},{"type":"Node","kind":"NAME","start":3,"end":6,"children":[{"type":"Token","kind":"IDENT","start":3,"end":6}]},{"type":"Node","kind":"PARAM_LIST","start":6,"end":8,"children":[{"type":"Token","kind":"L_PAREN","start":6,"end":7},{"type":"Token","kind":"R_PAREN","start":7,"end":8}]},{"type":"Token","kind":"WHITESPACE","start":8,"end":9},{"type":"Node","kind":"BLOCK_EXPR","start":9,"end":11,"children":[{"type":"Node","kind":"STMT_LIST","start":9,"end":11,"children":[{"type":"Token","kind":"L_CURLY","start":9,"end":10},{"type":"Token","kind":"R_CURLY","start":10,"end":11}]}]}]}]}"#
|
||||
]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
fn test() {
|
||||
assert!("
|
||||
fn foo() {
|
||||
}
|
||||
", "");
|
||||
}"#,
|
||||
expect![[
|
||||
r#"{"type":"Node","kind":"SOURCE_FILE","start":0,"end":60,"children":[{"type":"Node","kind":"FN","start":0,"end":60,"children":[{"type":"Token","kind":"FN_KW","start":0,"end":2},{"type":"Token","kind":"WHITESPACE","start":2,"end":3},{"type":"Node","kind":"NAME","start":3,"end":7,"children":[{"type":"Token","kind":"IDENT","start":3,"end":7}]},{"type":"Node","kind":"PARAM_LIST","start":7,"end":9,"children":[{"type":"Token","kind":"L_PAREN","start":7,"end":8},{"type":"Token","kind":"R_PAREN","start":8,"end":9}]},{"type":"Token","kind":"WHITESPACE","start":9,"end":10},{"type":"Node","kind":"BLOCK_EXPR","start":10,"end":60,"children":[{"type":"Node","kind":"STMT_LIST","start":10,"end":60,"children":[{"type":"Token","kind":"L_CURLY","start":10,"end":11},{"type":"Token","kind":"WHITESPACE","start":11,"end":16},{"type":"Node","kind":"EXPR_STMT","start":16,"end":58,"children":[{"type":"Node","kind":"MACRO_EXPR","start":16,"end":57,"children":[{"type":"Node","kind":"MACRO_CALL","start":16,"end":57,"children":[{"type":"Node","kind":"PATH","start":16,"end":22,"children":[{"type":"Node","kind":"PATH_SEGMENT","start":16,"end":22,"children":[{"type":"Node","kind":"NAME_REF","start":16,"end":22,"children":[{"type":"Token","kind":"IDENT","start":16,"end":22}]}]}]},{"type":"Token","kind":"BANG","start":22,"end":23},{"type":"Node","kind":"TOKEN_TREE","start":23,"end":57,"children":[{"type":"Token","kind":"L_PAREN","start":23,"end":24},{"type":"Node","kind":"STRING","start":24,"end":52,"children":[{"type":"Node","kind":"SOURCE_FILE","start":25,"end":51,"istart":0,"iend":26,"children":[{"type":"Token","kind":"WHITESPACE","start":25,"end":30,"istart":0,"iend":5},{"type":"Node","kind":"FN","start":30,"end":46,"istart":5,"iend":21,"children":[{"type":"Token","kind":"FN_KW","start":30,"end":32,"istart":5,"iend":7},{"type":"Token","kind":"WHITESPACE","start":32,"end":33,"istart":7,"iend":8},{"type":"Node","kind":"NAME","start":33,"end":36,"istart":8,"iend":11,"children":[{"type":"Token","kind":"IDENT","start":33,"end":36,"istart":8,"iend":11}]},{"type":"Node","kind":"PARAM_LIST","start":36,"end":38,"istart":11,"iend":13,"children":[{"type":"Token","kind":"L_PAREN","start":36,"end":37,"istart":11,"iend":12},{"type":"Token","kind":"R_PAREN","start":37,"end":38,"istart":12,"iend":13}]},{"type":"Token","kind":"WHITESPACE","start":38,"end":39,"istart":13,"iend":14},{"type":"Node","kind":"BLOCK_EXPR","start":39,"end":46,"istart":14,"iend":21,"children":[{"type":"Node","kind":"STMT_LIST","start":39,"end":46,"istart":14,"iend":21,"children":[{"type":"Token","kind":"L_CURLY","start":39,"end":40,"istart":14,"iend":15},{"type":"Token","kind":"WHITESPACE","start":40,"end":45,"istart":15,"iend":20},{"type":"Token","kind":"R_CURLY","start":45,"end":46,"istart":20,"iend":21}]}]}]},{"type":"Token","kind":"WHITESPACE","start":46,"end":51,"istart":21,"iend":26}]}]},{"type":"Token","kind":"COMMA","start":52,"end":53},{"type":"Token","kind":"WHITESPACE","start":53,"end":54},{"type":"Token","kind":"STRING","start":54,"end":56},{"type":"Token","kind":"R_PAREN","start":56,"end":57}]}]}]},{"type":"Token","kind":"SEMICOLON","start":57,"end":58}]},{"type":"Token","kind":"WHITESPACE","start":58,"end":59},{"type":"Token","kind":"R_CURLY","start":59,"end":60}]}]}]}]}"#
|
||||
]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_syntax_tree_inside_string() {
|
||||
check(
|
||||
r#"fn test() {
|
||||
assert!("
|
||||
$0fn foo() {
|
||||
}$0
|
||||
fn bar() {
|
||||
}
|
||||
", "");
|
||||
}"#,
|
||||
expect![[
|
||||
r#"{"type":"Node","kind":"SOURCE_FILE","start":0,"end":65,"children":[{"type":"Node","kind":"FN","start":0,"end":65,"children":[{"type":"Token","kind":"FN_KW","start":0,"end":2},{"type":"Token","kind":"WHITESPACE","start":2,"end":3},{"type":"Node","kind":"NAME","start":3,"end":7,"children":[{"type":"Token","kind":"IDENT","start":3,"end":7}]},{"type":"Node","kind":"PARAM_LIST","start":7,"end":9,"children":[{"type":"Token","kind":"L_PAREN","start":7,"end":8},{"type":"Token","kind":"R_PAREN","start":8,"end":9}]},{"type":"Token","kind":"WHITESPACE","start":9,"end":10},{"type":"Node","kind":"BLOCK_EXPR","start":10,"end":65,"children":[{"type":"Node","kind":"STMT_LIST","start":10,"end":65,"children":[{"type":"Token","kind":"L_CURLY","start":10,"end":11},{"type":"Token","kind":"WHITESPACE","start":11,"end":16},{"type":"Node","kind":"EXPR_STMT","start":16,"end":63,"children":[{"type":"Node","kind":"MACRO_EXPR","start":16,"end":62,"children":[{"type":"Node","kind":"MACRO_CALL","start":16,"end":62,"children":[{"type":"Node","kind":"PATH","start":16,"end":22,"children":[{"type":"Node","kind":"PATH_SEGMENT","start":16,"end":22,"children":[{"type":"Node","kind":"NAME_REF","start":16,"end":22,"children":[{"type":"Token","kind":"IDENT","start":16,"end":22}]}]}]},{"type":"Token","kind":"BANG","start":22,"end":23},{"type":"Node","kind":"TOKEN_TREE","start":23,"end":62,"children":[{"type":"Token","kind":"L_PAREN","start":23,"end":24},{"type":"Node","kind":"STRING","start":24,"end":57,"children":[{"type":"Node","kind":"SOURCE_FILE","start":25,"end":56,"istart":0,"iend":31,"children":[{"type":"Token","kind":"WHITESPACE","start":25,"end":26,"istart":0,"iend":1},{"type":"Node","kind":"FN","start":26,"end":38,"istart":1,"iend":13,"children":[{"type":"Token","kind":"FN_KW","start":26,"end":28,"istart":1,"iend":3},{"type":"Token","kind":"WHITESPACE","start":28,"end":29,"istart":3,"iend":4},{"type":"Node","kind":"NAME","start":29,"end":32,"istart":4,"iend":7,"children":[{"type":"Token","kind":"IDENT","start":29,"end":32,"istart":4,"iend":7}]},{"type":"Node","kind":"PARAM_LIST","start":32,"end":34,"istart":7,"iend":9,"children":[{"type":"Token","kind":"L_PAREN","start":32,"end":33,"istart":7,"iend":8},{"type":"Token","kind":"R_PAREN","start":33,"end":34,"istart":8,"iend":9}]},{"type":"Token","kind":"WHITESPACE","start":34,"end":35,"istart":9,"iend":10},{"type":"Node","kind":"BLOCK_EXPR","start":35,"end":38,"istart":10,"iend":13,"children":[{"type":"Node","kind":"STMT_LIST","start":35,"end":38,"istart":10,"iend":13,"children":[{"type":"Token","kind":"L_CURLY","start":35,"end":36,"istart":10,"iend":11},{"type":"Token","kind":"WHITESPACE","start":36,"end":37,"istart":11,"iend":12},{"type":"Token","kind":"R_CURLY","start":37,"end":38,"istart":12,"iend":13}]}]}]},{"type":"Token","kind":"WHITESPACE","start":38,"end":39,"istart":13,"iend":14},{"type":"Node","kind":"FN","start":39,"end":51,"istart":14,"iend":26,"children":[{"type":"Token","kind":"FN_KW","start":39,"end":41,"istart":14,"iend":16},{"type":"Token","kind":"WHITESPACE","start":41,"end":42,"istart":16,"iend":17},{"type":"Node","kind":"NAME","start":42,"end":45,"istart":17,"iend":20,"children":[{"type":"Token","kind":"IDENT","start":42,"end":45,"istart":17,"iend":20}]},{"type":"Node","kind":"PARAM_LIST","start":45,"end":47,"istart":20,"iend":22,"children":[{"type":"Token","kind":"L_PAREN","start":45,"end":46,"istart":20,"iend":21},{"type":"Token","kind":"R_PAREN","start":46,"end":47,"istart":21,"iend":22}]},{"type":"Token","kind":"WHITESPACE","start":47,"end":48,"istart":22,"iend":23},{"type":"Node","kind":"BLOCK_EXPR","start":48,"end":51,"istart":23,"iend":26,"children":[{"type":"Node","kind":"STMT_LIST","start":48,"end":51,"istart":23,"iend":26,"children":[{"type":"Token","kind":"L_CURLY","start":48,"end":49,"istart":23,"iend":24},{"type":"Token","kind":"WHITESPACE","start":49,"end":50,"istart":24,"iend":25},{"type":"Token","kind":"R_CURLY","start":50,"end":51,"istart":25,"iend":26}]}]}]},{"type":"Token","kind":"WHITESPACE","start":51,"end":56,"istart":26,"iend":31}]}]},{"type":"Token","kind":"COMMA","start":57,"end":58},{"type":"Token","kind":"WHITESPACE","start":58,"end":59},{"type":"Token","kind":"STRING","start":59,"end":61},{"type":"Token","kind":"R_PAREN","start":61,"end":62}]}]}]},{"type":"Token","kind":"SEMICOLON","start":62,"end":63}]},{"type":"Token","kind":"WHITESPACE","start":63,"end":64},{"type":"Token","kind":"R_CURLY","start":64,"end":65}]}]}]}]}"#
|
||||
]],
|
||||
);
|
||||
|
||||
// With a raw string
|
||||
check(
|
||||
r###"fn test() {
|
||||
assert!(r#"
|
||||
$0fn foo() {
|
||||
}$0
|
||||
fn bar() {
|
||||
}
|
||||
"#, "");
|
||||
}"###,
|
||||
expect![[
|
||||
r#"{"type":"Node","kind":"SOURCE_FILE","start":0,"end":68,"children":[{"type":"Node","kind":"FN","start":0,"end":68,"children":[{"type":"Token","kind":"FN_KW","start":0,"end":2},{"type":"Token","kind":"WHITESPACE","start":2,"end":3},{"type":"Node","kind":"NAME","start":3,"end":7,"children":[{"type":"Token","kind":"IDENT","start":3,"end":7}]},{"type":"Node","kind":"PARAM_LIST","start":7,"end":9,"children":[{"type":"Token","kind":"L_PAREN","start":7,"end":8},{"type":"Token","kind":"R_PAREN","start":8,"end":9}]},{"type":"Token","kind":"WHITESPACE","start":9,"end":10},{"type":"Node","kind":"BLOCK_EXPR","start":10,"end":68,"children":[{"type":"Node","kind":"STMT_LIST","start":10,"end":68,"children":[{"type":"Token","kind":"L_CURLY","start":10,"end":11},{"type":"Token","kind":"WHITESPACE","start":11,"end":16},{"type":"Node","kind":"EXPR_STMT","start":16,"end":66,"children":[{"type":"Node","kind":"MACRO_EXPR","start":16,"end":65,"children":[{"type":"Node","kind":"MACRO_CALL","start":16,"end":65,"children":[{"type":"Node","kind":"PATH","start":16,"end":22,"children":[{"type":"Node","kind":"PATH_SEGMENT","start":16,"end":22,"children":[{"type":"Node","kind":"NAME_REF","start":16,"end":22,"children":[{"type":"Token","kind":"IDENT","start":16,"end":22}]}]}]},{"type":"Token","kind":"BANG","start":22,"end":23},{"type":"Node","kind":"TOKEN_TREE","start":23,"end":65,"children":[{"type":"Token","kind":"L_PAREN","start":23,"end":24},{"type":"Node","kind":"STRING","start":24,"end":60,"children":[{"type":"Node","kind":"SOURCE_FILE","start":27,"end":58,"istart":0,"iend":31,"children":[{"type":"Token","kind":"WHITESPACE","start":27,"end":28,"istart":0,"iend":1},{"type":"Node","kind":"FN","start":28,"end":40,"istart":1,"iend":13,"children":[{"type":"Token","kind":"FN_KW","start":28,"end":30,"istart":1,"iend":3},{"type":"Token","kind":"WHITESPACE","start":30,"end":31,"istart":3,"iend":4},{"type":"Node","kind":"NAME","start":31,"end":34,"istart":4,"iend":7,"children":[{"type":"Token","kind":"IDENT","start":31,"end":34,"istart":4,"iend":7}]},{"type":"Node","kind":"PARAM_LIST","start":34,"end":36,"istart":7,"iend":9,"children":[{"type":"Token","kind":"L_PAREN","start":34,"end":35,"istart":7,"iend":8},{"type":"Token","kind":"R_PAREN","start":35,"end":36,"istart":8,"iend":9}]},{"type":"Token","kind":"WHITESPACE","start":36,"end":37,"istart":9,"iend":10},{"type":"Node","kind":"BLOCK_EXPR","start":37,"end":40,"istart":10,"iend":13,"children":[{"type":"Node","kind":"STMT_LIST","start":37,"end":40,"istart":10,"iend":13,"children":[{"type":"Token","kind":"L_CURLY","start":37,"end":38,"istart":10,"iend":11},{"type":"Token","kind":"WHITESPACE","start":38,"end":39,"istart":11,"iend":12},{"type":"Token","kind":"R_CURLY","start":39,"end":40,"istart":12,"iend":13}]}]}]},{"type":"Token","kind":"WHITESPACE","start":40,"end":41,"istart":13,"iend":14},{"type":"Node","kind":"FN","start":41,"end":53,"istart":14,"iend":26,"children":[{"type":"Token","kind":"FN_KW","start":41,"end":43,"istart":14,"iend":16},{"type":"Token","kind":"WHITESPACE","start":43,"end":44,"istart":16,"iend":17},{"type":"Node","kind":"NAME","start":44,"end":47,"istart":17,"iend":20,"children":[{"type":"Token","kind":"IDENT","start":44,"end":47,"istart":17,"iend":20}]},{"type":"Node","kind":"PARAM_LIST","start":47,"end":49,"istart":20,"iend":22,"children":[{"type":"Token","kind":"L_PAREN","start":47,"end":48,"istart":20,"iend":21},{"type":"Token","kind":"R_PAREN","start":48,"end":49,"istart":21,"iend":22}]},{"type":"Token","kind":"WHITESPACE","start":49,"end":50,"istart":22,"iend":23},{"type":"Node","kind":"BLOCK_EXPR","start":50,"end":53,"istart":23,"iend":26,"children":[{"type":"Node","kind":"STMT_LIST","start":50,"end":53,"istart":23,"iend":26,"children":[{"type":"Token","kind":"L_CURLY","start":50,"end":51,"istart":23,"iend":24},{"type":"Token","kind":"WHITESPACE","start":51,"end":52,"istart":24,"iend":25},{"type":"Token","kind":"R_CURLY","start":52,"end":53,"istart":25,"iend":26}]}]}]},{"type":"Token","kind":"WHITESPACE","start":53,"end":58,"istart":26,"iend":31}]}]},{"type":"Token","kind":"COMMA","start":60,"end":61},{"type":"Token","kind":"WHITESPACE","start":61,"end":62},{"type":"Token","kind":"STRING","start":62,"end":64},{"type":"Token","kind":"R_PAREN","start":64,"end":65}]}]}]},{"type":"Token","kind":"SEMICOLON","start":65,"end":66}]},{"type":"Token","kind":"WHITESPACE","start":66,"end":67},{"type":"Token","kind":"R_CURLY","start":67,"end":68}]}]}]}]}"#
|
||||
]],
|
||||
);
|
||||
|
||||
// With a raw string
|
||||
check(
|
||||
r###"fn test() {
|
||||
assert!(r$0#"
|
||||
fn foo() {
|
||||
}
|
||||
fn bar() {
|
||||
}"$0#, "");
|
||||
}"###,
|
||||
expect![[
|
||||
r#"{"type":"Node","kind":"SOURCE_FILE","start":0,"end":63,"children":[{"type":"Node","kind":"FN","start":0,"end":63,"children":[{"type":"Token","kind":"FN_KW","start":0,"end":2},{"type":"Token","kind":"WHITESPACE","start":2,"end":3},{"type":"Node","kind":"NAME","start":3,"end":7,"children":[{"type":"Token","kind":"IDENT","start":3,"end":7}]},{"type":"Node","kind":"PARAM_LIST","start":7,"end":9,"children":[{"type":"Token","kind":"L_PAREN","start":7,"end":8},{"type":"Token","kind":"R_PAREN","start":8,"end":9}]},{"type":"Token","kind":"WHITESPACE","start":9,"end":10},{"type":"Node","kind":"BLOCK_EXPR","start":10,"end":63,"children":[{"type":"Node","kind":"STMT_LIST","start":10,"end":63,"children":[{"type":"Token","kind":"L_CURLY","start":10,"end":11},{"type":"Token","kind":"WHITESPACE","start":11,"end":16},{"type":"Node","kind":"EXPR_STMT","start":16,"end":61,"children":[{"type":"Node","kind":"MACRO_EXPR","start":16,"end":60,"children":[{"type":"Node","kind":"MACRO_CALL","start":16,"end":60,"children":[{"type":"Node","kind":"PATH","start":16,"end":22,"children":[{"type":"Node","kind":"PATH_SEGMENT","start":16,"end":22,"children":[{"type":"Node","kind":"NAME_REF","start":16,"end":22,"children":[{"type":"Token","kind":"IDENT","start":16,"end":22}]}]}]},{"type":"Token","kind":"BANG","start":22,"end":23},{"type":"Node","kind":"TOKEN_TREE","start":23,"end":60,"children":[{"type":"Token","kind":"L_PAREN","start":23,"end":24},{"type":"Node","kind":"STRING","start":24,"end":55,"children":[{"type":"Node","kind":"SOURCE_FILE","start":27,"end":53,"istart":0,"iend":26,"children":[{"type":"Token","kind":"WHITESPACE","start":27,"end":28,"istart":0,"iend":1},{"type":"Node","kind":"FN","start":28,"end":40,"istart":1,"iend":13,"children":[{"type":"Token","kind":"FN_KW","start":28,"end":30,"istart":1,"iend":3},{"type":"Token","kind":"WHITESPACE","start":30,"end":31,"istart":3,"iend":4},{"type":"Node","kind":"NAME","start":31,"end":34,"istart":4,"iend":7,"children":[{"type":"Token","kind":"IDENT","start":31,"end":34,"istart":4,"iend":7}]},{"type":"Node","kind":"PARAM_LIST","start":34,"end":36,"istart":7,"iend":9,"children":[{"type":"Token","kind":"L_PAREN","start":34,"end":35,"istart":7,"iend":8},{"type":"Token","kind":"R_PAREN","start":35,"end":36,"istart":8,"iend":9}]},{"type":"Token","kind":"WHITESPACE","start":36,"end":37,"istart":9,"iend":10},{"type":"Node","kind":"BLOCK_EXPR","start":37,"end":40,"istart":10,"iend":13,"children":[{"type":"Node","kind":"STMT_LIST","start":37,"end":40,"istart":10,"iend":13,"children":[{"type":"Token","kind":"L_CURLY","start":37,"end":38,"istart":10,"iend":11},{"type":"Token","kind":"WHITESPACE","start":38,"end":39,"istart":11,"iend":12},{"type":"Token","kind":"R_CURLY","start":39,"end":40,"istart":12,"iend":13}]}]}]},{"type":"Token","kind":"WHITESPACE","start":40,"end":41,"istart":13,"iend":14},{"type":"Node","kind":"FN","start":41,"end":53,"istart":14,"iend":26,"children":[{"type":"Token","kind":"FN_KW","start":41,"end":43,"istart":14,"iend":16},{"type":"Token","kind":"WHITESPACE","start":43,"end":44,"istart":16,"iend":17},{"type":"Node","kind":"NAME","start":44,"end":47,"istart":17,"iend":20,"children":[{"type":"Token","kind":"IDENT","start":44,"end":47,"istart":17,"iend":20}]},{"type":"Node","kind":"PARAM_LIST","start":47,"end":49,"istart":20,"iend":22,"children":[{"type":"Token","kind":"L_PAREN","start":47,"end":48,"istart":20,"iend":21},{"type":"Token","kind":"R_PAREN","start":48,"end":49,"istart":21,"iend":22}]},{"type":"Token","kind":"WHITESPACE","start":49,"end":50,"istart":22,"iend":23},{"type":"Node","kind":"BLOCK_EXPR","start":50,"end":53,"istart":23,"iend":26,"children":[{"type":"Node","kind":"STMT_LIST","start":50,"end":53,"istart":23,"iend":26,"children":[{"type":"Token","kind":"L_CURLY","start":50,"end":51,"istart":23,"iend":24},{"type":"Token","kind":"WHITESPACE","start":51,"end":52,"istart":24,"iend":25},{"type":"Token","kind":"R_CURLY","start":52,"end":53,"istart":25,"iend":26}]}]}]}]}]},{"type":"Token","kind":"COMMA","start":55,"end":56},{"type":"Token","kind":"WHITESPACE","start":56,"end":57},{"type":"Token","kind":"STRING","start":57,"end":59},{"type":"Token","kind":"R_PAREN","start":59,"end":60}]}]}]},{"type":"Token","kind":"SEMICOLON","start":60,"end":61}]},{"type":"Token","kind":"WHITESPACE","start":61,"end":62},{"type":"Token","kind":"R_CURLY","start":62,"end":63}]}]}]}]}"#
|
||||
]],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -136,15 +136,13 @@ pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Res
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_syntax_tree(
|
||||
pub(crate) fn handle_view_syntax_tree(
|
||||
snap: GlobalStateSnapshot,
|
||||
params: lsp_ext::SyntaxTreeParams,
|
||||
params: lsp_ext::ViewSyntaxTreeParams,
|
||||
) -> anyhow::Result<String> {
|
||||
let _p = tracing::info_span!("handle_syntax_tree").entered();
|
||||
let _p = tracing::info_span!("handle_view_syntax_tree").entered();
|
||||
let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||
let line_index = snap.file_line_index(id)?;
|
||||
let text_range = params.range.and_then(|r| from_proto::text_range(&line_index, r).ok());
|
||||
let res = snap.analysis.syntax_tree(id, text_range)?;
|
||||
let res = snap.analysis.view_syntax_tree(id)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
|
|
@ -108,19 +108,18 @@ impl Request for RebuildProcMacros {
|
|||
const METHOD: &'static str = "rust-analyzer/rebuildProcMacros";
|
||||
}
|
||||
|
||||
pub enum SyntaxTree {}
|
||||
pub enum ViewSyntaxTree {}
|
||||
|
||||
impl Request for SyntaxTree {
|
||||
type Params = SyntaxTreeParams;
|
||||
impl Request for ViewSyntaxTree {
|
||||
type Params = ViewSyntaxTreeParams;
|
||||
type Result = String;
|
||||
const METHOD: &'static str = "rust-analyzer/syntaxTree";
|
||||
const METHOD: &'static str = "rust-analyzer/viewSyntaxTree";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SyntaxTreeParams {
|
||||
pub struct ViewSyntaxTreeParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
pub range: Option<Range>,
|
||||
}
|
||||
|
||||
pub enum ViewHir {}
|
||||
|
|
|
@ -1145,7 +1145,7 @@ impl GlobalState {
|
|||
.on::<RETRY, lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
|
||||
.on::<NO_RETRY, lsp_ext::Ssr>(handlers::handle_ssr)
|
||||
.on::<NO_RETRY, lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout)
|
||||
.on::<NO_RETRY, lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
|
||||
.on::<NO_RETRY, lsp_ext::ViewSyntaxTree>(handlers::handle_view_syntax_tree)
|
||||
.on::<NO_RETRY, lsp_ext::ViewHir>(handlers::handle_view_hir)
|
||||
.on::<NO_RETRY, lsp_ext::ViewMir>(handlers::handle_view_mir)
|
||||
.on::<NO_RETRY, lsp_ext::InterpretFunction>(handlers::handle_interpret_function)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!---
|
||||
lsp/ext.rs hash: 6dd762ae19630ec0
|
||||
lsp/ext.rs hash: 2d8604825c458288
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
|
@ -710,6 +710,23 @@ interface SyntaxTreeParams {
|
|||
Returns textual representation of a parse tree for the file/selected region.
|
||||
Primarily for debugging, but very useful for all people working on rust-analyzer itself.
|
||||
|
||||
## View Syntax Tree
|
||||
|
||||
**Method:** `rust-analyzer/viewSyntaxTree`
|
||||
|
||||
**Request:**
|
||||
|
||||
```typescript
|
||||
interface ViewSyntaxTreeParams {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `string`
|
||||
|
||||
Returns json representation of the file's syntax tree.
|
||||
Used to create a treeView for debugging and working on rust-analyzer itself.
|
||||
|
||||
## View Hir
|
||||
|
||||
**Method:** `rust-analyzer/viewHir`
|
||||
|
|
|
@ -108,11 +108,6 @@
|
|||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTree",
|
||||
"title": "Show Syntax Tree",
|
||||
"category": "rust-analyzer (debug command)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.viewHir",
|
||||
"title": "View Hir",
|
||||
|
@ -288,6 +283,30 @@
|
|||
"title": "Reveal File",
|
||||
"category": "rust-analyzer"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeReveal",
|
||||
"title": "Reveal Syntax Element",
|
||||
"icon": "$(search)",
|
||||
"category": "rust-analyzer (syntax tree)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeCopy",
|
||||
"title": "Copy",
|
||||
"icon": "$(copy)",
|
||||
"category": "rust-analyzer (syntax tree)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeHideWhitespace",
|
||||
"title": "Hide Whitespace",
|
||||
"icon": "$(filter)",
|
||||
"category": "rust-analyzer (syntax tree)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeShowWhitespace",
|
||||
"title": "Show Whitespace",
|
||||
"icon": "$(filter-filled)",
|
||||
"category": "rust-analyzer (syntax tree)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.viewMemoryLayout",
|
||||
"title": "View Memory Layout",
|
||||
|
@ -345,6 +364,11 @@
|
|||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.showSyntaxTree": {
|
||||
"markdownDescription": "Whether to show the syntax tree view.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.testExplorer": {
|
||||
"markdownDescription": "Whether to show the test explorer.",
|
||||
"default": false,
|
||||
|
@ -2944,17 +2968,6 @@
|
|||
"pattern": "$rustc"
|
||||
}
|
||||
],
|
||||
"colors": [
|
||||
{
|
||||
"id": "rust_analyzer.syntaxTreeBorder",
|
||||
"description": "Color of the border displayed in the Rust source code for the selected syntax node (see \"Show Syntax Tree\" command)",
|
||||
"defaults": {
|
||||
"dark": "#ffffff",
|
||||
"light": "#b700ff",
|
||||
"highContrast": "#b700ff"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticTokenTypes": [
|
||||
{
|
||||
"id": "angle",
|
||||
|
@ -3274,10 +3287,6 @@
|
|||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTree",
|
||||
"when": "inRustProject"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.viewHir",
|
||||
"when": "inRustProject"
|
||||
|
@ -3360,6 +3369,22 @@
|
|||
},
|
||||
{
|
||||
"command": "rust-analyzer.openWalkthrough"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeReveal",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeCopy",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeHideWhitespace",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeShowWhitespace",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
|
@ -3373,6 +3398,30 @@
|
|||
"when": "inRustProject && editorTextFocus && editorLangId == rust",
|
||||
"group": "navigation@1001"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeHideWhitespace",
|
||||
"group": "navigation",
|
||||
"when": "view == rustSyntaxTree && !rustSyntaxTree.hideWhitespace"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeShowWhitespace",
|
||||
"group": "navigation",
|
||||
"when": "view == rustSyntaxTree && rustSyntaxTree.hideWhitespace"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeCopy",
|
||||
"group": "inline",
|
||||
"when": "view == rustSyntaxTree"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.syntaxTreeReveal",
|
||||
"group": "inline",
|
||||
"when": "view == rustSyntaxTree"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
|
@ -3382,6 +3431,22 @@
|
|||
"name": "Rust Dependencies",
|
||||
"when": "inRustProject && config.rust-analyzer.showDependenciesExplorer"
|
||||
}
|
||||
],
|
||||
"rustSyntaxTreeContainer": [
|
||||
{
|
||||
"id": "rustSyntaxTree",
|
||||
"name": "Rust Syntax Tree",
|
||||
"when": "inRustProject && config.rust-analyzer.showSyntaxTree"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "rustSyntaxTreeContainer",
|
||||
"title": "Rust Syntax Tree",
|
||||
"icon": "$(list-tree)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jsonValidation": [
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
import * as vscode from "vscode";
|
||||
|
||||
import type { Ctx, Disposable } from "./ctx";
|
||||
import { type RustEditor, isRustEditor, unwrapUndefinable } from "./util";
|
||||
|
||||
// FIXME: consider implementing this via the Tree View API?
|
||||
// https://code.visualstudio.com/api/extension-guides/tree-view
|
||||
export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable {
|
||||
private readonly astDecorationType = vscode.window.createTextEditorDecorationType({
|
||||
borderColor: new vscode.ThemeColor("rust_analyzer.syntaxTreeBorder"),
|
||||
borderStyle: "solid",
|
||||
borderWidth: "2px",
|
||||
});
|
||||
private rustEditor: undefined | RustEditor;
|
||||
|
||||
// Lazy rust token range -> syntax tree file range.
|
||||
private readonly rust2Ast = new Lazy(() => {
|
||||
const astEditor = this.findAstTextEditor();
|
||||
if (!this.rustEditor || !astEditor) return undefined;
|
||||
|
||||
const buf: [vscode.Range, vscode.Range][] = [];
|
||||
for (let i = 0; i < astEditor.document.lineCount; ++i) {
|
||||
const astLine = astEditor.document.lineAt(i);
|
||||
|
||||
// Heuristically look for nodes with quoted text (which are token nodes)
|
||||
const isTokenNode = astLine.text.lastIndexOf('"') >= 0;
|
||||
if (!isTokenNode) continue;
|
||||
|
||||
const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text);
|
||||
if (!rustRange) continue;
|
||||
|
||||
buf.push([rustRange, this.findAstNodeRange(astLine)]);
|
||||
}
|
||||
return buf;
|
||||
});
|
||||
|
||||
constructor(ctx: Ctx) {
|
||||
ctx.pushExtCleanup(
|
||||
vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this),
|
||||
);
|
||||
ctx.pushExtCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
|
||||
vscode.workspace.onDidCloseTextDocument(
|
||||
this.onDidCloseTextDocument,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
vscode.workspace.onDidChangeTextDocument(
|
||||
this.onDidChangeTextDocument,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
vscode.window.onDidChangeVisibleTextEditors(
|
||||
this.onDidChangeVisibleTextEditors,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
}
|
||||
dispose() {
|
||||
this.setRustEditor(undefined);
|
||||
}
|
||||
|
||||
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
|
||||
if (
|
||||
this.rustEditor &&
|
||||
event.document.uri.toString() === this.rustEditor.document.uri.toString()
|
||||
) {
|
||||
this.rust2Ast.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidCloseTextDocument(doc: vscode.TextDocument) {
|
||||
if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
|
||||
this.setRustEditor(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeVisibleTextEditors(editors: readonly vscode.TextEditor[]) {
|
||||
if (!this.findAstTextEditor()) {
|
||||
this.setRustEditor(undefined);
|
||||
return;
|
||||
}
|
||||
this.setRustEditor(editors.find(isRustEditor));
|
||||
}
|
||||
|
||||
private findAstTextEditor(): undefined | vscode.TextEditor {
|
||||
return vscode.window.visibleTextEditors.find(
|
||||
(it) => it.document.uri.scheme === "rust-analyzer",
|
||||
);
|
||||
}
|
||||
|
||||
private setRustEditor(newRustEditor: undefined | RustEditor) {
|
||||
if (this.rustEditor && this.rustEditor !== newRustEditor) {
|
||||
this.rustEditor.setDecorations(this.astDecorationType, []);
|
||||
this.rust2Ast.reset();
|
||||
}
|
||||
this.rustEditor = newRustEditor;
|
||||
}
|
||||
|
||||
// additional positional params are omitted
|
||||
provideDefinition(
|
||||
doc: vscode.TextDocument,
|
||||
pos: vscode.Position,
|
||||
): vscode.ProviderResult<vscode.DefinitionLink[]> {
|
||||
if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const astEditor = this.findAstTextEditor();
|
||||
if (!astEditor) return;
|
||||
|
||||
const rust2AstRanges = this.rust2Ast
|
||||
.get()
|
||||
?.find(([rustRange, _]) => rustRange.contains(pos));
|
||||
if (!rust2AstRanges) return;
|
||||
|
||||
const [rustFileRange, astFileRange] = rust2AstRanges;
|
||||
|
||||
astEditor.revealRange(astFileRange);
|
||||
astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end);
|
||||
|
||||
return [
|
||||
{
|
||||
targetRange: astFileRange,
|
||||
targetUri: astEditor.document.uri,
|
||||
originSelectionRange: rustFileRange,
|
||||
targetSelectionRange: astFileRange,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// additional positional params are omitted
|
||||
provideHover(
|
||||
doc: vscode.TextDocument,
|
||||
hoverPosition: vscode.Position,
|
||||
): vscode.ProviderResult<vscode.Hover> {
|
||||
if (!this.rustEditor) return;
|
||||
|
||||
const astFileLine = doc.lineAt(hoverPosition.line);
|
||||
|
||||
const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text);
|
||||
if (!rustFileRange) return;
|
||||
|
||||
this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]);
|
||||
this.rustEditor.revealRange(rustFileRange);
|
||||
|
||||
const rustSourceCode = this.rustEditor.document.getText(rustFileRange);
|
||||
const astFileRange = this.findAstNodeRange(astFileLine);
|
||||
|
||||
return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange);
|
||||
}
|
||||
|
||||
private findAstNodeRange(astLine: vscode.TextLine): vscode.Range {
|
||||
const lineOffset = astLine.range.start;
|
||||
const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
|
||||
const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
|
||||
return new vscode.Range(begin, end);
|
||||
}
|
||||
|
||||
private parseRustTextRange(
|
||||
doc: vscode.TextDocument,
|
||||
astLine: string,
|
||||
): undefined | vscode.Range {
|
||||
const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine);
|
||||
if (!parsedRange) return;
|
||||
|
||||
const [begin, end] = parsedRange.slice(1).map((off) => this.positionAt(doc, +off));
|
||||
const actualBegin = unwrapUndefinable(begin);
|
||||
const actualEnd = unwrapUndefinable(end);
|
||||
return new vscode.Range(actualBegin, actualEnd);
|
||||
}
|
||||
|
||||
// Memoize the last value, otherwise the CPU is at 100% single core
|
||||
// with quadratic lookups when we build rust2Ast cache
|
||||
cache?: { doc: vscode.TextDocument; offset: number; line: number };
|
||||
|
||||
positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position {
|
||||
if (doc.eol === vscode.EndOfLine.LF) {
|
||||
return doc.positionAt(targetOffset);
|
||||
}
|
||||
|
||||
// Dirty workaround for crlf line endings
|
||||
// We are still in this prehistoric era of carriage returns here...
|
||||
|
||||
let line = 0;
|
||||
let offset = 0;
|
||||
|
||||
const cache = this.cache;
|
||||
if (cache?.doc === doc && cache.offset <= targetOffset) {
|
||||
({ line, offset } = cache);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const lineLenWithLf = doc.lineAt(line).text.length + 1;
|
||||
if (offset + lineLenWithLf > targetOffset) {
|
||||
this.cache = { doc, offset, line };
|
||||
return doc.positionAt(targetOffset + line);
|
||||
}
|
||||
offset += lineLenWithLf;
|
||||
line += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Lazy<T> {
|
||||
val: undefined | T;
|
||||
|
||||
constructor(private readonly compute: () => undefined | T) {}
|
||||
|
||||
get() {
|
||||
return this.val ?? (this.val = this.compute());
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.val = undefined;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ import {
|
|||
createTaskFromRunnable,
|
||||
createCargoArgs,
|
||||
} from "./run";
|
||||
import { AstInspector } from "./ast_inspector";
|
||||
import {
|
||||
isRustDocument,
|
||||
isCargoRunnableArgs,
|
||||
|
@ -31,8 +30,8 @@ import type { LanguageClient } from "vscode-languageclient/node";
|
|||
import { HOVER_REFERENCE_COMMAND } from "./client";
|
||||
import type { DependencyId } from "./dependencies_provider";
|
||||
import { log } from "./util";
|
||||
import type { SyntaxElement } from "./syntax_tree_provider";
|
||||
|
||||
export * from "./ast_inspector";
|
||||
export * from "./run";
|
||||
|
||||
export function analyzerStatus(ctx: CtxInit): Cmd {
|
||||
|
@ -288,13 +287,13 @@ export function openCargoToml(ctx: CtxInit): Cmd {
|
|||
|
||||
export function revealDependency(ctx: CtxInit): Cmd {
|
||||
return async (editor: RustEditor) => {
|
||||
if (!ctx.dependencies?.isInitialized()) {
|
||||
if (!ctx.dependenciesProvider?.isInitialized()) {
|
||||
return;
|
||||
}
|
||||
const documentPath = editor.document.uri.fsPath;
|
||||
const dep = ctx.dependencies?.getDependency(documentPath);
|
||||
const dep = ctx.dependenciesProvider?.getDependency(documentPath);
|
||||
if (dep) {
|
||||
await ctx.treeView?.reveal(dep, { select: true, expand: true });
|
||||
await ctx.dependencyTreeView?.reveal(dep, { select: true, expand: true });
|
||||
} else {
|
||||
await revealParentChain(editor.document, ctx);
|
||||
}
|
||||
|
@ -340,10 +339,10 @@ async function revealParentChain(document: RustDocument, ctx: CtxInit) {
|
|||
// a open file referencing the old version
|
||||
return;
|
||||
}
|
||||
} while (!ctx.dependencies?.contains(documentPath));
|
||||
} while (!ctx.dependenciesProvider?.contains(documentPath));
|
||||
parentChain.reverse();
|
||||
for (const idx in parentChain) {
|
||||
const treeView = ctx.treeView;
|
||||
const treeView = ctx.dependencyTreeView;
|
||||
if (!treeView) {
|
||||
continue;
|
||||
}
|
||||
|
@ -357,6 +356,77 @@ export async function execRevealDependency(e: RustEditor): Promise<void> {
|
|||
await vscode.commands.executeCommand("rust-analyzer.revealDependency", e);
|
||||
}
|
||||
|
||||
export function syntaxTreeReveal(): Cmd {
|
||||
return async (element: SyntaxElement) => {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
|
||||
if (activeEditor !== undefined) {
|
||||
const start = activeEditor.document.positionAt(element.start);
|
||||
const end = activeEditor.document.positionAt(element.end);
|
||||
|
||||
const newSelection = new vscode.Selection(start, end);
|
||||
|
||||
activeEditor.selection = newSelection;
|
||||
activeEditor.revealRange(newSelection);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function elementToString(
|
||||
activeDocument: vscode.TextDocument,
|
||||
element: SyntaxElement,
|
||||
depth: number = 0,
|
||||
): string {
|
||||
let result = " ".repeat(depth);
|
||||
const start = element.istart ?? element.start;
|
||||
const end = element.iend ?? element.end;
|
||||
|
||||
result += `${element.kind}@${start}..${end}`;
|
||||
|
||||
if (element.type === "Token") {
|
||||
const startPosition = activeDocument.positionAt(element.start);
|
||||
const endPosition = activeDocument.positionAt(element.end);
|
||||
const text = activeDocument.getText(new vscode.Range(startPosition, endPosition));
|
||||
// JSON.stringify quotes and escapes the string for us.
|
||||
result += ` ${JSON.stringify(text)}\n`;
|
||||
} else {
|
||||
result += "\n";
|
||||
for (const child of element.children) {
|
||||
result += elementToString(activeDocument, child, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function syntaxTreeCopy(): Cmd {
|
||||
return async (element: SyntaxElement) => {
|
||||
const activeDocument = vscode.window.activeTextEditor?.document;
|
||||
if (!activeDocument) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = elementToString(activeDocument, element);
|
||||
await vscode.env.clipboard.writeText(result);
|
||||
};
|
||||
}
|
||||
|
||||
export function syntaxTreeHideWhitespace(ctx: CtxInit): Cmd {
|
||||
return async () => {
|
||||
if (ctx.syntaxTreeProvider !== undefined) {
|
||||
await ctx.syntaxTreeProvider.toggleWhitespace();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function syntaxTreeShowWhitespace(ctx: CtxInit): Cmd {
|
||||
return async () => {
|
||||
if (ctx.syntaxTreeProvider !== undefined) {
|
||||
await ctx.syntaxTreeProvider.toggleWhitespace();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function ssr(ctx: CtxInit): Cmd {
|
||||
return async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
|
@ -426,89 +496,6 @@ export function serverVersion(ctx: CtxInit): Cmd {
|
|||
};
|
||||
}
|
||||
|
||||
// Opens the virtual file that will show the syntax tree
|
||||
//
|
||||
// The contents of the file come from the `TextDocumentContentProvider`
|
||||
export function syntaxTree(ctx: CtxInit): Cmd {
|
||||
const tdcp = new (class implements vscode.TextDocumentContentProvider {
|
||||
readonly uri = vscode.Uri.parse("rust-analyzer-syntax-tree://syntaxtree/tree.rast");
|
||||
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||
constructor() {
|
||||
vscode.workspace.onDidChangeTextDocument(
|
||||
this.onDidChangeTextDocument,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
vscode.window.onDidChangeActiveTextEditor(
|
||||
this.onDidChangeActiveTextEditor,
|
||||
this,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
}
|
||||
|
||||
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
|
||||
if (isRustDocument(event.document)) {
|
||||
// We need to order this after language server updates, but there's no API for that.
|
||||
// Hence, good old sleep().
|
||||
void sleep(10).then(() => this.eventEmitter.fire(this.uri));
|
||||
}
|
||||
}
|
||||
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
|
||||
if (editor && isRustEditor(editor)) {
|
||||
this.eventEmitter.fire(this.uri);
|
||||
}
|
||||
}
|
||||
|
||||
async provideTextDocumentContent(
|
||||
uri: vscode.Uri,
|
||||
ct: vscode.CancellationToken,
|
||||
): Promise<string> {
|
||||
const rustEditor = ctx.activeRustEditor;
|
||||
if (!rustEditor) return "";
|
||||
const client = ctx.client;
|
||||
|
||||
// When the range based query is enabled we take the range of the selection
|
||||
const range =
|
||||
uri.query === "range=true" && !rustEditor.selection.isEmpty
|
||||
? client.code2ProtocolConverter.asRange(rustEditor.selection)
|
||||
: null;
|
||||
|
||||
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range };
|
||||
return client.sendRequest(ra.syntaxTree, params, ct);
|
||||
}
|
||||
|
||||
get onDidChange(): vscode.Event<vscode.Uri> {
|
||||
return this.eventEmitter.event;
|
||||
}
|
||||
})();
|
||||
|
||||
ctx.pushExtCleanup(new AstInspector(ctx));
|
||||
ctx.pushExtCleanup(
|
||||
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp),
|
||||
);
|
||||
ctx.pushExtCleanup(
|
||||
vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
|
||||
brackets: [["[", ")"]],
|
||||
}),
|
||||
);
|
||||
|
||||
return async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
const rangeEnabled = !!editor && !editor.selection.isEmpty;
|
||||
|
||||
const uri = rangeEnabled ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) : tdcp.uri;
|
||||
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
|
||||
tdcp.eventEmitter.fire(uri);
|
||||
|
||||
void (await vscode.window.showTextDocument(document, {
|
||||
viewColumn: vscode.ViewColumn.Two,
|
||||
preserveFocus: true,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
|
||||
const viewXir = xir === "hir" ? "viewHir" : "viewMir";
|
||||
const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
|
||||
|
|
|
@ -351,6 +351,10 @@ export class Config {
|
|||
return this.get<boolean>("showDependenciesExplorer");
|
||||
}
|
||||
|
||||
get showSyntaxTree() {
|
||||
return this.get<boolean>("showSyntaxTree");
|
||||
}
|
||||
|
||||
get statusBarClickAction() {
|
||||
return this.get<string>("statusBar.clickAction");
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
RustDependenciesProvider,
|
||||
type DependencyId,
|
||||
} from "./dependencies_provider";
|
||||
import { SyntaxTreeProvider, type SyntaxElement } from "./syntax_tree_provider";
|
||||
import { execRevealDependency } from "./commands";
|
||||
import { PersistentState } from "./persistent_state";
|
||||
import { bootstrap } from "./bootstrap";
|
||||
|
@ -84,8 +85,12 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
private commandFactories: Record<string, CommandFactory>;
|
||||
private commandDisposables: Disposable[];
|
||||
private unlinkedFiles: vscode.Uri[];
|
||||
private _dependencies: RustDependenciesProvider | undefined;
|
||||
private _treeView: vscode.TreeView<Dependency | DependencyFile | DependencyId> | undefined;
|
||||
private _dependenciesProvider: RustDependenciesProvider | undefined;
|
||||
private _dependencyTreeView:
|
||||
| vscode.TreeView<Dependency | DependencyFile | DependencyId>
|
||||
| undefined;
|
||||
private _syntaxTreeProvider: SyntaxTreeProvider | undefined;
|
||||
private _syntaxTreeView: vscode.TreeView<SyntaxElement> | undefined;
|
||||
private lastStatus: ServerStatusParams | { health: "stopped" } = { health: "stopped" };
|
||||
private _serverVersion: string;
|
||||
private statusBarActiveEditorListener: Disposable;
|
||||
|
@ -102,12 +107,20 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
return this._client;
|
||||
}
|
||||
|
||||
get treeView() {
|
||||
return this._treeView;
|
||||
get dependencyTreeView() {
|
||||
return this._dependencyTreeView;
|
||||
}
|
||||
|
||||
get dependencies() {
|
||||
return this._dependencies;
|
||||
get dependenciesProvider() {
|
||||
return this._dependenciesProvider;
|
||||
}
|
||||
|
||||
get syntaxTreeView() {
|
||||
return this._syntaxTreeView;
|
||||
}
|
||||
|
||||
get syntaxTreeProvider() {
|
||||
return this._syntaxTreeProvider;
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
@ -278,6 +291,9 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
if (this.config.showDependenciesExplorer) {
|
||||
this.prepareTreeDependenciesView(client);
|
||||
}
|
||||
if (this.config.showSyntaxTree) {
|
||||
this.prepareSyntaxTreeView(client);
|
||||
}
|
||||
}
|
||||
|
||||
private prepareTreeDependenciesView(client: lc.LanguageClient) {
|
||||
|
@ -285,13 +301,13 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
...this,
|
||||
client: client,
|
||||
};
|
||||
this._dependencies = new RustDependenciesProvider(ctxInit);
|
||||
this._treeView = vscode.window.createTreeView("rustDependencies", {
|
||||
treeDataProvider: this._dependencies,
|
||||
this._dependenciesProvider = new RustDependenciesProvider(ctxInit);
|
||||
this._dependencyTreeView = vscode.window.createTreeView("rustDependencies", {
|
||||
treeDataProvider: this._dependenciesProvider,
|
||||
showCollapseAll: true,
|
||||
});
|
||||
|
||||
this.pushExtCleanup(this._treeView);
|
||||
this.pushExtCleanup(this._dependencyTreeView);
|
||||
vscode.window.onDidChangeActiveTextEditor(async (e) => {
|
||||
// we should skip documents that belong to the current workspace
|
||||
if (this.shouldRevealDependency(e)) {
|
||||
|
@ -303,7 +319,7 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
}
|
||||
});
|
||||
|
||||
this.treeView?.onDidChangeVisibility(async (e) => {
|
||||
this.dependencyTreeView?.onDidChangeVisibility(async (e) => {
|
||||
if (e.visible) {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (this.shouldRevealDependency(activeEditor)) {
|
||||
|
@ -322,10 +338,60 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
e !== undefined &&
|
||||
isRustEditor(e) &&
|
||||
!isDocumentInWorkspace(e.document) &&
|
||||
(this.treeView?.visible || false)
|
||||
(this.dependencyTreeView?.visible || false)
|
||||
);
|
||||
}
|
||||
|
||||
private prepareSyntaxTreeView(client: lc.LanguageClient) {
|
||||
const ctxInit: CtxInit = {
|
||||
...this,
|
||||
client: client,
|
||||
};
|
||||
this._syntaxTreeProvider = new SyntaxTreeProvider(ctxInit);
|
||||
this._syntaxTreeView = vscode.window.createTreeView("rustSyntaxTree", {
|
||||
treeDataProvider: this._syntaxTreeProvider,
|
||||
showCollapseAll: true,
|
||||
});
|
||||
|
||||
this.pushExtCleanup(this._syntaxTreeView);
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor(async () => {
|
||||
if (this.syntaxTreeView?.visible) {
|
||||
await this.syntaxTreeProvider?.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
vscode.workspace.onDidChangeTextDocument(async () => {
|
||||
if (this.syntaxTreeView?.visible) {
|
||||
await this.syntaxTreeProvider?.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
vscode.window.onDidChangeTextEditorSelection(async (e) => {
|
||||
if (!this.syntaxTreeView?.visible || !isRustEditor(e.textEditor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = e.selections[0];
|
||||
if (selection === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = e.textEditor.document.offsetAt(selection.start);
|
||||
const end = e.textEditor.document.offsetAt(selection.end);
|
||||
const result = this.syntaxTreeProvider?.getElementByRange(start, end);
|
||||
if (result !== undefined) {
|
||||
await this.syntaxTreeView?.reveal(result);
|
||||
}
|
||||
});
|
||||
|
||||
this._syntaxTreeView.onDidChangeVisibility(async (e) => {
|
||||
if (e.visible) {
|
||||
await this.syntaxTreeProvider?.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async restart() {
|
||||
// FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
|
||||
await this.stopAndDispose();
|
||||
|
@ -423,7 +489,8 @@ export class Ctx implements RustAnalyzerExtensionApi {
|
|||
} else {
|
||||
statusBar.command = "rust-analyzer.openLogs";
|
||||
}
|
||||
this.dependencies?.refresh();
|
||||
this.dependenciesProvider?.refresh();
|
||||
void this.syntaxTreeProvider?.refresh();
|
||||
break;
|
||||
case "warning":
|
||||
statusBar.color = new vscode.ThemeColor("statusBarItem.warningForeground");
|
||||
|
|
|
@ -48,6 +48,9 @@ export const runFlycheck = new lc.NotificationType<{
|
|||
export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>(
|
||||
"rust-analyzer/syntaxTree",
|
||||
);
|
||||
export const viewSyntaxTree = new lc.RequestType<ViewSyntaxTreeParams, string, void>(
|
||||
"rust-analyzer/viewSyntaxTree",
|
||||
);
|
||||
export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>(
|
||||
"rust-analyzer/viewCrateGraph",
|
||||
);
|
||||
|
@ -157,6 +160,7 @@ export type SyntaxTreeParams = {
|
|||
textDocument: lc.TextDocumentIdentifier;
|
||||
range: lc.Range | null;
|
||||
};
|
||||
export type ViewSyntaxTreeParams = { textDocument: lc.TextDocumentIdentifier };
|
||||
export type ViewCrateGraphParams = { full: boolean };
|
||||
export type ViewItemTreeParams = { textDocument: lc.TextDocumentIdentifier };
|
||||
|
||||
|
|
|
@ -158,7 +158,6 @@ function createCommands(): Record<string, CommandFactory> {
|
|||
matchingBrace: { enabled: commands.matchingBrace },
|
||||
joinLines: { enabled: commands.joinLines },
|
||||
parentModule: { enabled: commands.parentModule },
|
||||
syntaxTree: { enabled: commands.syntaxTree },
|
||||
viewHir: { enabled: commands.viewHir },
|
||||
viewMir: { enabled: commands.viewMir },
|
||||
interpretFunction: { enabled: commands.interpretFunction },
|
||||
|
@ -199,6 +198,10 @@ function createCommands(): Record<string, CommandFactory> {
|
|||
rename: { enabled: commands.rename },
|
||||
openLogs: { enabled: commands.openLogs },
|
||||
revealDependency: { enabled: commands.revealDependency },
|
||||
syntaxTreeReveal: { enabled: commands.syntaxTreeReveal },
|
||||
syntaxTreeCopy: { enabled: commands.syntaxTreeCopy },
|
||||
syntaxTreeHideWhitespace: { enabled: commands.syntaxTreeHideWhitespace },
|
||||
syntaxTreeShowWhitespace: { enabled: commands.syntaxTreeShowWhitespace },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
301
editors/code/src/syntax_tree_provider.ts
Normal file
301
editors/code/src/syntax_tree_provider.ts
Normal file
|
@ -0,0 +1,301 @@
|
|||
import * as vscode from "vscode";
|
||||
|
||||
import { isRustEditor, setContextValue } from "./util";
|
||||
import type { CtxInit } from "./ctx";
|
||||
import * as ra from "./lsp_ext";
|
||||
|
||||
export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<SyntaxElement | undefined | void> =
|
||||
new vscode.EventEmitter<SyntaxElement | undefined | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<SyntaxElement | undefined | void> =
|
||||
this._onDidChangeTreeData.event;
|
||||
ctx: CtxInit;
|
||||
root: SyntaxNode | undefined;
|
||||
hideWhitespace: boolean = false;
|
||||
|
||||
constructor(ctx: CtxInit) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
getTreeItem(element: SyntaxElement): vscode.TreeItem {
|
||||
return new SyntaxTreeItem(element);
|
||||
}
|
||||
|
||||
getChildren(element?: SyntaxElement): vscode.ProviderResult<SyntaxElement[]> {
|
||||
return this.getRawChildren(element);
|
||||
}
|
||||
|
||||
getParent(element: SyntaxElement): vscode.ProviderResult<SyntaxElement> {
|
||||
return element.parent;
|
||||
}
|
||||
|
||||
resolveTreeItem(
|
||||
item: SyntaxTreeItem,
|
||||
element: SyntaxElement,
|
||||
_token: vscode.CancellationToken,
|
||||
): vscode.ProviderResult<SyntaxTreeItem> {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
|
||||
if (editor !== undefined) {
|
||||
const start = editor.document.positionAt(element.start);
|
||||
const end = editor.document.positionAt(element.end);
|
||||
const range = new vscode.Range(start, end);
|
||||
|
||||
const text = editor.document.getText(range);
|
||||
item.tooltip = new vscode.MarkdownString().appendCodeblock(text, "rust");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private getRawChildren(element?: SyntaxElement): SyntaxElement[] {
|
||||
if (element?.type === "Node") {
|
||||
if (this.hideWhitespace) {
|
||||
return element.children.filter((e) => e.kind !== "WHITESPACE");
|
||||
}
|
||||
|
||||
return element.children;
|
||||
}
|
||||
|
||||
if (element?.type === "Token") {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (element === undefined && this.root !== undefined) {
|
||||
return [this.root];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
|
||||
if (editor && isRustEditor(editor)) {
|
||||
const params = { textDocument: { uri: editor.document.uri.toString() }, range: null };
|
||||
const fileText = await this.ctx.client.sendRequest(ra.viewSyntaxTree, params);
|
||||
this.root = JSON.parse(fileText, (_key, value: SyntaxElement) => {
|
||||
if (value.type === "Node") {
|
||||
for (const child of value.children) {
|
||||
child.parent = value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
} else {
|
||||
this.root = undefined;
|
||||
}
|
||||
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
getElementByRange(start: number, end: number): SyntaxElement | undefined {
|
||||
if (this.root === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let result: SyntaxElement = this.root;
|
||||
|
||||
if (this.root.start === start && this.root.end === end) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let children = this.getRawChildren(this.root);
|
||||
|
||||
outer: while (true) {
|
||||
for (const child of children) {
|
||||
if (child.start <= start && child.end >= end) {
|
||||
result = child;
|
||||
if (start === end && start === child.end) {
|
||||
// When the cursor is on the very end of a token,
|
||||
// we assume the user wants the next token instead.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.type === "Token") {
|
||||
return result;
|
||||
} else {
|
||||
children = this.getRawChildren(child);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async toggleWhitespace() {
|
||||
this.hideWhitespace = !this.hideWhitespace;
|
||||
this._onDidChangeTreeData.fire();
|
||||
await setContextValue("rustSyntaxTree.hideWhitespace", this.hideWhitespace);
|
||||
}
|
||||
}
|
||||
|
||||
export type SyntaxNode = {
|
||||
type: "Node";
|
||||
kind: string;
|
||||
start: number;
|
||||
end: number;
|
||||
istart?: number;
|
||||
iend?: number;
|
||||
children: SyntaxElement[];
|
||||
parent?: SyntaxElement;
|
||||
};
|
||||
|
||||
type SyntaxToken = {
|
||||
type: "Token";
|
||||
kind: string;
|
||||
start: number;
|
||||
end: number;
|
||||
istart?: number;
|
||||
iend?: number;
|
||||
parent?: SyntaxElement;
|
||||
};
|
||||
|
||||
export type SyntaxElement = SyntaxNode | SyntaxToken;
|
||||
|
||||
export class SyntaxTreeItem extends vscode.TreeItem {
|
||||
constructor(private readonly element: SyntaxElement) {
|
||||
super(element.kind);
|
||||
const icon = getIcon(element.kind);
|
||||
if (element.type === "Node") {
|
||||
this.contextValue = "syntaxNode";
|
||||
this.iconPath = icon ?? new vscode.ThemeIcon("list-tree");
|
||||
this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
|
||||
} else {
|
||||
this.contextValue = "syntaxToken";
|
||||
this.iconPath = icon ?? new vscode.ThemeIcon("symbol-string");
|
||||
this.collapsibleState = vscode.TreeItemCollapsibleState.None;
|
||||
}
|
||||
|
||||
if (element.istart !== undefined && element.iend !== undefined) {
|
||||
this.description = `${this.element.istart}..${this.element.iend}`;
|
||||
} else {
|
||||
this.description = `${this.element.start}..${this.element.end}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getIcon(kind: string): vscode.ThemeIcon | undefined {
|
||||
const icon = iconTable[kind];
|
||||
|
||||
if (icon !== undefined) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
if (kind.endsWith("_KW")) {
|
||||
return new vscode.ThemeIcon(
|
||||
"symbol-keyword",
|
||||
new vscode.ThemeColor("symbolIcon.keywordForeground"),
|
||||
);
|
||||
}
|
||||
|
||||
if (operators.includes(kind)) {
|
||||
return new vscode.ThemeIcon(
|
||||
"symbol-operator",
|
||||
new vscode.ThemeColor("symbolIcon.operatorForeground"),
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const iconTable: Record<string, vscode.ThemeIcon> = {
|
||||
CALL_EXPR: new vscode.ThemeIcon("call-outgoing"),
|
||||
COMMENT: new vscode.ThemeIcon("comment"),
|
||||
ENUM: new vscode.ThemeIcon("symbol-enum", new vscode.ThemeColor("symbolIcon.enumForeground")),
|
||||
FN: new vscode.ThemeIcon(
|
||||
"symbol-function",
|
||||
new vscode.ThemeColor("symbolIcon.functionForeground"),
|
||||
),
|
||||
FLOAT_NUMBER: new vscode.ThemeIcon(
|
||||
"symbol-number",
|
||||
new vscode.ThemeColor("symbolIcon.numberForeground"),
|
||||
),
|
||||
INDEX_EXPR: new vscode.ThemeIcon(
|
||||
"symbol-array",
|
||||
new vscode.ThemeColor("symbolIcon.arrayForeground"),
|
||||
),
|
||||
INT_NUMBER: new vscode.ThemeIcon(
|
||||
"symbol-number",
|
||||
new vscode.ThemeColor("symbolIcon.numberForeground"),
|
||||
),
|
||||
LITERAL: new vscode.ThemeIcon(
|
||||
"symbol-misc",
|
||||
new vscode.ThemeColor("symbolIcon.miscForeground"),
|
||||
),
|
||||
MODULE: new vscode.ThemeIcon(
|
||||
"symbol-module",
|
||||
new vscode.ThemeColor("symbolIcon.moduleForeground"),
|
||||
),
|
||||
METHOD_CALL_EXPR: new vscode.ThemeIcon("call-outgoing"),
|
||||
PARAM: new vscode.ThemeIcon(
|
||||
"symbol-parameter",
|
||||
new vscode.ThemeColor("symbolIcon.parameterForeground"),
|
||||
),
|
||||
RECORD_FIELD: new vscode.ThemeIcon(
|
||||
"symbol-field",
|
||||
new vscode.ThemeColor("symbolIcon.fieldForeground"),
|
||||
),
|
||||
SOURCE_FILE: new vscode.ThemeIcon("file-code"),
|
||||
STRING: new vscode.ThemeIcon("quote"),
|
||||
STRUCT: new vscode.ThemeIcon(
|
||||
"symbol-struct",
|
||||
new vscode.ThemeColor("symbolIcon.structForeground"),
|
||||
),
|
||||
TRAIT: new vscode.ThemeIcon(
|
||||
"symbol-interface",
|
||||
new vscode.ThemeColor("symbolIcon.interfaceForeground"),
|
||||
),
|
||||
TYPE_PARAM: new vscode.ThemeIcon(
|
||||
"symbol-type-parameter",
|
||||
new vscode.ThemeColor("symbolIcon.typeParameterForeground"),
|
||||
),
|
||||
VARIANT: new vscode.ThemeIcon(
|
||||
"symbol-enum-member",
|
||||
new vscode.ThemeColor("symbolIcon.enumMemberForeground"),
|
||||
),
|
||||
WHITESPACE: new vscode.ThemeIcon("whitespace"),
|
||||
};
|
||||
|
||||
const operators = [
|
||||
"PLUS",
|
||||
"PLUSEQ",
|
||||
"MINUS",
|
||||
"MINUSEQ",
|
||||
"STAR",
|
||||
"STAREQ",
|
||||
"SLASH",
|
||||
"SLASHEQ",
|
||||
"PERCENT",
|
||||
"PERCENTEQ",
|
||||
"CARET",
|
||||
"CARETEQ",
|
||||
"AMP",
|
||||
"AMPEQ",
|
||||
"AMP2",
|
||||
"PIPE",
|
||||
"PIPEEQ",
|
||||
"PIPE2",
|
||||
"SHL",
|
||||
"SHLEQ",
|
||||
"SHR",
|
||||
"SHREQ",
|
||||
"EQ",
|
||||
"EQ2",
|
||||
"BANG",
|
||||
"NEQ",
|
||||
"L_ANGLE",
|
||||
"LTEQ",
|
||||
"R_ANGLE",
|
||||
"GTEQ",
|
||||
"COLON2",
|
||||
"THIN_ARROW",
|
||||
"FAT_ARROW",
|
||||
"DOT",
|
||||
"DOT2",
|
||||
"DOT2EQ",
|
||||
"AT",
|
||||
];
|
Loading…
Reference in a new issue