aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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
10 files changed, 248 insertions, 24 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_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 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;
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 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
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 {