aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml1
-rw-r--r--Cargo.lock6
-rw-r--r--crates/ra_hir/src/source_binder.rs2
-rw-r--r--crates/ra_hir_expand/src/quote.rs8
-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--crates/ra_syntax/Cargo.toml2
-rw-r--r--crates/ra_syntax/src/algo.rs33
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs2
-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
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
9jobs: 10jobs:
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]]
1447name = "rowan" 1447name = "rowan"
1448version = "0.6.3" 1448version = "0.7.0"
1449source = "registry+https://github.com/rust-lang/crates.io-index" 1449source = "registry+https://github.com/rust-lang/crates.io-index"
1450dependencies = [ 1450dependencies = [
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
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/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]
14itertools = "0.8.0" 14itertools = "0.8.0"
15rowan = "0.6.1" 15rowan = "0.7.0"
16rustc_lexer = "0.1.0" 16rustc_lexer = "0.1.0"
17rustc-hash = "1.0.1" 17rustc-hash = "1.0.1"
18arrayvec = "0.5.1" 18arrayvec = "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
34fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { 34fn 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
38impl ast::Attr { 38impl 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.
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 fbf675d46..e21dfa174 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -92,6 +92,11 @@
92 "category": "Rust Analyzer" 92 "category": "Rust Analyzer"
93 }, 93 },
94 { 94 {
95 "command": "rust-analyzer.expandMacro",
96 "title": "Expand macro recursively",
97 "category": "Rust Analyzer"
98 },
99 {
95 "command": "rust-analyzer.matchingBrace", 100 "command": "rust-analyzer.matchingBrace",
96 "title": "Find matching brace", 101 "title": "Find matching brace",
97 "category": "Rust Analyzer" 102 "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),