diff options
-rw-r--r-- | crates/ra_ide_api/src/expand_macro.rs | 112 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 15 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/req.rs | 15 | ||||
-rw-r--r-- | editors/code/src/commands/expand_macro.ts | 45 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 2 | ||||
-rw-r--r-- | editors/code/src/extension.ts | 20 |
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 | |||
3 | use crate::{db::RootDatabase, FilePosition}; | ||
4 | use ra_db::SourceDatabase; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | |||
7 | use hir::db::AstDatabase; | ||
8 | use ra_syntax::{ | ||
9 | algo::{find_node_at_offset, replace_descendants}, | ||
10 | ast::{self}, | ||
11 | AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, | ||
12 | }; | ||
13 | |||
14 | fn 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 | |||
43 | fn 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, ¯o_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 | |||
65 | pub(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)] | ||
83 | mod 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; | |||
42 | mod inlay_hints; | 42 | mod inlay_hints; |
43 | mod wasm_shims; | 43 | mod wasm_shims; |
44 | mod expand; | 44 | mod expand; |
45 | mod expand_macro; | ||
45 | 46 | ||
46 | #[cfg(test)] | 47 | #[cfg(test)] |
47 | mod marks; | 48 | mod 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 | ||
50 | pub 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 | |||
50 | pub fn handle_selection_range( | 65 | pub 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 | ||
48 | pub enum ExpandMacro {} | ||
49 | |||
50 | impl 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")] | ||
58 | pub struct ExpandMacroParams { | ||
59 | pub text_document: TextDocumentIdentifier, | ||
60 | pub position: Option<Position>, | ||
61 | } | ||
62 | |||
48 | pub enum SelectionRangeRequest {} | 63 | pub enum SelectionRangeRequest {} |
49 | 64 | ||
50 | impl Request for SelectionRangeRequest { | 65 | impl 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 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import { Position, TextDocumentIdentifier } from 'vscode-languageclient'; | ||
3 | import { Server } from '../server'; | ||
4 | |||
5 | type ExpandMacroResult = [string, string] | ||
6 | |||
7 | function 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 | |||
13 | export 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 | |||
41 | interface 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 @@ | |||
1 | import * as analyzerStatus from './analyzer_status'; | 1 | import * as analyzerStatus from './analyzer_status'; |
2 | import * as applySourceChange from './apply_source_change'; | 2 | import * as applySourceChange from './apply_source_change'; |
3 | import * as expandMacro from './expand_macro'; | ||
3 | import * as inlayHints from './inlay_hints'; | 4 | import * as inlayHints from './inlay_hints'; |
4 | import * as joinLines from './join_lines'; | 5 | import * as joinLines from './join_lines'; |
5 | import * as matchingBrace from './matching_brace'; | 6 | import * as matchingBrace from './matching_brace'; |
@@ -11,6 +12,7 @@ import * as syntaxTree from './syntaxTree'; | |||
11 | export { | 12 | export { |
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 | ||
4 | import * as commands from './commands'; | 4 | import * as commands from './commands'; |
5 | import { CargoWatchProvider } from './commands/cargo_watch'; | 5 | import { CargoWatchProvider } from './commands/cargo_watch'; |
6 | import { ExpandMacroHoverProvider } from './commands/expand_macro' | ||
6 | import { HintsUpdater } from './commands/inlay_hints'; | 7 | import { HintsUpdater } from './commands/inlay_hints'; |
7 | import { | 8 | import { |
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 | ||