mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-17 02:42:54 +00:00
Remove the old syntax tree viewer
This commit is contained in:
parent
5ffe45d8cd
commit
cb5ce9eaa6
10 changed files with 1 additions and 700 deletions
|
|
@ -48,7 +48,6 @@ mod ssr;
|
||||||
mod static_index;
|
mod static_index;
|
||||||
mod status;
|
mod status;
|
||||||
mod syntax_highlighting;
|
mod syntax_highlighting;
|
||||||
mod syntax_tree;
|
|
||||||
mod test_explorer;
|
mod test_explorer;
|
||||||
mod typing;
|
mod typing;
|
||||||
mod view_crate_graph;
|
mod view_crate_graph;
|
||||||
|
|
@ -330,16 +329,6 @@ 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> {
|
pub fn view_syntax_tree(&self, file_id: FileId) -> Cancellable<String> {
|
||||||
self.with_db(|db| view_syntax_tree::view_syntax_tree(db, file_id))
|
self.with_db(|db| view_syntax_tree::view_syntax_tree(db, file_id))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,338 +0,0 @@
|
||||||
use hir::Semantics;
|
|
||||||
use ide_db::{FileId, RootDatabase};
|
|
||||||
use syntax::{
|
|
||||||
AstNode, NodeOrToken, SourceFile, SyntaxKind::STRING, SyntaxToken, TextRange, TextSize,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Feature: Show Debug Syntax Tree
|
|
||||||
//
|
|
||||||
// Shows the textual 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 "}"
|
|
||||||
"#]],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -136,18 +136,6 @@ pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Res
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_syntax_tree(
|
|
||||||
snap: GlobalStateSnapshot,
|
|
||||||
params: lsp_ext::SyntaxTreeParams,
|
|
||||||
) -> anyhow::Result<String> {
|
|
||||||
let _p = tracing::info_span!("handle_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)?;
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_view_syntax_tree(
|
pub(crate) fn handle_view_syntax_tree(
|
||||||
snap: GlobalStateSnapshot,
|
snap: GlobalStateSnapshot,
|
||||||
params: lsp_ext::ViewSyntaxTreeParams,
|
params: lsp_ext::ViewSyntaxTreeParams,
|
||||||
|
|
|
||||||
|
|
@ -108,21 +108,6 @@ impl Request for RebuildProcMacros {
|
||||||
const METHOD: &'static str = "rust-analyzer/rebuildProcMacros";
|
const METHOD: &'static str = "rust-analyzer/rebuildProcMacros";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SyntaxTree {}
|
|
||||||
|
|
||||||
impl Request for SyntaxTree {
|
|
||||||
type Params = SyntaxTreeParams;
|
|
||||||
type Result = String;
|
|
||||||
const METHOD: &'static str = "rust-analyzer/syntaxTree";
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SyntaxTreeParams {
|
|
||||||
pub text_document: TextDocumentIdentifier,
|
|
||||||
pub range: Option<Range>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ViewSyntaxTree {}
|
pub enum ViewSyntaxTree {}
|
||||||
|
|
||||||
impl Request for ViewSyntaxTree {
|
impl Request for ViewSyntaxTree {
|
||||||
|
|
|
||||||
|
|
@ -1145,7 +1145,6 @@ impl GlobalState {
|
||||||
.on::<RETRY, lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
|
.on::<RETRY, lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
|
||||||
.on::<NO_RETRY, lsp_ext::Ssr>(handlers::handle_ssr)
|
.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::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::ViewSyntaxTree>(handlers::handle_view_syntax_tree)
|
||||||
.on::<NO_RETRY, lsp_ext::ViewHir>(handlers::handle_view_hir)
|
.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::ViewMir>(handlers::handle_view_mir)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!---
|
<!---
|
||||||
lsp/ext.rs hash: a85494375e528064
|
lsp/ext.rs hash: 2d8604825c458288
|
||||||
|
|
||||||
If you need to change the above hash to make the test pass, please check if you
|
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:
|
need to adjust this doc as well and ping this issue:
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
|
||||||
"command": "rust-analyzer.syntaxTree",
|
|
||||||
"title": "Show Syntax Tree",
|
|
||||||
"category": "rust-analyzer (debug command)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "rust-analyzer.viewHir",
|
"command": "rust-analyzer.viewHir",
|
||||||
"title": "View Hir",
|
"title": "View Hir",
|
||||||
|
|
@ -2969,17 +2964,6 @@
|
||||||
"pattern": "$rustc"
|
"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": [
|
"semanticTokenTypes": [
|
||||||
{
|
{
|
||||||
"id": "angle",
|
"id": "angle",
|
||||||
|
|
@ -3299,10 +3283,6 @@
|
||||||
],
|
],
|
||||||
"menus": {
|
"menus": {
|
||||||
"commandPalette": [
|
"commandPalette": [
|
||||||
{
|
|
||||||
"command": "rust-analyzer.syntaxTree",
|
|
||||||
"when": "inRustProject"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "rust-analyzer.viewHir",
|
"command": "rust-analyzer.viewHir",
|
||||||
"when": "inRustProject"
|
"when": "inRustProject"
|
||||||
|
|
|
||||||
|
|
@ -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,
|
createTaskFromRunnable,
|
||||||
createCargoArgs,
|
createCargoArgs,
|
||||||
} from "./run";
|
} from "./run";
|
||||||
import { AstInspector } from "./ast_inspector";
|
|
||||||
import {
|
import {
|
||||||
isRustDocument,
|
isRustDocument,
|
||||||
isCargoRunnableArgs,
|
isCargoRunnableArgs,
|
||||||
|
|
@ -33,7 +32,6 @@ import type { DependencyId } from "./dependencies_provider";
|
||||||
import { log } from "./util";
|
import { log } from "./util";
|
||||||
import type { SyntaxElement } from "./syntax_tree_provider";
|
import type { SyntaxElement } from "./syntax_tree_provider";
|
||||||
|
|
||||||
export * from "./ast_inspector";
|
|
||||||
export * from "./run";
|
export * from "./run";
|
||||||
|
|
||||||
export function analyzerStatus(ctx: CtxInit): Cmd {
|
export function analyzerStatus(ctx: CtxInit): Cmd {
|
||||||
|
|
@ -459,89 +457,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 {
|
function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
|
||||||
const viewXir = xir === "hir" ? "viewHir" : "viewMir";
|
const viewXir = xir === "hir" ? "viewHir" : "viewMir";
|
||||||
const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
|
const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,6 @@ function createCommands(): Record<string, CommandFactory> {
|
||||||
matchingBrace: { enabled: commands.matchingBrace },
|
matchingBrace: { enabled: commands.matchingBrace },
|
||||||
joinLines: { enabled: commands.joinLines },
|
joinLines: { enabled: commands.joinLines },
|
||||||
parentModule: { enabled: commands.parentModule },
|
parentModule: { enabled: commands.parentModule },
|
||||||
syntaxTree: { enabled: commands.syntaxTree },
|
|
||||||
viewHir: { enabled: commands.viewHir },
|
viewHir: { enabled: commands.viewHir },
|
||||||
viewMir: { enabled: commands.viewMir },
|
viewMir: { enabled: commands.viewMir },
|
||||||
interpretFunction: { enabled: commands.interpretFunction },
|
interpretFunction: { enabled: commands.interpretFunction },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue