diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir/src/source_binder.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/expand_macro.rs | 178 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 18 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/req.rs | 22 |
6 files changed, 226 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 | |||
3 | use crate::{db::RootDatabase, FilePosition}; | ||
4 | use hir::db::AstDatabase; | ||
5 | use ra_db::SourceDatabase; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | |||
8 | use ra_syntax::{ | ||
9 | algo::{find_node_at_offset, replace_descendants}, | ||
10 | ast::{self}, | ||
11 | AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T, | ||
12 | }; | ||
13 | |||
14 | pub struct ExpandedMacro { | ||
15 | pub name: String, | ||
16 | pub expansion: String, | ||
17 | } | ||
18 | |||
19 | pub(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 | |||
35 | fn 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, ¯o_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. | ||
60 | fn 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)] | ||
119 | mod 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###" | ||
149 | fn 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###" | ||
172 | fn 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; | |||
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; |
@@ -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 | ||
50 | pub 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 | |||
50 | pub fn handle_selection_range( | 68 | pub 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")] | ||
50 | pub struct ExpandedMacro { | ||
51 | pub name: String, | ||
52 | pub expansion: String, | ||
53 | } | ||
54 | |||
55 | pub enum ExpandMacro {} | ||
56 | |||
57 | impl 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")] | ||
65 | pub struct ExpandMacroParams { | ||
66 | pub text_document: TextDocumentIdentifier, | ||
67 | pub position: Option<Position>, | ||
68 | } | ||
69 | |||
48 | pub enum SelectionRangeRequest {} | 70 | pub enum SelectionRangeRequest {} |
49 | 71 | ||
50 | impl Request for SelectionRangeRequest { | 72 | impl Request for SelectionRangeRequest { |