aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs67
-rw-r--r--crates/ra_assists/src/ast_transform.rs2
-rw-r--r--crates/ra_hir/src/code_model.rs24
-rw-r--r--crates/ra_hir_expand/src/lib.rs15
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs6
-rw-r--r--crates/ra_ide/src/extend_selection.rs168
-rw-r--r--crates/ra_ide/src/impls.rs12
-rw-r--r--crates/ra_syntax/src/ast/make.rs5
8 files changed, 263 insertions, 36 deletions
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs
index b75bd44eb..99d80998c 100644
--- a/crates/ra_assists/src/assists/fill_match_arms.rs
+++ b/crates/ra_assists/src/assists/fill_match_arms.rs
@@ -46,19 +46,24 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
46 }; 46 };
47 47
48 let expr = match_expr.expr()?; 48 let expr = match_expr.expr()?;
49 let enum_def = { 49 let (enum_def, module) = {
50 let analyzer = ctx.source_analyzer(expr.syntax(), None); 50 let analyzer = ctx.source_analyzer(expr.syntax(), None);
51 resolve_enum_def(ctx.db, &analyzer, &expr)? 51 (resolve_enum_def(ctx.db, &analyzer, &expr)?, analyzer.module()?)
52 }; 52 };
53 let variant_list = enum_def.variant_list()?; 53 let variants = enum_def.variants(ctx.db);
54 if variants.is_empty() {
55 return None;
56 }
57
58 let db = ctx.db;
54 59
55 ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| { 60 ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| {
56 let indent_level = IndentLevel::from_node(match_arm_list.syntax()); 61 let indent_level = IndentLevel::from_node(match_arm_list.syntax());
57 62
58 let new_arm_list = { 63 let new_arm_list = {
59 let variants = variant_list.variants();
60 let arms = variants 64 let arms = variants
61 .filter_map(build_pat) 65 .into_iter()
66 .filter_map(|variant| build_pat(db, module, variant))
62 .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())); 67 .map(|pat| make::match_arm(iter::once(pat), make::expr_unit()));
63 indent_level.increase_indent(make::match_arm_list(arms)) 68 indent_level.increase_indent(make::match_arm_list(arms))
64 }; 69 };
@@ -80,23 +85,25 @@ fn resolve_enum_def(
80 db: &impl HirDatabase, 85 db: &impl HirDatabase,
81 analyzer: &hir::SourceAnalyzer, 86 analyzer: &hir::SourceAnalyzer,
82 expr: &ast::Expr, 87 expr: &ast::Expr,
83) -> Option<ast::EnumDef> { 88) -> Option<hir::Enum> {
84 let expr_ty = analyzer.type_of(db, &expr)?; 89 let expr_ty = analyzer.type_of(db, &expr)?;
85 90
86 let res = expr_ty.autoderef(db).find_map(|ty| match ty.as_adt() { 91 let result = expr_ty.autoderef(db).find_map(|ty| match ty.as_adt() {
87 Some(Adt::Enum(e)) => Some(e.source(db).value), 92 Some(Adt::Enum(e)) => Some(e),
88 _ => None, 93 _ => None,
89 }); 94 });
90 res 95 result
91} 96}
92 97
93fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> { 98fn build_pat(
94 let path = make::path_qualified( 99 db: &impl HirDatabase,
95 make::path_from_name_ref(make::name_ref(&var.parent_enum().name()?.syntax().to_string())), 100 module: hir::Module,
96 make::name_ref(&var.name()?.syntax().to_string()), 101 var: hir::EnumVariant,
97 ); 102) -> Option<ast::Pat> {
103 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?);
98 104
99 let pat: ast::Pat = match var.kind() { 105 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
106 let pat: ast::Pat = match var.source(db).value.kind() {
100 ast::StructKind::Tuple(field_list) => { 107 ast::StructKind::Tuple(field_list) => {
101 let pats = 108 let pats =
102 iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count()); 109 iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count());
@@ -106,7 +113,7 @@ fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> {
106 let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into()); 113 let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into());
107 make::record_pat(path, pats).into() 114 make::record_pat(path, pats).into()
108 } 115 }
109 ast::StructKind::Unit => make::path_pat(path).into(), 116 ast::StructKind::Unit => make::path_pat(path),
110 }; 117 };
111 118
112 Some(pat) 119 Some(pat)
@@ -252,4 +259,32 @@ mod tests {
252 "#, 259 "#,
253 ); 260 );
254 } 261 }
262
263 #[test]
264 fn fill_match_arms_qualifies_path() {
265 check_assist(
266 fill_match_arms,
267 r#"
268 mod foo { pub enum E { X, Y } }
269 use foo::E::X;
270
271 fn main() {
272 match X {
273 <|>
274 }
275 }
276 "#,
277 r#"
278 mod foo { pub enum E { X, Y } }
279 use foo::E::X;
280
281 fn main() {
282 match <|>X {
283 X => (),
284 foo::E::Y => (),
285 }
286 }
287 "#,
288 );
289 }
255} 290}
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
index cbddc50ac..eac2903d1 100644
--- a/crates/ra_assists/src/ast_transform.rs
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -173,7 +173,7 @@ impl<'a, DB: HirDatabase> AstTransform<'a> for QualifyPaths<'a, DB> {
173 } 173 }
174} 174}
175 175
176fn path_to_ast(path: hir::ModPath) -> ast::Path { 176pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
177 let parse = ast::SourceFile::parse(&path.to_string()); 177 let parse = ast::SourceFile::parse(&path.to_string());
178 parse.tree().syntax().descendants().find_map(ast::Path::cast).unwrap() 178 parse.tree().syntax().descendants().find_map(ast::Path::cast).unwrap()
179} 179}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index df9c151e5..a177cebca 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -26,10 +26,11 @@ use hir_ty::{
26}; 26};
27use ra_db::{CrateId, Edition, FileId}; 27use ra_db::{CrateId, Edition, FileId};
28use ra_prof::profile; 28use ra_prof::profile;
29use ra_syntax::ast; 29use ra_syntax::ast::{self, AttrsOwner};
30 30
31use crate::{ 31use crate::{
32 db::{DefDatabase, HirDatabase}, 32 db::{DefDatabase, HirDatabase},
33 has_source::HasSource,
33 CallableDef, HirDisplay, InFile, Name, 34 CallableDef, HirDisplay, InFile, Name,
34}; 35};
35 36
@@ -805,6 +806,27 @@ impl ImplBlock {
805 pub fn krate(&self, db: &impl DefDatabase) -> Crate { 806 pub fn krate(&self, db: &impl DefDatabase) -> Crate {
806 Crate { id: self.module(db).id.krate } 807 Crate { id: self.module(db).id.krate }
807 } 808 }
809
810 pub fn is_builtin_derive(&self, db: &impl DefDatabase) -> Option<InFile<ast::Attr>> {
811 let src = self.source(db);
812 let item = src.file_id.is_builtin_derive(db)?;
813 let hygenic = hir_expand::hygiene::Hygiene::new(db, item.file_id);
814
815 let attr = item
816 .value
817 .attrs()
818 .filter_map(|it| {
819 let path = hir_def::path::ModPath::from_src(it.path()?, &hygenic)?;
820 if path.as_ident()?.to_string() == "derive" {
821 Some(it)
822 } else {
823 None
824 }
825 })
826 .last()?;
827
828 Some(item.with_value(attr))
829 }
808} 830}
809 831
810#[derive(Clone, PartialEq, Eq, Debug)] 832#[derive(Clone, PartialEq, Eq, Debug)]
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 51c5f9623..7cf3b59a7 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -112,6 +112,21 @@ impl HirFileId {
112 } 112 }
113 } 113 }
114 } 114 }
115
116 /// Indicate it is macro file generated for builtin derive
117 pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::ModuleItem>> {
118 match self.0 {
119 HirFileIdRepr::FileId(_) => None,
120 HirFileIdRepr::MacroFile(macro_file) => {
121 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
122 let item = match loc.def.kind {
123 MacroDefKind::BuiltInDerive(_) => loc.kind.node(db),
124 _ => return None,
125 };
126 Some(item.with_value(ast::ModuleItem::cast(item.value.clone())?))
127 }
128 }
129 }
115} 130}
116 131
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index f2e45fa31..b2af3479c 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -251,7 +251,11 @@ impl ToNav for hir::Module {
251impl ToNav for hir::ImplBlock { 251impl ToNav for hir::ImplBlock {
252 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 252 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
253 let src = self.source(db); 253 let src = self.source(db);
254 let frange = original_range(db, src.as_ref().map(|it| it.syntax())); 254 let frange = if let Some(item) = self.is_builtin_derive(db) {
255 original_range(db, item.syntax())
256 } else {
257 original_range(db, src.as_ref().map(|it| it.syntax()))
258 };
255 259
256 NavigationTarget::from_syntax( 260 NavigationTarget::from_syntax(
257 frange.file_id, 261 frange.file_id,
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs
index 1ec41a117..70b6fde82 100644
--- a/crates/ra_ide/src/extend_selection.rs
+++ b/crates/ra_ide/src/extend_selection.rs
@@ -4,20 +4,27 @@ use ra_db::SourceDatabase;
4use ra_syntax::{ 4use ra_syntax::{
5 algo::find_covering_element, 5 algo::find_covering_element,
6 ast::{self, AstNode, AstToken}, 6 ast::{self, AstNode, AstToken},
7 Direction, NodeOrToken, 7 Direction, NodeOrToken, SyntaxElement,
8 SyntaxKind::{self, *}, 8 SyntaxKind::{self, *},
9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, 9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
10}; 10};
11 11
12use crate::{db::RootDatabase, FileRange}; 12use crate::{db::RootDatabase, expand::descend_into_macros, FileId, FileRange};
13use hir::db::AstDatabase;
14use std::iter::successors;
13 15
14// FIXME: restore macro support
15pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 16pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
16 let parse = db.parse(frange.file_id); 17 let src = db.parse(frange.file_id).tree();
17 try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range) 18 try_extend_selection(db, src.syntax(), frange).unwrap_or(frange.range)
18} 19}
19 20
20fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { 21fn try_extend_selection(
22 db: &RootDatabase,
23 root: &SyntaxNode,
24 frange: FileRange,
25) -> Option<TextRange> {
26 let range = frange.range;
27
21 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; 28 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
22 let list_kinds = [ 29 let list_kinds = [
23 RECORD_FIELD_PAT_LIST, 30 RECORD_FIELD_PAT_LIST,
@@ -72,12 +79,21 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
72 } 79 }
73 NodeOrToken::Node(node) => node, 80 NodeOrToken::Node(node) => node,
74 }; 81 };
82
83 // if we are in single token_tree, we maybe live in macro or attr
84 if node.kind() == TOKEN_TREE {
85 if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
86 if let Some(range) = extend_tokens_from_range(db, frange.file_id, macro_call, range) {
87 return Some(range);
88 }
89 }
90 }
91
75 if node.text_range() != range { 92 if node.text_range() != range {
76 return Some(node.text_range()); 93 return Some(node.text_range());
77 } 94 }
78 95
79 // Using shallowest node with same range allows us to traverse siblings. 96 let node = shallowest_node(&node.into()).unwrap();
80 let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap();
81 97
82 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { 98 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
83 if let Some(range) = extend_list_item(&node) { 99 if let Some(range) = extend_list_item(&node) {
@@ -88,6 +104,94 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
88 node.parent().map(|it| it.text_range()) 104 node.parent().map(|it| it.text_range())
89} 105}
90 106
107fn extend_tokens_from_range(
108 db: &RootDatabase,
109 file_id: FileId,
110 macro_call: ast::MacroCall,
111 original_range: TextRange,
112) -> Option<TextRange> {
113 let src = find_covering_element(&macro_call.syntax(), original_range);
114 let (first_token, last_token) = match src {
115 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
116 NodeOrToken::Token(it) => (it.clone(), it),
117 };
118
119 let mut first_token = skip_whitespace(first_token, Direction::Next)?;
120 let mut last_token = skip_whitespace(last_token, Direction::Prev)?;
121
122 while !first_token.text_range().is_subrange(&original_range) {
123 first_token = skip_whitespace(first_token.next_token()?, Direction::Next)?;
124 }
125 while !last_token.text_range().is_subrange(&original_range) {
126 last_token = skip_whitespace(last_token.prev_token()?, Direction::Prev)?;
127 }
128
129 // compute original mapped token range
130 let expanded = {
131 let first_node = descend_into_macros(db, file_id, first_token.clone());
132 let first_node = first_node.map(|it| it.text_range());
133
134 let last_node = descend_into_macros(db, file_id, last_token.clone());
135 if last_node.file_id == file_id.into() || first_node.file_id != last_node.file_id {
136 return None;
137 }
138 first_node.map(|it| union_range(it, last_node.value.text_range()))
139 };
140
141 // Compute parent node range
142 let src = db.parse_or_expand(expanded.file_id)?;
143 let parent = shallowest_node(&find_covering_element(&src, expanded.value))?.parent()?;
144
145 let validate = |token: SyntaxToken| {
146 let node = descend_into_macros(db, file_id, token.clone());
147 if node.file_id == expanded.file_id
148 && node.value.text_range().is_subrange(&parent.text_range())
149 {
150 Some(token)
151 } else {
152 None
153 }
154 };
155
156 // Find the first and last text range under expanded parent
157 let first = successors(Some(first_token), |token| {
158 validate(skip_whitespace(token.prev_token()?, Direction::Prev)?)
159 })
160 .last()?;
161 let last = successors(Some(last_token), |token| {
162 validate(skip_whitespace(token.next_token()?, Direction::Next)?)
163 })
164 .last()?;
165
166 let range = union_range(first.text_range(), last.text_range());
167 if original_range.is_subrange(&range) && original_range != range {
168 Some(range)
169 } else {
170 None
171 }
172}
173
174fn skip_whitespace(mut token: SyntaxToken, direction: Direction) -> Option<SyntaxToken> {
175 while token.kind() == WHITESPACE {
176 token = match direction {
177 Direction::Next => token.next_token()?,
178 Direction::Prev => token.prev_token()?,
179 }
180 }
181 Some(token)
182}
183
184fn union_range(range: TextRange, r: TextRange) -> TextRange {
185 let start = range.start().min(r.start());
186 let end = range.end().max(r.end());
187 TextRange::from_to(start, end)
188}
189
190/// Find the shallowest node with same range, which allows us to traverse siblings.
191fn shallowest_node(node: &SyntaxElement) -> Option<SyntaxNode> {
192 node.ancestors().take_while(|n| n.text_range() == node.text_range()).last()
193}
194
91fn extend_single_word_in_comment_or_string( 195fn extend_single_word_in_comment_or_string(
92 leaf: &SyntaxToken, 196 leaf: &SyntaxToken,
93 offset: TextUnit, 197 offset: TextUnit,
@@ -227,18 +331,19 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
227 331
228#[cfg(test)] 332#[cfg(test)]
229mod tests { 333mod tests {
230 use ra_syntax::{AstNode, SourceFile};
231 use test_utils::extract_offset;
232
233 use super::*; 334 use super::*;
335 use crate::mock_analysis::single_file;
336 use test_utils::extract_offset;
234 337
235 fn do_check(before: &str, afters: &[&str]) { 338 fn do_check(before: &str, afters: &[&str]) {
236 let (cursor, before) = extract_offset(before); 339 let (cursor, before) = extract_offset(before);
237 let parse = SourceFile::parse(&before); 340 let (analysis, file_id) = single_file(&before);
238 let mut range = TextRange::offset_len(cursor, 0.into()); 341 let range = TextRange::offset_len(cursor, 0.into());
342 let mut frange = FileRange { file_id: file_id, range };
343
239 for &after in afters { 344 for &after in afters {
240 range = try_extend_selection(parse.tree().syntax(), range).unwrap(); 345 frange.range = analysis.extend_selection(frange).unwrap();
241 let actual = &before[range]; 346 let actual = &before[frange.range];
242 assert_eq!(after, actual); 347 assert_eq!(after, actual);
243 } 348 }
244 } 349 }
@@ -503,4 +608,37 @@ fn main() { let var = (
503 ], 608 ],
504 ); 609 );
505 } 610 }
611
612 #[test]
613 fn extend_selection_inside_macros() {
614 do_check(
615 r#"macro_rules! foo { ($item:item) => {$item} }
616 foo!{fn hello(na<|>me:usize){}}"#,
617 &[
618 "name",
619 "name:usize",
620 "(name:usize)",
621 "fn hello(name:usize){}",
622 "{fn hello(name:usize){}}",
623 "foo!{fn hello(name:usize){}}",
624 ],
625 );
626 }
627
628 #[test]
629 fn extend_selection_inside_recur_macros() {
630 do_check(
631 r#" macro_rules! foo2 { ($item:item) => {$item} }
632 macro_rules! foo { ($item:item) => {foo2!($item);} }
633 foo!{fn hello(na<|>me:usize){}}"#,
634 &[
635 "name",
636 "name:usize",
637 "(name:usize)",
638 "fn hello(name:usize){}",
639 "{fn hello(name:usize){}}",
640 "foo!{fn hello(name:usize){}}",
641 ],
642 );
643 }
506} 644}
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/impls.rs
index 9b165ee2a..31195036e 100644
--- a/crates/ra_ide/src/impls.rs
+++ b/crates/ra_ide/src/impls.rs
@@ -203,4 +203,16 @@ mod tests {
203 ], 203 ],
204 ); 204 );
205 } 205 }
206
207 #[test]
208 fn goto_implementation_to_builtin_derive() {
209 check_goto(
210 "
211 //- /lib.rs
212 #[derive(Copy)]
213 struct Foo<|>;
214 ",
215 &["impl IMPL_BLOCK FileId(1) [0; 15)"],
216 );
217 }
206} 218}
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 9781b748f..eef45090d 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -117,10 +117,11 @@ pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) ->
117 } 117 }
118} 118}
119 119
120pub fn path_pat(path: ast::Path) -> ast::PathPat { 120/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
121pub fn path_pat(path: ast::Path) -> ast::Pat {
121 let path_str = path.syntax().text().to_string(); 122 let path_str = path.syntax().text().to_string();
122 return from_text(path_str.as_str()); 123 return from_text(path_str.as_str());
123 fn from_text(text: &str) -> ast::PathPat { 124 fn from_text(text: &str) -> ast::Pat {
124 ast_from_text(&format!("fn f({}: ())", text)) 125 ast_from_text(&format!("fn f({}: ())", text))
125 } 126 }
126} 127}