aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/expand_macro.rs112
-rw-r--r--crates/ra_ide_api/src/lib.rs5
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs1
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs15
-rw-r--r--crates/ra_lsp_server/src/req.rs15
-rw-r--r--editors/code/src/commands/expand_macro.ts45
-rw-r--r--editors/code/src/commands/index.ts2
-rw-r--r--editors/code/src/extension.ts20
8 files changed, 210 insertions, 5 deletions
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..48dc90932
--- /dev/null
+++ b/crates/ra_ide_api/src/expand_macro.rs
@@ -0,0 +1,112 @@
1//! FIXME: write short doc here
2
3use crate::{db::RootDatabase, FilePosition};
4use ra_db::SourceDatabase;
5use rustc_hash::FxHashMap;
6
7use hir::db::AstDatabase;
8use ra_syntax::{
9 algo::{find_node_at_offset, replace_descendants},
10 ast::{self},
11 AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent,
12};
13
14fn insert_whitespaces(syn: SyntaxNode) -> String {
15 let mut res = String::new();
16
17 let mut token_iter = syn
18 .preorder_with_tokens()
19 .filter_map(|event| {
20 if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
21 Some(token)
22 } else {
23 None
24 }
25 })
26 .peekable();
27
28 while let Some(token) = token_iter.next() {
29 res += &token.text().to_string();
30 if token.kind().is_keyword()
31 || token.kind().is_literal()
32 || token.kind() == SyntaxKind::IDENT
33 {
34 if !token_iter.peek().map(|it| it.kind().is_punct()).unwrap_or(false) {
35 res += " ";
36 }
37 }
38 }
39
40 res
41}
42
43fn expand_macro_recur(
44 db: &RootDatabase,
45 source: hir::Source<&SyntaxNode>,
46 macro_call: &ast::MacroCall,
47) -> Option<SyntaxNode> {
48 let analyzer = hir::SourceAnalyzer::new(db, source, None);
49 let expansion = analyzer.expand(db, &macro_call)?;
50 let expanded: SyntaxNode = db.parse_or_expand(expansion.file_id())?;
51
52 let children = expanded.descendants().filter_map(ast::MacroCall::cast);
53 let mut replaces = FxHashMap::default();
54
55 for child in children.into_iter() {
56 let source = hir::Source::new(expansion.file_id(), source.ast);
57 let new_node = expand_macro_recur(db, source, &child)?;
58
59 replaces.insert(child.syntax().clone().into(), new_node.into());
60 }
61
62 Some(replace_descendants(&expanded, &replaces))
63}
64
65pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<(String, String)> {
66 let parse = db.parse(position.file_id);
67 let file = parse.tree();
68 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?;
69 let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?;
70
71 let source = hir::Source::new(position.file_id.into(), mac.syntax());
72
73 let expanded = expand_macro_recur(db, source, &mac)?;
74
75 // FIXME:
76 // macro expansion may lose all white space information
77 // But we hope someday we can use ra_fmt for that
78 let res = insert_whitespaces(expanded);
79 Some((name_ref.text().to_string(), res))
80}
81
82#[cfg(test)]
83mod tests {
84 use crate::mock_analysis::analysis_and_position;
85
86 fn check_expand_macro(fixture: &str, expected: (&str, &str)) {
87 let (analysis, pos) = analysis_and_position(fixture);
88
89 let result = analysis.expand_macro(pos).unwrap().unwrap();
90 assert_eq!(result, (expected.0.to_string(), expected.1.to_string()));
91 }
92
93 #[test]
94 fn macro_expand_recursive_expansion() {
95 check_expand_macro(
96 r#"
97 //- /lib.rs
98 macro_rules! bar {
99 () => { fn b() {} }
100 }
101 macro_rules! foo {
102 () => { bar!(); }
103 }
104 macro_rules! baz {
105 () => { foo!(); }
106 }
107 f<|>oo!();
108 "#,
109 ("foo", "fn b(){}"),
110 );
111 }
112}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 110ddcd62..d1b73ef6f 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;
@@ -296,6 +297,10 @@ impl Analysis {
296 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) 297 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
297 } 298 }
298 299
300 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<(String, String)>> {
301 self.with_db(|db| expand_macro::expand_macro(db, position))
302 }
303
299 /// Returns an edit to remove all newlines in the range, cleaning up minor 304 /// Returns an edit to remove all newlines in the range, cleaning up minor
300 /// stuff like trailing commas. 305 /// stuff like trailing commas.
301 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { 306 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..783b0a827 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -47,6 +47,21 @@ 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<(String, String)>> {
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) => Ok(world.analysis().expand_macro(FilePosition { file_id, offset })?),
62 }
63}
64
50pub fn handle_selection_range( 65pub fn handle_selection_range(
51 world: WorldSnapshot, 66 world: WorldSnapshot,
52 params: req::SelectionRangeParams, 67 params: req::SelectionRangeParams,
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index d25fc5726..dbc0e9624 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -45,6 +45,21 @@ pub struct SyntaxTreeParams {
45 pub range: Option<Range>, 45 pub range: Option<Range>,
46} 46}
47 47
48pub enum ExpandMacro {}
49
50impl Request for ExpandMacro {
51 type Params = ExpandMacroParams;
52 type Result = Option<(String, String)>;
53 const METHOD: &'static str = "rust-analyzer/expandMacro";
54}
55
56#[derive(Deserialize, Debug)]
57#[serde(rename_all = "camelCase")]
58pub struct ExpandMacroParams {
59 pub text_document: TextDocumentIdentifier,
60 pub position: Option<Position>,
61}
62
48pub enum SelectionRangeRequest {} 63pub enum SelectionRangeRequest {}
49 64
50impl Request for SelectionRangeRequest { 65impl Request for SelectionRangeRequest {
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts
new file mode 100644
index 000000000..bf1923190
--- /dev/null
+++ b/editors/code/src/commands/expand_macro.ts
@@ -0,0 +1,45 @@
1import * as vscode from 'vscode';
2import { Position, TextDocumentIdentifier } from 'vscode-languageclient';
3import { Server } from '../server';
4
5type ExpandMacroResult = [string, string]
6
7function code_format([name, text]: [string, string]): vscode.MarkdownString {
8 const markdown = new vscode.MarkdownString(`#### Recursive expansion of ${name}! macro`);
9 markdown.appendCodeblock(text, 'rust');
10 return markdown;
11}
12
13export class ExpandMacroHoverProvider implements vscode.HoverProvider {
14 public provideHover(
15 document: vscode.TextDocument,
16 position: vscode.Position,
17 token: vscode.CancellationToken,
18 ): Thenable<vscode.Hover | null> | null {
19 async function handle() {
20 const request: MacroExpandParams = {
21 textDocument: { uri: document.uri.toString() },
22 position,
23 };
24 const result = await Server.client.sendRequest<ExpandMacroResult>(
25 'rust-analyzer/expandMacro',
26 request
27 );
28 if (result != null) {
29 const formated = code_format(result);
30 return new vscode.Hover(formated);
31 }
32
33 return null;
34 };
35
36 return handle();
37 }
38}
39
40
41interface MacroExpandParams {
42 textDocument: TextDocumentIdentifier;
43 position: Position;
44}
45
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..1dfa6046f 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 { ExpandMacroHoverProvider } from './commands/expand_macro'
6import { HintsUpdater } from './commands/inlay_hints'; 7import { HintsUpdater } from './commands/inlay_hints';
7import { 8import {
8 interactivelyStartCargoWatch, 9 interactivelyStartCargoWatch,
@@ -91,11 +92,11 @@ export function activate(context: vscode.ExtensionContext) {
91 const allNotifications: Iterable< 92 const allNotifications: Iterable<
92 [string, lc.GenericNotificationHandler] 93 [string, lc.GenericNotificationHandler]
93 > = [ 94 > = [
94 [ 95 [
95 'rust-analyzer/publishDecorations', 96 'rust-analyzer/publishDecorations',
96 notifications.publishDecorations.handle 97 notifications.publishDecorations.handle
97 ] 98 ]
98 ]; 99 ];
99 const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); 100 const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
100 101
101 // The events below are plain old javascript events, triggered and handled by vscode 102 // The events below are plain old javascript events, triggered and handled by vscode
@@ -121,6 +122,15 @@ export function activate(context: vscode.ExtensionContext) {
121 context.subscriptions 122 context.subscriptions
122 ); 123 );
123 124
125 const expandMacroContentProvider = new ExpandMacroHoverProvider();
126
127 disposeOnDeactivation(
128 vscode.languages.registerHoverProvider(
129 'rust',
130 expandMacroContentProvider
131 )
132 );
133
124 const startServer = () => Server.start(allNotifications); 134 const startServer = () => Server.start(allNotifications);
125 const reloadCommand = () => reloadServer(startServer); 135 const reloadCommand = () => reloadServer(startServer);
126 136