diff options
-rw-r--r-- | .github/workflows/ci.yaml | 1 | ||||
-rw-r--r-- | Cargo.lock | 6 | ||||
-rw-r--r-- | crates/ra_hir/src/source_binder.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/quote.rs | 8 | ||||
-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 | ||||
-rw-r--r-- | crates/ra_syntax/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 33 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/extensions.rs | 2 | ||||
-rw-r--r-- | docs/user/features.md | 4 | ||||
-rw-r--r-- | editors/code/package.json | 5 | ||||
-rw-r--r-- | editors/code/src/commands/expand_macro.ts | 83 | ||||
-rw-r--r-- | editors/code/src/commands/index.ts | 2 | ||||
-rw-r--r-- | editors/code/src/extension.ts | 12 |
17 files changed, 358 insertions, 27 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b70d005fb..5bc41533c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml | |||
@@ -5,6 +5,7 @@ on: | |||
5 | branches: | 5 | branches: |
6 | - master | 6 | - master |
7 | - staging | 7 | - staging |
8 | - trying | ||
8 | 9 | ||
9 | jobs: | 10 | jobs: |
10 | rust: | 11 | rust: |
diff --git a/Cargo.lock b/Cargo.lock index fe08edd29..ef3735197 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1180,7 +1180,7 @@ dependencies = [ | |||
1180 | "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1180 | "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1181 | "ra_parser 0.1.0", | 1181 | "ra_parser 0.1.0", |
1182 | "ra_text_edit 0.1.0", | 1182 | "ra_text_edit 0.1.0", |
1183 | "rowan 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", | 1183 | "rowan 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1184 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1184 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1185 | "rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1185 | "rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1186 | "smol_str 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", | 1186 | "smol_str 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -1445,7 +1445,7 @@ dependencies = [ | |||
1445 | 1445 | ||
1446 | [[package]] | 1446 | [[package]] |
1447 | name = "rowan" | 1447 | name = "rowan" |
1448 | version = "0.6.3" | 1448 | version = "0.7.0" |
1449 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1450 | dependencies = [ | 1450 | dependencies = [ |
1451 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1451 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -1993,7 +1993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1993 | "checksum relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bedde000f40f2921ce439ea165c9c53fd629bfa115140c72e22aceacb4a21954" | 1993 | "checksum relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bedde000f40f2921ce439ea165c9c53fd629bfa115140c72e22aceacb4a21954" |
1994 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" | 1994 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" |
1995 | "checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" | 1995 | "checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" |
1996 | "checksum rowan 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fc3a6fb2a35518af7cab43ec4e21ca82eb086a8b3bb1739e426dc3923d459607" | 1996 | "checksum rowan 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a241900475bf2ba302061550ff50c82b45095ca95d23d1872345793fd42407" |
1997 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" | 1997 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" |
1998 | "checksum rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" | 1998 | "checksum rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" |
1999 | "checksum rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c86aae0c77166108c01305ee1a36a1e77289d7dc6ca0a3cd91ff4992de2d16a5" | 1999 | "checksum rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c86aae0c77166108c01305ee1a36a1e77289d7dc6ca0a3cd91ff4992de2d16a5" |
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_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs index 35133d216..65a35e52f 100644 --- a/crates/ra_hir_expand/src/quote.rs +++ b/crates/ra_hir_expand/src/quote.rs | |||
@@ -172,12 +172,12 @@ impl_to_to_tokentrees! { | |||
172 | u32 => self { tt::Literal{text: self.to_string().into()} }; | 172 | u32 => self { tt::Literal{text: self.to_string().into()} }; |
173 | usize => self { tt::Literal{text: self.to_string().into()}}; | 173 | usize => self { tt::Literal{text: self.to_string().into()}}; |
174 | i32 => self { tt::Literal{text: self.to_string().into()}}; | 174 | i32 => self { tt::Literal{text: self.to_string().into()}}; |
175 | &str => self { tt::Literal{text: self.to_string().into()}}; | ||
176 | String => self { tt::Literal{text: self.into()}}; | ||
177 | tt::Leaf => self { self }; | 175 | tt::Leaf => self { self }; |
178 | tt::Literal => self { self }; | 176 | tt::Literal => self { self }; |
179 | tt::Ident => self { self }; | 177 | tt::Ident => self { self }; |
180 | tt::Punct => self { self } | 178 | tt::Punct => self { self }; |
179 | &str => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into()}}; | ||
180 | String => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into()}} | ||
181 | } | 181 | } |
182 | 182 | ||
183 | #[cfg(test)] | 183 | #[cfg(test)] |
@@ -200,7 +200,7 @@ mod tests { | |||
200 | let a = 20; | 200 | let a = 20; |
201 | assert_eq!(quote!(#a).to_string(), "20"); | 201 | assert_eq!(quote!(#a).to_string(), "20"); |
202 | let s: String = "hello".into(); | 202 | let s: String = "hello".into(); |
203 | assert_eq!(quote!(#s).to_string(), "hello"); | 203 | assert_eq!(quote!(#s).to_string(), "\"hello\""); |
204 | } | 204 | } |
205 | 205 | ||
206 | fn mk_ident(name: &str) -> tt::Ident { | 206 | fn mk_ident(name: &str) -> tt::Ident { |
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 fcb3da90e..62ad996bc 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 3e2ac3683..0dc0aeee8 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -437,6 +437,7 @@ fn on_request( | |||
437 | })? | 437 | })? |
438 | .on::<req::AnalyzerStatus>(handlers::handle_analyzer_status)? | 438 | .on::<req::AnalyzerStatus>(handlers::handle_analyzer_status)? |
439 | .on::<req::SyntaxTree>(handlers::handle_syntax_tree)? | 439 | .on::<req::SyntaxTree>(handlers::handle_syntax_tree)? |
440 | .on::<req::ExpandMacro>(handlers::handle_expand_macro)? | ||
440 | .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? | 441 | .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? |
441 | .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? | 442 | .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? |
442 | .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)? | 443 | .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 7347a78c7..e552f2106 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 { |
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml index 45a18a73f..5db2b58c0 100644 --- a/crates/ra_syntax/Cargo.toml +++ b/crates/ra_syntax/Cargo.toml | |||
@@ -12,7 +12,7 @@ doctest = false | |||
12 | 12 | ||
13 | [dependencies] | 13 | [dependencies] |
14 | itertools = "0.8.0" | 14 | itertools = "0.8.0" |
15 | rowan = "0.6.1" | 15 | rowan = "0.7.0" |
16 | rustc_lexer = "0.1.0" | 16 | rustc_lexer = "0.1.0" |
17 | rustc-hash = "1.0.1" | 17 | rustc-hash = "1.0.1" |
18 | arrayvec = "0.5.1" | 18 | arrayvec = "0.5.1" |
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 7cfea70f9..1c075082a 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -134,23 +134,19 @@ pub fn insert_children( | |||
134 | to_green_element(element) | 134 | to_green_element(element) |
135 | }); | 135 | }); |
136 | 136 | ||
137 | let old_children = parent.green().children(); | 137 | let mut old_children = parent.green().children().map(|it| match it { |
138 | NodeOrToken::Token(it) => NodeOrToken::Token(it.clone()), | ||
139 | NodeOrToken::Node(it) => NodeOrToken::Node(it.clone()), | ||
140 | }); | ||
138 | 141 | ||
139 | let new_children = match &position { | 142 | let new_children = match &position { |
140 | InsertPosition::First => { | 143 | InsertPosition::First => to_insert.chain(old_children).collect::<Box<[_]>>(), |
141 | to_insert.chain(old_children.iter().cloned()).collect::<Box<[_]>>() | 144 | InsertPosition::Last => old_children.chain(to_insert).collect::<Box<[_]>>(), |
142 | } | ||
143 | InsertPosition::Last => old_children.iter().cloned().chain(to_insert).collect::<Box<[_]>>(), | ||
144 | InsertPosition::Before(anchor) | InsertPosition::After(anchor) => { | 145 | InsertPosition::Before(anchor) | InsertPosition::After(anchor) => { |
145 | let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 }; | 146 | let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 }; |
146 | let split_at = position_of_child(parent, anchor.clone()) + take_anchor; | 147 | let split_at = position_of_child(parent, anchor.clone()) + take_anchor; |
147 | let (before, after) = old_children.split_at(split_at); | 148 | let before = old_children.by_ref().take(split_at).collect::<Vec<_>>(); |
148 | before | 149 | before.into_iter().chain(to_insert).chain(old_children).collect::<Box<[_]>>() |
149 | .iter() | ||
150 | .cloned() | ||
151 | .chain(to_insert) | ||
152 | .chain(after.iter().cloned()) | ||
153 | .collect::<Box<[_]>>() | ||
154 | } | 150 | } |
155 | }; | 151 | }; |
156 | 152 | ||
@@ -168,13 +164,16 @@ pub fn replace_children( | |||
168 | ) -> SyntaxNode { | 164 | ) -> SyntaxNode { |
169 | let start = position_of_child(parent, to_delete.start().clone()); | 165 | let start = position_of_child(parent, to_delete.start().clone()); |
170 | let end = position_of_child(parent, to_delete.end().clone()); | 166 | let end = position_of_child(parent, to_delete.end().clone()); |
171 | let old_children = parent.green().children(); | 167 | let mut old_children = parent.green().children().map(|it| match it { |
168 | NodeOrToken::Token(it) => NodeOrToken::Token(it.clone()), | ||
169 | NodeOrToken::Node(it) => NodeOrToken::Node(it.clone()), | ||
170 | }); | ||
172 | 171 | ||
173 | let new_children = old_children[..start] | 172 | let before = old_children.by_ref().take(start).collect::<Vec<_>>(); |
174 | .iter() | 173 | let new_children = before |
175 | .cloned() | 174 | .into_iter() |
176 | .chain(to_insert.map(to_green_element)) | 175 | .chain(to_insert.map(to_green_element)) |
177 | .chain(old_children[end + 1..].iter().cloned()) | 176 | .chain(old_children.skip(end + 1 - start)) |
178 | .collect::<Box<[_]>>(); | 177 | .collect::<Box<[_]>>(); |
179 | with_children(parent, new_children) | 178 | with_children(parent, new_children) |
180 | } | 179 | } |
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index 761b2435c..4851dacb2 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs | |||
@@ -32,7 +32,7 @@ impl ast::NameRef { | |||
32 | } | 32 | } |
33 | 33 | ||
34 | fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { | 34 | fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { |
35 | node.green().children().first().and_then(|it| it.as_token()).unwrap().text() | 35 | node.green().children().next().and_then(|it| it.into_token()).unwrap().text() |
36 | } | 36 | } |
37 | 37 | ||
38 | impl ast::Attr { | 38 | impl ast::Attr { |
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. | |||
81 | Shows the parse tree of the current file. It exists mostly for debugging | 81 | Shows the parse tree of the current file. It exists mostly for debugging |
82 | rust-analyzer itself. | 82 | rust-analyzer itself. |
83 | 83 | ||
84 | #### Expand Macro Recursively | ||
85 | |||
86 | Shows the full macro expansion of the macro at current cursor. | ||
87 | |||
84 | #### Status | 88 | #### Status |
85 | 89 | ||
86 | Shows internal statistic about memory usage of rust-analyzer | 90 | Shows 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 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import { Position, TextDocumentIdentifier } from 'vscode-languageclient'; | ||
3 | import { Server } from '../server'; | ||
4 | |||
5 | export const expandMacroUri = vscode.Uri.parse( | ||
6 | 'rust-analyzer://expandMacro/[EXPANSION].rs' | ||
7 | ); | ||
8 | |||
9 | export 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` | ||
50 | export 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 | |||
66 | interface MacroExpandParams { | ||
67 | textDocument: TextDocumentIdentifier; | ||
68 | position: Position; | ||
69 | } | ||
70 | |||
71 | interface ExpandedMacro { | ||
72 | name: string; | ||
73 | expansion: string; | ||
74 | } | ||
75 | |||
76 | function 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 @@ | |||
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..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 | ||
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 { ExpandMacroContentProvider } 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, |
@@ -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), |