aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-11-19 17:53:27 +0000
committerGitHub <[email protected]>2019-11-19 17:53:27 +0000
commit545c21923e2bc8daee889b26919256bb2ba55282 (patch)
treeba089b257e57e066ba190bfc9ea228ef8366a07d
parentd2782ab1c1ec0b9f2ac2131859a9ee880f97bc12 (diff)
parent1d56b80250d43a7d263d2e9583871c85081261b6 (diff)
Merge #2291
2291: Show expanded macro in vscode r=matklad a=edwin0cheng *Edited* ![new_screen_shot](https://user-images.githubusercontent.com/11014119/69169852-00550c00-0b34-11ea-9c40-8ecebdca0621.gif) Co-authored-by: Edwin Cheng <[email protected]>
-rw-r--r--crates/ra_hir/src/source_binder.rs2
-rw-r--r--crates/ra_ide_api/src/expand_macro.rs178
-rw-r--r--crates/ra_ide_api/src/lib.rs6
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs1
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs18
-rw-r--r--crates/ra_lsp_server/src/req.rs22
-rw-r--r--docs/user/features.md4
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/commands/expand_macro.ts83
-rw-r--r--editors/code/src/commands/index.ts2
-rw-r--r--editors/code/src/extension.ts12
11 files changed, 332 insertions, 1 deletions
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs
index f0ed8e2b2..5d3196c2a 100644
--- a/crates/ra_hir/src/source_binder.rs
+++ b/crates/ra_hir/src/source_binder.rs
@@ -140,7 +140,7 @@ impl Expansion {
140 exp_info.map_token_down(token) 140 exp_info.map_token_down(token)
141 } 141 }
142 142
143 fn file_id(&self) -> HirFileId { 143 pub fn file_id(&self) -> HirFileId {
144 self.macro_call_id.as_file(MacroFileKind::Items) 144 self.macro_call_id.as_file(MacroFileKind::Items)
145 } 145 }
146} 146}
diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs
new file mode 100644
index 000000000..e9eb2a7fb
--- /dev/null
+++ b/crates/ra_ide_api/src/expand_macro.rs
@@ -0,0 +1,178 @@
1//! This modules implements "expand macro" functionality in the IDE
2
3use crate::{db::RootDatabase, FilePosition};
4use hir::db::AstDatabase;
5use ra_db::SourceDatabase;
6use rustc_hash::FxHashMap;
7
8use ra_syntax::{
9 algo::{find_node_at_offset, replace_descendants},
10 ast::{self},
11 AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T,
12};
13
14pub struct ExpandedMacro {
15 pub name: String,
16 pub expansion: String,
17}
18
19pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
20 let parse = db.parse(position.file_id);
21 let file = parse.tree();
22 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?;
23 let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?;
24
25 let source = hir::Source::new(position.file_id.into(), mac.syntax());
26 let expanded = expand_macro_recur(db, source, &mac)?;
27
28 // FIXME:
29 // macro expansion may lose all white space information
30 // But we hope someday we can use ra_fmt for that
31 let expansion = insert_whitespaces(expanded);
32 Some(ExpandedMacro { name: name_ref.text().to_string(), expansion })
33}
34
35fn expand_macro_recur(
36 db: &RootDatabase,
37 source: hir::Source<&SyntaxNode>,
38 macro_call: &ast::MacroCall,
39) -> Option<SyntaxNode> {
40 let analyzer = hir::SourceAnalyzer::new(db, source, None);
41 let expansion = analyzer.expand(db, &macro_call)?;
42 let macro_file_id = expansion.file_id();
43 let expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?;
44
45 let children = expanded.descendants().filter_map(ast::MacroCall::cast);
46 let mut replaces = FxHashMap::default();
47
48 for child in children.into_iter() {
49 let source = hir::Source::new(macro_file_id, source.ast);
50 let new_node = expand_macro_recur(db, source, &child)?;
51
52 replaces.insert(child.syntax().clone().into(), new_node.into());
53 }
54
55 Some(replace_descendants(&expanded, &replaces))
56}
57
58// FIXME: It would also be cool to share logic here and in the mbe tests,
59// which are pretty unreadable at the moment.
60fn insert_whitespaces(syn: SyntaxNode) -> String {
61 use SyntaxKind::*;
62
63 let mut res = String::new();
64 let mut token_iter = syn
65 .preorder_with_tokens()
66 .filter_map(|event| {
67 if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
68 Some(token)
69 } else {
70 None
71 }
72 })
73 .peekable();
74
75 let mut indent = 0;
76 let mut last: Option<SyntaxKind> = None;
77
78 while let Some(token) = token_iter.next() {
79 let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
80 token_iter.peek().map(|it| f(it.kind())).unwrap_or(default)
81 };
82 let is_last = |f: fn(SyntaxKind) -> bool, default| -> bool {
83 last.map(|it| f(it)).unwrap_or(default)
84 };
85
86 res += &match token.kind() {
87 k @ _
88 if (k.is_keyword() || k.is_literal() || k == IDENT)
89 && is_next(|it| !it.is_punct(), true) =>
90 {
91 token.text().to_string() + " "
92 }
93 L_CURLY if is_next(|it| it != R_CURLY, true) => {
94 indent += 1;
95 format!(" {{\n{}", " ".repeat(indent))
96 }
97 R_CURLY if is_last(|it| it != L_CURLY, true) => {
98 indent = indent.checked_sub(1).unwrap_or(0);
99 format!("\n}}{}", " ".repeat(indent))
100 }
101 R_CURLY => {
102 indent = indent.checked_sub(1).unwrap_or(0);
103 format!("}}\n{}", " ".repeat(indent))
104 }
105 T![;] => format!(";\n{}", " ".repeat(indent)),
106 T![->] => " -> ".to_string(),
107 T![=] => " = ".to_string(),
108 T![=>] => " => ".to_string(),
109 _ => token.text().to_string(),
110 };
111
112 last = Some(token.kind());
113 }
114
115 res
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::mock_analysis::analysis_and_position;
122 use insta::assert_snapshot;
123
124 fn check_expand_macro(fixture: &str) -> ExpandedMacro {
125 let (analysis, pos) = analysis_and_position(fixture);
126 analysis.expand_macro(pos).unwrap().unwrap()
127 }
128
129 #[test]
130 fn macro_expand_recursive_expansion() {
131 let res = check_expand_macro(
132 r#"
133 //- /lib.rs
134 macro_rules! bar {
135 () => { fn b() {} }
136 }
137 macro_rules! foo {
138 () => { bar!(); }
139 }
140 macro_rules! baz {
141 () => { foo!(); }
142 }
143 f<|>oo!();
144 "#,
145 );
146
147 assert_eq!(res.name, "foo");
148 assert_snapshot!(res.expansion, @r###"
149fn b(){}
150"###);
151 }
152
153 #[test]
154 fn macro_expand_multiple_lines() {
155 let res = check_expand_macro(
156 r#"
157 //- /lib.rs
158 macro_rules! foo {
159 () => {
160 fn some_thing() -> u32 {
161 let a = 0;
162 a + 10
163 }
164 }
165 }
166 f<|>oo!();
167 "#,
168 );
169
170 assert_eq!(res.name, "foo");
171 assert_snapshot!(res.expansion, @r###"
172fn some_thing() -> u32 {
173 let a = 0;
174 a+10
175}
176"###);
177 }
178}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 110ddcd62..57ed97147 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -42,6 +42,7 @@ mod display;
42mod inlay_hints; 42mod inlay_hints;
43mod wasm_shims; 43mod wasm_shims;
44mod expand; 44mod expand;
45mod expand_macro;
45 46
46#[cfg(test)] 47#[cfg(test)]
47mod marks; 48mod marks;
@@ -65,6 +66,7 @@ pub use crate::{
65 completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, 66 completion::{CompletionItem, CompletionItemKind, InsertTextFormat},
66 diagnostics::Severity, 67 diagnostics::Severity,
67 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 68 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
69 expand_macro::ExpandedMacro,
68 feature_flags::FeatureFlags, 70 feature_flags::FeatureFlags,
69 folding_ranges::{Fold, FoldKind}, 71 folding_ranges::{Fold, FoldKind},
70 hover::HoverResult, 72 hover::HoverResult,
@@ -296,6 +298,10 @@ impl Analysis {
296 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) 298 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
297 } 299 }
298 300
301 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
302 self.with_db(|db| expand_macro::expand_macro(db, position))
303 }
304
299 /// Returns an edit to remove all newlines in the range, cleaning up minor 305 /// Returns an edit to remove all newlines in the range, cleaning up minor
300 /// stuff like trailing commas. 306 /// stuff like trailing commas.
301 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { 307 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> {
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 379dab438..f828efdee 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -436,6 +436,7 @@ fn on_request(
436 })? 436 })?
437 .on::<req::AnalyzerStatus>(handlers::handle_analyzer_status)? 437 .on::<req::AnalyzerStatus>(handlers::handle_analyzer_status)?
438 .on::<req::SyntaxTree>(handlers::handle_syntax_tree)? 438 .on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
439 .on::<req::ExpandMacro>(handlers::handle_expand_macro)?
439 .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? 440 .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
440 .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? 441 .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
441 .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 442 .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 20f9aee13..0461bf385 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -47,6 +47,24 @@ pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -
47 Ok(res) 47 Ok(res)
48} 48}
49 49
50pub fn handle_expand_macro(
51 world: WorldSnapshot,
52 params: req::ExpandMacroParams,
53) -> Result<Option<req::ExpandedMacro>> {
54 let _p = profile("handle_expand_macro");
55 let file_id = params.text_document.try_conv_with(&world)?;
56 let line_index = world.analysis().file_line_index(file_id)?;
57 let offset = params.position.map(|p| p.conv_with(&line_index));
58
59 match offset {
60 None => Ok(None),
61 Some(offset) => {
62 let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
63 Ok(res.map(|it| req::ExpandedMacro { name: it.name, expansion: it.expansion }))
64 }
65 }
66}
67
50pub fn handle_selection_range( 68pub fn handle_selection_range(
51 world: WorldSnapshot, 69 world: WorldSnapshot,
52 params: req::SelectionRangeParams, 70 params: req::SelectionRangeParams,
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index d25fc5726..39361b7e8 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -45,6 +45,28 @@ pub struct SyntaxTreeParams {
45 pub range: Option<Range>, 45 pub range: Option<Range>,
46} 46}
47 47
48#[derive(Serialize, Debug)]
49#[serde(rename_all = "camelCase")]
50pub struct ExpandedMacro {
51 pub name: String,
52 pub expansion: String,
53}
54
55pub enum ExpandMacro {}
56
57impl Request for ExpandMacro {
58 type Params = ExpandMacroParams;
59 type Result = Option<ExpandedMacro>;
60 const METHOD: &'static str = "rust-analyzer/expandMacro";
61}
62
63#[derive(Deserialize, Debug)]
64#[serde(rename_all = "camelCase")]
65pub struct ExpandMacroParams {
66 pub text_document: TextDocumentIdentifier,
67 pub position: Option<Position>,
68}
69
48pub enum SelectionRangeRequest {} 70pub enum SelectionRangeRequest {}
49 71
50impl Request for SelectionRangeRequest { 72impl Request for SelectionRangeRequest {
diff --git a/docs/user/features.md b/docs/user/features.md
index c160dd70b..309d2775d 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -81,6 +81,10 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas.
81Shows the parse tree of the current file. It exists mostly for debugging 81Shows the parse tree of the current file. It exists mostly for debugging
82rust-analyzer itself. 82rust-analyzer itself.
83 83
84#### Expand Macro Recursively
85
86Shows the full macro expansion of the macro at current cursor.
87
84#### Status 88#### Status
85 89
86Shows internal statistic about memory usage of rust-analyzer 90Shows internal statistic about memory usage of rust-analyzer
diff --git a/editors/code/package.json b/editors/code/package.json
index ee997e58f..94887674b 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -91,6 +91,11 @@
91 "category": "Rust Analyzer" 91 "category": "Rust Analyzer"
92 }, 92 },
93 { 93 {
94 "command": "rust-analyzer.expandMacro",
95 "title": "Expand macro recursively",
96 "category": "Rust Analyzer"
97 },
98 {
94 "command": "rust-analyzer.matchingBrace", 99 "command": "rust-analyzer.matchingBrace",
95 "title": "Find matching brace", 100 "title": "Find matching brace",
96 "category": "Rust Analyzer" 101 "category": "Rust Analyzer"
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts
new file mode 100644
index 000000000..34e0c8fb3
--- /dev/null
+++ b/editors/code/src/commands/expand_macro.ts
@@ -0,0 +1,83 @@
1import * as vscode from 'vscode';
2import { Position, TextDocumentIdentifier } from 'vscode-languageclient';
3import { Server } from '../server';
4
5export const expandMacroUri = vscode.Uri.parse(
6 'rust-analyzer://expandMacro/[EXPANSION].rs'
7);
8
9export class ExpandMacroContentProvider
10 implements vscode.TextDocumentContentProvider {
11 public eventEmitter = new vscode.EventEmitter<vscode.Uri>();
12
13 public provideTextDocumentContent(
14 uri: vscode.Uri
15 ): vscode.ProviderResult<string> {
16 async function handle() {
17 const editor = vscode.window.activeTextEditor;
18 if (editor == null) {
19 return '';
20 }
21
22 const position = editor.selection.active;
23 const request: MacroExpandParams = {
24 textDocument: { uri: editor.document.uri.toString() },
25 position
26 };
27 const expanded = await Server.client.sendRequest<ExpandedMacro>(
28 'rust-analyzer/expandMacro',
29 request
30 );
31
32 if (expanded == null) {
33 return 'Not available';
34 }
35
36 return code_format(expanded);
37 }
38
39 return handle();
40 }
41
42 get onDidChange(): vscode.Event<vscode.Uri> {
43 return this.eventEmitter.event;
44 }
45}
46
47// Opens the virtual file that will show the syntax tree
48//
49// The contents of the file come from the `TextDocumentContentProvider`
50export function createHandle(provider: ExpandMacroContentProvider) {
51 return async () => {
52 const uri = expandMacroUri;
53
54 const document = await vscode.workspace.openTextDocument(uri);
55
56 provider.eventEmitter.fire(uri);
57
58 return vscode.window.showTextDocument(
59 document,
60 vscode.ViewColumn.Two,
61 true
62 );
63 };
64}
65
66interface MacroExpandParams {
67 textDocument: TextDocumentIdentifier;
68 position: Position;
69}
70
71interface ExpandedMacro {
72 name: string;
73 expansion: string;
74}
75
76function code_format(expanded: ExpandedMacro): string {
77 let result = `// Recursive expansion of ${expanded.name}! macro\n`;
78 result += '// ' + '='.repeat(result.length - 3);
79 result += '\n\n';
80 result += expanded.expansion;
81
82 return result;
83}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index c194bd2ea..2ade6d331 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -1,5 +1,6 @@
1import * as analyzerStatus from './analyzer_status'; 1import * as analyzerStatus from './analyzer_status';
2import * as applySourceChange from './apply_source_change'; 2import * as applySourceChange from './apply_source_change';
3import * as expandMacro from './expand_macro';
3import * as inlayHints from './inlay_hints'; 4import * as inlayHints from './inlay_hints';
4import * as joinLines from './join_lines'; 5import * as joinLines from './join_lines';
5import * as matchingBrace from './matching_brace'; 6import * as matchingBrace from './matching_brace';
@@ -11,6 +12,7 @@ import * as syntaxTree from './syntaxTree';
11export { 12export {
12 analyzerStatus, 13 analyzerStatus,
13 applySourceChange, 14 applySourceChange,
15 expandMacro,
14 joinLines, 16 joinLines,
15 matchingBrace, 17 matchingBrace,
16 parentModule, 18 parentModule,
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index c06928d12..683497dfd 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -3,6 +3,7 @@ import * as lc from 'vscode-languageclient';
3 3
4import * as commands from './commands'; 4import * as commands from './commands';
5import { CargoWatchProvider } from './commands/cargo_watch'; 5import { CargoWatchProvider } from './commands/cargo_watch';
6import { ExpandMacroContentProvider } from './commands/expand_macro';
6import { HintsUpdater } from './commands/inlay_hints'; 7import { HintsUpdater } from './commands/inlay_hints';
7import { 8import {
8 interactivelyStartCargoWatch, 9 interactivelyStartCargoWatch,
@@ -97,6 +98,7 @@ export function activate(context: vscode.ExtensionContext) {
97 ] 98 ]
98 ]; 99 ];
99 const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); 100 const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
101 const expandMacroContentProvider = new ExpandMacroContentProvider();
100 102
101 // The events below are plain old javascript events, triggered and handled by vscode 103 // The events below are plain old javascript events, triggered and handled by vscode
102 vscode.window.onDidChangeActiveTextEditor( 104 vscode.window.onDidChangeActiveTextEditor(
@@ -109,11 +111,21 @@ export function activate(context: vscode.ExtensionContext) {
109 syntaxTreeContentProvider 111 syntaxTreeContentProvider
110 ) 112 )
111 ); 113 );
114 disposeOnDeactivation(
115 vscode.workspace.registerTextDocumentContentProvider(
116 'rust-analyzer',
117 expandMacroContentProvider
118 )
119 );
112 120
113 registerCommand( 121 registerCommand(
114 'rust-analyzer.syntaxTree', 122 'rust-analyzer.syntaxTree',
115 commands.syntaxTree.createHandle(syntaxTreeContentProvider) 123 commands.syntaxTree.createHandle(syntaxTreeContentProvider)
116 ); 124 );
125 registerCommand(
126 'rust-analyzer.expandMacro',
127 commands.expandMacro.createHandle(expandMacroContentProvider)
128 );
117 129
118 vscode.workspace.onDidChangeTextDocument( 130 vscode.workspace.onDidChangeTextDocument(
119 events.changeTextDocument.createHandler(syntaxTreeContentProvider), 131 events.changeTextDocument.createHandler(syntaxTreeContentProvider),