aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r--crates/ra_assists/src/assists/add_custom_impl.rs206
-rw-r--r--crates/ra_assists/src/assists/add_import.rs18
-rw-r--r--crates/ra_assists/src/assists/add_new.rs86
-rw-r--r--crates/ra_assists/src/assists/early_return.rs10
4 files changed, 262 insertions, 58 deletions
diff --git a/crates/ra_assists/src/assists/add_custom_impl.rs b/crates/ra_assists/src/assists/add_custom_impl.rs
new file mode 100644
index 000000000..037306fd6
--- /dev/null
+++ b/crates/ra_assists/src/assists/add_custom_impl.rs
@@ -0,0 +1,206 @@
1//! FIXME: write short doc here
2
3use crate::{Assist, AssistCtx, AssistId};
4use hir::db::HirDatabase;
5use join_to_string::join;
6use ra_syntax::{
7 ast::{self, AstNode},
8 Direction, SmolStr,
9 SyntaxKind::{IDENT, WHITESPACE},
10 TextRange, TextUnit,
11};
12
13const DERIVE_TRAIT: &'static str = "derive";
14
15// Assist: add_custom_impl
16//
17// Adds impl block for derived trait.
18//
19// ```
20// #[derive(Deb<|>ug, Display)]
21// struct S;
22// ```
23// ->
24// ```
25// #[derive(Display)]
26// struct S;
27//
28// impl Debug for S {
29//
30// }
31// ```
32pub(crate) fn add_custom_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
33 let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
34 let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
35
36 let attr_name = attr
37 .syntax()
38 .descendants_with_tokens()
39 .filter(|t| t.kind() == IDENT)
40 .find_map(|i| i.into_token())
41 .filter(|t| *t.text() == DERIVE_TRAIT)?
42 .text()
43 .clone();
44
45 let trait_token =
46 ctx.token_at_offset().filter(|t| t.kind() == IDENT && *t.text() != attr_name).next()?;
47
48 let annotated = attr.syntax().siblings(Direction::Next).find_map(|s| ast::Name::cast(s))?;
49 let annotated_name = annotated.syntax().text().to_string();
50 let start_offset = annotated.syntax().parent()?.text_range().end();
51
52 ctx.add_assist(AssistId("add_custom_impl"), "add custom impl", |edit| {
53 edit.target(attr.syntax().text_range());
54
55 let new_attr_input = input
56 .syntax()
57 .descendants_with_tokens()
58 .filter(|t| t.kind() == IDENT)
59 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
60 .filter(|t| t != trait_token.text())
61 .collect::<Vec<SmolStr>>();
62 let has_more_derives = new_attr_input.len() > 0;
63 let new_attr_input =
64 join(new_attr_input.iter()).separator(", ").surround_with("(", ")").to_string();
65 let new_attr_input_len = new_attr_input.len();
66
67 let mut buf = String::new();
68 buf.push_str("\n\nimpl ");
69 buf.push_str(trait_token.text().as_str());
70 buf.push_str(" for ");
71 buf.push_str(annotated_name.as_str());
72 buf.push_str(" {\n");
73
74 let cursor_delta = if has_more_derives {
75 edit.replace(input.syntax().text_range(), new_attr_input);
76 input.syntax().text_range().len() - TextUnit::from_usize(new_attr_input_len)
77 } else {
78 let attr_range = attr.syntax().text_range();
79 edit.delete(attr_range);
80
81 let line_break_range = attr
82 .syntax()
83 .next_sibling_or_token()
84 .filter(|t| t.kind() == WHITESPACE)
85 .map(|t| t.text_range())
86 .unwrap_or(TextRange::from_to(TextUnit::from(0), TextUnit::from(0)));
87 edit.delete(line_break_range);
88
89 attr_range.len() + line_break_range.len()
90 };
91
92 edit.set_cursor(start_offset + TextUnit::of_str(&buf) - cursor_delta);
93 buf.push_str("\n}");
94 edit.insert(start_offset, buf);
95 })
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::helpers::{check_assist, check_assist_not_applicable};
102
103 #[test]
104 fn add_custom_impl_for_unique_input() {
105 check_assist(
106 add_custom_impl,
107 "
108#[derive(Debu<|>g)]
109struct Foo {
110 bar: String,
111}
112 ",
113 "
114struct Foo {
115 bar: String,
116}
117
118impl Debug for Foo {
119<|>
120}
121 ",
122 )
123 }
124
125 #[test]
126 fn add_custom_impl_for_with_visibility_modifier() {
127 check_assist(
128 add_custom_impl,
129 "
130#[derive(Debug<|>)]
131pub struct Foo {
132 bar: String,
133}
134 ",
135 "
136pub struct Foo {
137 bar: String,
138}
139
140impl Debug for Foo {
141<|>
142}
143 ",
144 )
145 }
146
147 #[test]
148 fn add_custom_impl_when_multiple_inputs() {
149 check_assist(
150 add_custom_impl,
151 "
152#[derive(Display, Debug<|>, Serialize)]
153struct Foo {}
154 ",
155 "
156#[derive(Display, Serialize)]
157struct Foo {}
158
159impl Debug for Foo {
160<|>
161}
162 ",
163 )
164 }
165
166 #[test]
167 fn test_ignore_derive_macro_without_input() {
168 check_assist_not_applicable(
169 add_custom_impl,
170 "
171#[derive(<|>)]
172struct Foo {}
173 ",
174 )
175 }
176
177 #[test]
178 fn test_ignore_if_cursor_on_param() {
179 check_assist_not_applicable(
180 add_custom_impl,
181 "
182#[derive<|>(Debug)]
183struct Foo {}
184 ",
185 );
186
187 check_assist_not_applicable(
188 add_custom_impl,
189 "
190#[derive(Debug)<|>]
191struct Foo {}
192 ",
193 )
194 }
195
196 #[test]
197 fn test_ignore_if_not_derive() {
198 check_assist_not_applicable(
199 add_custom_impl,
200 "
201#[allow(non_camel_<|>case_types)]
202struct Foo {}
203 ",
204 )
205 }
206}
diff --git a/crates/ra_assists/src/assists/add_import.rs b/crates/ra_assists/src/assists/add_import.rs
index 363ade016..b8752cbad 100644
--- a/crates/ra_assists/src/assists/add_import.rs
+++ b/crates/ra_assists/src/assists/add_import.rs
@@ -578,17 +578,21 @@ fn apply_auto_import(
578 578
579fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { 579fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
580 let mut ps = Vec::<SmolStr>::with_capacity(10); 580 let mut ps = Vec::<SmolStr>::with_capacity(10);
581 match path.kind { 581 match path.kind() {
582 hir::PathKind::Abs => ps.push("".into()), 582 hir::PathKind::Abs => ps.push("".into()),
583 hir::PathKind::Crate => ps.push("crate".into()), 583 hir::PathKind::Crate => ps.push("crate".into()),
584 hir::PathKind::Plain => {} 584 hir::PathKind::Plain => {}
585 hir::PathKind::Self_ => ps.push("self".into()), 585 hir::PathKind::Super(0) => ps.push("self".into()),
586 hir::PathKind::Super => ps.push("super".into()), 586 hir::PathKind::Super(lvl) => {
587 hir::PathKind::Type(_) | hir::PathKind::DollarCrate(_) => return None, 587 let mut chain = "super".to_string();
588 } 588 for _ in 0..*lvl {
589 for s in path.segments.iter() { 589 chain += "::super";
590 ps.push(s.name.to_string().into()); 590 }
591 ps.push(chain.into());
592 }
593 hir::PathKind::DollarCrate(_) => return None,
591 } 594 }
595 ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
592 Some(ps) 596 Some(ps)
593} 597}
594 598
diff --git a/crates/ra_assists/src/assists/add_new.rs b/crates/ra_assists/src/assists/add_new.rs
index 8f68bd5fb..b2f946fac 100644
--- a/crates/ra_assists/src/assists/add_new.rs
+++ b/crates/ra_assists/src/assists/add_new.rs
@@ -1,5 +1,5 @@
1use format_buf::format; 1use format_buf::format;
2use hir::{db::HirDatabase, FromSource}; 2use hir::{db::HirDatabase, FromSource, InFile};
3use join_to_string::join; 3use join_to_string::join;
4use ra_syntax::{ 4use ra_syntax::{
5 ast::{ 5 ast::{
@@ -56,42 +56,39 @@ pub(crate) fn add_new(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
56 let vis = vis.as_ref().map(String::as_str).unwrap_or(""); 56 let vis = vis.as_ref().map(String::as_str).unwrap_or("");
57 write!(&mut buf, " {}fn new(", vis).unwrap(); 57 write!(&mut buf, " {}fn new(", vis).unwrap();
58 58
59 join(field_list.fields().map(|f| { 59 join(field_list.fields().filter_map(|f| {
60 format!( 60 Some(format!("{}: {}", f.name()?.syntax().text(), f.ascribed_type()?.syntax().text()))
61 "{}: {}",
62 f.name().unwrap().syntax().text(),
63 f.ascribed_type().unwrap().syntax().text()
64 )
65 })) 61 }))
66 .separator(", ") 62 .separator(", ")
67 .to_buf(&mut buf); 63 .to_buf(&mut buf);
68 64
69 buf.push_str(") -> Self { Self {"); 65 buf.push_str(") -> Self { Self {");
70 66
71 join(field_list.fields().map(|f| f.name().unwrap().syntax().text())) 67 join(field_list.fields().filter_map(|f| Some(f.name()?.syntax().text())))
72 .separator(", ") 68 .separator(", ")
73 .surround_with(" ", " ") 69 .surround_with(" ", " ")
74 .to_buf(&mut buf); 70 .to_buf(&mut buf);
75 71
76 buf.push_str("} }"); 72 buf.push_str("} }");
77 73
78 let (start_offset, end_offset) = if let Some(impl_block) = impl_block { 74 let (start_offset, end_offset) = impl_block
79 buf.push('\n'); 75 .and_then(|impl_block| {
80 let start = impl_block 76 buf.push('\n');
81 .syntax() 77 let start = impl_block
82 .descendants_with_tokens() 78 .syntax()
83 .find(|t| t.kind() == T!['{']) 79 .descendants_with_tokens()
84 .unwrap() 80 .find(|t| t.kind() == T!['{'])?
85 .text_range() 81 .text_range()
86 .end(); 82 .end();
87 83
88 (start, TextUnit::from_usize(1)) 84 Some((start, TextUnit::from_usize(1)))
89 } else { 85 })
90 buf = generate_impl_text(&strukt, &buf); 86 .unwrap_or_else(|| {
91 let start = strukt.syntax().text_range().end(); 87 buf = generate_impl_text(&strukt, &buf);
92 88 let start = strukt.syntax().text_range().end();
93 (start, TextUnit::from_usize(3)) 89
94 }; 90 (start, TextUnit::from_usize(3))
91 });
95 92
96 edit.set_cursor(start_offset + TextUnit::of_str(&buf) - end_offset); 93 edit.set_cursor(start_offset + TextUnit::of_str(&buf) - end_offset);
97 edit.insert(start_offset, buf); 94 edit.insert(start_offset, buf);
@@ -141,44 +138,41 @@ fn find_struct_impl(
141 })?; 138 })?;
142 139
143 let struct_ty = { 140 let struct_ty = {
144 let src = hir::Source { file_id: ctx.frange.file_id.into(), value: strukt.clone() }; 141 let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() };
145 hir::Struct::from_source(db, src).unwrap().ty(db) 142 hir::Struct::from_source(db, src)?.ty(db)
146 }; 143 };
147 144
148 let mut found_new_fn = false; 145 let block = module.descendants().filter_map(ast::ImplBlock::cast).find_map(|impl_blk| {
149 146 let src = InFile { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() };
150 let block = module.descendants().filter_map(ast::ImplBlock::cast).find(|impl_blk| { 147 let blk = hir::ImplBlock::from_source(db, src)?;
151 if found_new_fn {
152 return false;
153 }
154
155 let src = hir::Source { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() };
156 let blk = hir::ImplBlock::from_source(db, src).unwrap();
157 148
158 let same_ty = blk.target_ty(db) == struct_ty; 149 let same_ty = blk.target_ty(db) == struct_ty;
159 let not_trait_impl = blk.target_trait(db).is_none(); 150 let not_trait_impl = blk.target_trait(db).is_none();
160 151
161 if !(same_ty && not_trait_impl) { 152 if !(same_ty && not_trait_impl) {
162 return false; 153 None
154 } else {
155 Some(impl_blk)
163 } 156 }
164
165 found_new_fn = has_new_fn(impl_blk);
166 true
167 }); 157 });
168 158
169 if found_new_fn { 159 if let Some(ref impl_blk) = block {
170 None 160 if has_new_fn(impl_blk) {
171 } else { 161 return None;
172 Some(block) 162 }
173 } 163 }
164
165 Some(block)
174} 166}
175 167
176fn has_new_fn(imp: &ast::ImplBlock) -> bool { 168fn has_new_fn(imp: &ast::ImplBlock) -> bool {
177 if let Some(il) = imp.item_list() { 169 if let Some(il) = imp.item_list() {
178 for item in il.impl_items() { 170 for item in il.impl_items() {
179 if let ast::ImplItem::FnDef(f) = item { 171 if let ast::ImplItem::FnDef(f) = item {
180 if f.name().unwrap().text().eq_ignore_ascii_case("new") { 172 if let Some(name) = f.name() {
181 return true; 173 if name.text().eq_ignore_ascii_case("new") {
174 return true;
175 }
182 } 176 }
183 } 177 }
184 } 178 }
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs
index 264412526..023917aca 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/assists/early_return.rs
@@ -83,8 +83,8 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
83 let parent_container = parent_block.syntax().parent()?.parent()?; 83 let parent_container = parent_block.syntax().parent()?.parent()?;
84 84
85 let early_expression: ast::Expr = match parent_container.kind() { 85 let early_expression: ast::Expr = match parent_container.kind() {
86 WHILE_EXPR | LOOP_EXPR => make::expr_continue().into(), 86 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
87 FN_DEF => make::expr_return().into(), 87 FN_DEF => make::expr_return(),
88 _ => return None, 88 _ => return None,
89 }; 89 };
90 90
@@ -116,13 +116,13 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
116 ) 116 )
117 .into(), 117 .into(),
118 ), 118 ),
119 make::expr_path(make::path_from_name_ref(make::name_ref("it"))).into(), 119 make::expr_path(make::path_from_name_ref(make::name_ref("it"))),
120 ); 120 );
121 121
122 let sad_arm = make::match_arm( 122 let sad_arm = make::match_arm(
123 // FIXME: would be cool to use `None` or `Err(_)` if appropriate 123 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
124 once(make::placeholder_pat().into()), 124 once(make::placeholder_pat().into()),
125 early_expression.into(), 125 early_expression,
126 ); 126 );
127 127
128 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) 128 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
@@ -130,7 +130,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
130 130
131 let let_stmt = make::let_stmt( 131 let let_stmt = make::let_stmt(
132 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 132 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
133 Some(match_expr.into()), 133 Some(match_expr),
134 ); 134 );
135 let let_stmt = if_indent_level.increase_indent(let_stmt); 135 let let_stmt = if_indent_level.increase_indent(let_stmt);
136 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 136 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)