From 25bcb6bec934b7e8e1105db274d1e2546c79487a Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:12:04 -0500 Subject: [PATCH] feat: Add a new and improved syntax tree viewer --- crates/ide/src/lib.rs | 5 + crates/ide/src/view_syntax_tree.rs | 219 ++++++++++++ crates/rust-analyzer/src/handlers/request.rs | 10 + crates/rust-analyzer/src/lsp/ext.rs | 14 + crates/rust-analyzer/src/main_loop.rs | 1 + editors/code/package.json | 70 ++++ editors/code/src/commands.ts | 33 ++ editors/code/src/config.ts | 4 + editors/code/src/ctx.ts | 73 +++- editors/code/src/lsp_ext.ts | 4 + editors/code/src/main.ts | 3 + editors/code/src/syntax_tree_provider.ts | 332 +++++++++++++++++++ 12 files changed, 767 insertions(+), 1 deletion(-) create mode 100644 crates/ide/src/view_syntax_tree.rs create mode 100644 editors/code/src/syntax_tree_provider.ts diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 22a9ee52079c..88f4fccbf32a 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -56,6 +56,7 @@ mod view_hir; mod view_item_tree; mod view_memory_layout; mod view_mir; +mod view_syntax_tree; use std::{iter, panic::UnwindSafe}; @@ -339,6 +340,10 @@ impl Analysis { self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range)) } + pub fn view_syntax_tree(&self, file_id: FileId) -> Cancellable { + self.with_db(|db| view_syntax_tree::view_syntax_tree(db, file_id)) + } + pub fn view_hir(&self, position: FilePosition) -> Cancellable { self.with_db(|db| view_hir::view_hir(db, position)) } diff --git a/crates/ide/src/view_syntax_tree.rs b/crates/ide/src/view_syntax_tree.rs new file mode 100644 index 000000000000..b8c4f5dd0f0d --- /dev/null +++ b/crates/ide/src/view_syntax_tree.rs @@ -0,0 +1,219 @@ +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, +}; + +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) -> 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 { + let string_node = ast::String::cast(token)?; + let text = string_node.value().ok()?; + + // SAFETY: This is actually just a modified version of str::replace that + // tracks replacements, so I defer to the standard library on safety here + 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(unsafe { text.get_unchecked(last_end..start) }); + skipped += part.len(); + last_end = start + part.len(); + } + trim_result.push_str(unsafe { text.get_unchecked(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, +} + +#[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}]}]}]}]}"# + ]], + ); + } +} diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 7ac70efe2d6e..4644ebd44d73 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -148,6 +148,16 @@ pub(crate) fn handle_syntax_tree( Ok(res) } +pub(crate) fn handle_view_syntax_tree( + snap: GlobalStateSnapshot, + params: lsp_ext::ViewSyntaxTreeParams, +) -> anyhow::Result { + let _p = tracing::info_span!("handle_view_syntax_tree").entered(); + let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let res = snap.analysis.view_syntax_tree(id)?; + Ok(res) +} + pub(crate) fn handle_view_hir( snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index e1677cbcda80..d25f342a42b9 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -119,6 +119,20 @@ pub struct SyntaxTreeParams { pub range: Option, } +pub enum ViewSyntaxTree {} + +impl Request for ViewSyntaxTree { + type Params = ViewSyntaxTreeParams; + type Result = String; + const METHOD: &'static str = "rust-analyzer/viewSyntaxTree"; +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ViewSyntaxTreeParams { + pub text_document: TextDocumentIdentifier, +} + pub enum ViewHir {} impl Request for ViewHir { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 97657b926583..325b0afc7143 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -1146,6 +1146,7 @@ impl GlobalState { .on::(handlers::handle_ssr) .on::(handlers::handle_view_recursive_memory_layout) .on::(handlers::handle_syntax_tree) + .on::(handlers::handle_view_syntax_tree) .on::(handlers::handle_view_hir) .on::(handlers::handle_view_mir) .on::(handlers::handle_interpret_function) diff --git a/editors/code/package.json b/editors/code/package.json index e1edab4ba24d..cd6bdef6e0b9 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -288,6 +288,24 @@ "title": "Reveal File", "category": "rust-analyzer" }, + { + "command": "rust-analyzer.syntaxTreeReveal", + "title": "Reveal Syntax Element", + "icon": "$(search)", + "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.syntaxTreeHideWhitespace", + "title": "Hide Whitespace", + "icon": "$(filter)", + "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.syntaxTreeShowWhitespace", + "title": "Show Whitespace", + "icon": "$(filter-filled)", + "category": "rust-analyzer" + }, { "command": "rust-analyzer.viewMemoryLayout", "title": "View Memory Layout", @@ -345,6 +363,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, @@ -3362,6 +3385,18 @@ }, { "command": "rust-analyzer.openWalkthrough" + }, + { + "command": "rust-analyzer.syntaxTreeReveal", + "when": "false" + }, + { + "command": "rust-analyzer.syntaxTreeHideWhitespace", + "when": "false" + }, + { + "command": "rust-analyzer.syntaxTreeShowWhitespace", + "when": "false" } ], "editor/context": [ @@ -3375,6 +3410,25 @@ "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.syntaxTreeReveal", + "group": "inline", + "when": "view == rustSyntaxTree" + } ] }, "views": { @@ -3384,6 +3438,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": [ diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 64af124e7269..a78696e1863c 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -31,6 +31,7 @@ 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"; @@ -357,6 +358,38 @@ export async function execRevealDependency(e: RustEditor): Promise { 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); + } + }; +} + +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; diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 720c473c5b45..d1467a4e8241 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -351,6 +351,10 @@ export class Config { return this.get("showDependenciesExplorer"); } + get showSyntaxTree() { + return this.get("showSyntaxTree"); + } + get statusBarClickAction() { return this.get("statusBar.clickAction"); } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 7b5cc9f10611..13d18af3b5ef 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -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"; @@ -85,7 +86,11 @@ export class Ctx implements RustAnalyzerExtensionApi { private commandDisposables: Disposable[]; private unlinkedFiles: vscode.Uri[]; private _dependenciesProvider: RustDependenciesProvider | undefined; - private _dependencyTreeView: vscode.TreeView | undefined; + private _dependencyTreeView: + | vscode.TreeView + | undefined; + private _syntaxTreeProvider: SyntaxTreeProvider | undefined; + private _syntaxTreeView: vscode.TreeView | undefined; private lastStatus: ServerStatusParams | { health: "stopped" } = { health: "stopped" }; private _serverVersion: string; private statusBarActiveEditorListener: Disposable; @@ -110,6 +115,14 @@ export class Ctx implements RustAnalyzerExtensionApi { return this._dependenciesProvider; } + get syntaxTreeView() { + return this._syntaxTreeView; + } + + get syntaxTreeProvider() { + return this._syntaxTreeProvider; + } + constructor( readonly extCtx: vscode.ExtensionContext, commandFactories: Record, @@ -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) { @@ -326,6 +342,60 @@ export class Ctx implements RustAnalyzerExtensionApi { ); } + 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 (e.kind === vscode.TextEditorSelectionChangeKind.Command) { + return; + } + + 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(); @@ -424,6 +494,7 @@ export class Ctx implements RustAnalyzerExtensionApi { statusBar.command = "rust-analyzer.openLogs"; } this.dependenciesProvider?.refresh(); + void this.syntaxTreeProvider?.refresh(); break; case "warning": statusBar.color = new vscode.ThemeColor("statusBarItem.warningForeground"); diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index d52e314e2191..af86d9efd142 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -48,6 +48,9 @@ export const runFlycheck = new lc.NotificationType<{ export const syntaxTree = new lc.RequestType( "rust-analyzer/syntaxTree", ); +export const viewSyntaxTree = new lc.RequestType( + "rust-analyzer/viewSyntaxTree", +); export const viewCrateGraph = new lc.RequestType( "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 }; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index fdf43f66f949..989f66050ceb 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -199,6 +199,9 @@ function createCommands(): Record { rename: { enabled: commands.rename }, openLogs: { enabled: commands.openLogs }, revealDependency: { enabled: commands.revealDependency }, + syntaxTreeReveal: { enabled: commands.syntaxTreeReveal }, + syntaxTreeHideWhitespace: { enabled: commands.syntaxTreeHideWhitespace }, + syntaxTreeShowWhitespace: { enabled: commands.syntaxTreeShowWhitespace }, }; } diff --git a/editors/code/src/syntax_tree_provider.ts b/editors/code/src/syntax_tree_provider.ts new file mode 100644 index 000000000000..d248b72640cb --- /dev/null +++ b/editors/code/src/syntax_tree_provider.ts @@ -0,0 +1,332 @@ +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 { + private _onDidChangeTreeData: vscode.EventEmitter = + new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = + 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 { + return this.getRawChildren(element); + } + + getParent(element: SyntaxElement): vscode.ProviderResult { + return element.parent; + } + + resolveTreeItem( + item: SyntaxTreeItem, + element: SyntaxElement, + _token: vscode.CancellationToken, + ): vscode.ProviderResult { + 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 { + 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; + } + } + + getPathByRange(start: number, end: number): SyntaxElement[] | undefined { + if (this.root === undefined) { + return undefined; + } + + const path: SyntaxElement[] = [this.root]; + + if (this.root.start === start && this.root.end === end) { + return path; + } + + let children = this.getRawChildren(this.root); + + outer: while (true) { + for (const child of children) { + if (child.start <= start && child.end >= end) { + path.push(child); + + if (child.type === "Token") { + return path; + } else { + children = this.getRawChildren(child); + continue outer; + } + } + } + + return path; + } + } + + 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 = { + 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", +];