aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/Cargo.toml2
-rw-r--r--crates/assists/src/assist_context.rs64
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs16
-rw-r--r--crates/assists/src/handlers/extract_module_to_file.rs133
-rw-r--r--crates/assists/src/handlers/extract_variable.rs2
-rw-r--r--crates/assists/src/handlers/invert_if.rs9
-rw-r--r--crates/assists/src/handlers/remove_unused_param.rs81
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs31
-rw-r--r--crates/assists/src/lib.rs43
-rw-r--r--crates/assists/src/tests.rs72
-rw-r--r--crates/assists/src/tests/generated.rs31
-rw-r--r--crates/assists/src/utils.rs8
-rw-r--r--crates/assists/src/utils/import_assets.rs37
13 files changed, 375 insertions, 154 deletions
diff --git a/crates/assists/Cargo.toml b/crates/assists/Cargo.toml
index 3fd8327d6..ed8ad666f 100644
--- a/crates/assists/Cargo.toml
+++ b/crates/assists/Cargo.toml
@@ -11,7 +11,7 @@ doctest = false
11 11
12[dependencies] 12[dependencies]
13rustc-hash = "1.1.0" 13rustc-hash = "1.1.0"
14itertools = "0.9.0" 14itertools = "0.10.0"
15either = "1.6.1" 15either = "1.6.1"
16 16
17stdx = { path = "../stdx", version = "0.0.0" } 17stdx = { path = "../stdx", version = "0.0.0" }
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index 69499ea32..4f59d39a9 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -4,10 +4,10 @@ use std::mem;
4 4
5use algo::find_covering_element; 5use algo::find_covering_element;
6use hir::Semantics; 6use hir::Semantics;
7use ide_db::base_db::{FileId, FileRange}; 7use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange};
8use ide_db::{ 8use ide_db::{
9 label::Label, 9 label::Label,
10 source_change::{SourceChange, SourceFileEdit}, 10 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
11 RootDatabase, 11 RootDatabase,
12}; 12};
13use syntax::{ 13use syntax::{
@@ -19,7 +19,7 @@ use text_edit::{TextEdit, TextEditBuilder};
19 19
20use crate::{ 20use crate::{
21 assist_config::{AssistConfig, SnippetCap}, 21 assist_config::{AssistConfig, SnippetCap},
22 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist, 22 Assist, AssistId, AssistKind, GroupLabel,
23}; 23};
24 24
25/// `AssistContext` allows to apply an assist or check if it could be applied. 25/// `AssistContext` allows to apply an assist or check if it could be applied.
@@ -105,46 +105,23 @@ impl<'a> AssistContext<'a> {
105pub(crate) struct Assists { 105pub(crate) struct Assists {
106 resolve: bool, 106 resolve: bool,
107 file: FileId, 107 file: FileId,
108 buf: Vec<(Assist, Option<SourceChange>)>, 108 buf: Vec<Assist>,
109 allowed: Option<Vec<AssistKind>>, 109 allowed: Option<Vec<AssistKind>>,
110} 110}
111 111
112impl Assists { 112impl Assists {
113 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { 113 pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists {
114 Assists { 114 Assists {
115 resolve: true, 115 resolve,
116 file: ctx.frange.file_id, 116 file: ctx.frange.file_id,
117 buf: Vec::new(), 117 buf: Vec::new(),
118 allowed: ctx.config.allowed.clone(), 118 allowed: ctx.config.allowed.clone(),
119 } 119 }
120 } 120 }
121 121
122 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { 122 pub(crate) fn finish(mut self) -> Vec<Assist> {
123 Assists { 123 self.buf.sort_by_key(|assist| assist.target.len());
124 resolve: false, 124 self.buf
125 file: ctx.frange.file_id,
126 buf: Vec::new(),
127 allowed: ctx.config.allowed.clone(),
128 }
129 }
130
131 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
132 assert!(!self.resolve);
133 self.finish()
134 .into_iter()
135 .map(|(label, edit)| {
136 assert!(edit.is_none());
137 label
138 })
139 .collect()
140 }
141
142 pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
143 assert!(self.resolve);
144 self.finish()
145 .into_iter()
146 .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
147 .collect()
148 } 125 }
149 126
150 pub(crate) fn add( 127 pub(crate) fn add(
@@ -158,7 +135,7 @@ impl Assists {
158 return None; 135 return None;
159 } 136 }
160 let label = Label::new(label.into()); 137 let label = Label::new(label.into());
161 let assist = Assist { id, label, group: None, target }; 138 let assist = Assist { id, label, group: None, target, source_change: None };
162 self.add_impl(assist, f) 139 self.add_impl(assist, f)
163 } 140 }
164 141
@@ -174,11 +151,11 @@ impl Assists {
174 return None; 151 return None;
175 } 152 }
176 let label = Label::new(label.into()); 153 let label = Label::new(label.into());
177 let assist = Assist { id, label, group: Some(group.clone()), target }; 154 let assist = Assist { id, label, group: Some(group.clone()), target, source_change: None };
178 self.add_impl(assist, f) 155 self.add_impl(assist, f)
179 } 156 }
180 157
181 fn add_impl(&mut self, assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { 158 fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
182 let source_change = if self.resolve { 159 let source_change = if self.resolve {
183 let mut builder = AssistBuilder::new(self.file); 160 let mut builder = AssistBuilder::new(self.file);
184 f(&mut builder); 161 f(&mut builder);
@@ -186,16 +163,12 @@ impl Assists {
186 } else { 163 } else {
187 None 164 None
188 }; 165 };
166 assist.source_change = source_change.clone();
189 167
190 self.buf.push((assist, source_change)); 168 self.buf.push(assist);
191 Some(()) 169 Some(())
192 } 170 }
193 171
194 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
195 self.buf.sort_by_key(|(label, _edit)| label.target.len());
196 self.buf
197 }
198
199 fn is_allowed(&self, id: &AssistId) -> bool { 172 fn is_allowed(&self, id: &AssistId) -> bool {
200 match &self.allowed { 173 match &self.allowed {
201 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)), 174 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
@@ -209,6 +182,7 @@ pub(crate) struct AssistBuilder {
209 file_id: FileId, 182 file_id: FileId,
210 is_snippet: bool, 183 is_snippet: bool,
211 source_file_edits: Vec<SourceFileEdit>, 184 source_file_edits: Vec<SourceFileEdit>,
185 file_system_edits: Vec<FileSystemEdit>,
212} 186}
213 187
214impl AssistBuilder { 188impl AssistBuilder {
@@ -218,6 +192,7 @@ impl AssistBuilder {
218 file_id, 192 file_id,
219 is_snippet: false, 193 is_snippet: false,
220 source_file_edits: Vec::default(), 194 source_file_edits: Vec::default(),
195 file_system_edits: Vec::default(),
221 } 196 }
222 } 197 }
223 198
@@ -282,12 +257,17 @@ impl AssistBuilder {
282 algo::diff(&node, &new).into_text_edit(&mut self.edit); 257 algo::diff(&node, &new).into_text_edit(&mut self.edit);
283 } 258 }
284 } 259 }
260 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
261 let file_system_edit =
262 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() };
263 self.file_system_edits.push(file_system_edit);
264 }
285 265
286 fn finish(mut self) -> SourceChange { 266 fn finish(mut self) -> SourceChange {
287 self.commit(); 267 self.commit();
288 SourceChange { 268 SourceChange {
289 source_file_edits: mem::take(&mut self.source_file_edits), 269 source_file_edits: mem::take(&mut self.source_file_edits),
290 file_system_edits: Default::default(), 270 file_system_edits: mem::take(&mut self.file_system_edits),
291 is_snippet: self.is_snippet, 271 is_snippet: self.is_snippet,
292 } 272 }
293 } 273 }
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index e413505d3..7df05b841 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -15,7 +15,7 @@ use crate::{
15// 15//
16// ``` 16// ```
17// trait Trait<T> { 17// trait Trait<T> {
18// Type X; 18// type X;
19// fn foo(&self) -> T; 19// fn foo(&self) -> T;
20// fn bar(&self) {} 20// fn bar(&self) {}
21// } 21// }
@@ -27,14 +27,16 @@ use crate::{
27// -> 27// ->
28// ``` 28// ```
29// trait Trait<T> { 29// trait Trait<T> {
30// Type X; 30// type X;
31// fn foo(&self) -> T; 31// fn foo(&self) -> T;
32// fn bar(&self) {} 32// fn bar(&self) {}
33// } 33// }
34// 34//
35// impl Trait<u32> for () { 35// impl Trait<u32> for () {
36// $0type X;
37//
36// fn foo(&self) -> u32 { 38// fn foo(&self) -> u32 {
37// ${0:todo!()} 39// todo!()
38// } 40// }
39// } 41// }
40// ``` 42// ```
@@ -54,13 +56,13 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
54// 56//
55// ``` 57// ```
56// trait Trait { 58// trait Trait {
57// Type X; 59// type X;
58// fn foo(&self); 60// fn foo(&self);
59// fn bar(&self) {} 61// fn bar(&self) {}
60// } 62// }
61// 63//
62// impl Trait for () { 64// impl Trait for () {
63// Type X = (); 65// type X = ();
64// fn foo(&self) {}<|> 66// fn foo(&self) {}<|>
65// 67//
66// } 68// }
@@ -68,13 +70,13 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
68// -> 70// ->
69// ``` 71// ```
70// trait Trait { 72// trait Trait {
71// Type X; 73// type X;
72// fn foo(&self); 74// fn foo(&self);
73// fn bar(&self) {} 75// fn bar(&self) {}
74// } 76// }
75// 77//
76// impl Trait for () { 78// impl Trait for () {
77// Type X = (); 79// type X = ();
78// fn foo(&self) {} 80// fn foo(&self) {}
79// 81//
80// $0fn bar(&self) {} 82// $0fn bar(&self) {}
diff --git a/crates/assists/src/handlers/extract_module_to_file.rs b/crates/assists/src/handlers/extract_module_to_file.rs
new file mode 100644
index 000000000..50bf67ef7
--- /dev/null
+++ b/crates/assists/src/handlers/extract_module_to_file.rs
@@ -0,0 +1,133 @@
1use ast::edit::IndentLevel;
2use ide_db::base_db::AnchoredPathBuf;
3use syntax::{
4 ast::{self, edit::AstNodeEdit, NameOwner},
5 AstNode,
6};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10// Assist: extract_module_to_file
11//
12// This assist extract module to file.
13//
14// ```
15// mod foo {<|>
16// fn t() {}
17// }
18// ```
19// ->
20// ```
21// mod foo;
22// ```
23pub(crate) fn extract_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let module_ast = ctx.find_node_at_offset::<ast::Module>()?;
25 let module_name = module_ast.name()?;
26
27 let module_def = ctx.sema.to_def(&module_ast)?;
28 let parent_module = module_def.parent(ctx.db())?;
29
30 let module_items = module_ast.item_list()?;
31 let target = module_ast.syntax().text_range();
32 let anchor_file_id = ctx.frange.file_id;
33
34 acc.add(
35 AssistId("extract_module_to_file", AssistKind::RefactorExtract),
36 "Extract module to file",
37 target,
38 |builder| {
39 let path = {
40 let dir = match parent_module.name(ctx.db()) {
41 Some(name) if !parent_module.is_mod_rs(ctx.db()) => format!("{}/", name),
42 _ => String::new(),
43 };
44 format!("./{}{}.rs", dir, module_name)
45 };
46 let contents = {
47 let items = module_items.dedent(IndentLevel(1)).to_string();
48 let mut items =
49 items.trim_start_matches('{').trim_end_matches('}').trim().to_string();
50 if !items.is_empty() {
51 items.push('\n');
52 }
53 items
54 };
55
56 builder.replace(target, format!("mod {};", module_name));
57
58 let dst = AnchoredPathBuf { anchor: anchor_file_id, path };
59 builder.create_file(dst, contents);
60 },
61 )
62}
63
64#[cfg(test)]
65mod tests {
66 use crate::tests::check_assist;
67
68 use super::*;
69
70 #[test]
71 fn extract_from_root() {
72 check_assist(
73 extract_module_to_file,
74 r#"
75mod tests {<|>
76 #[test] fn t() {}
77}
78"#,
79 r#"
80//- /main.rs
81mod tests;
82//- /tests.rs
83#[test] fn t() {}
84"#,
85 );
86 }
87
88 #[test]
89 fn extract_from_submodule() {
90 check_assist(
91 extract_module_to_file,
92 r#"
93//- /main.rs
94mod submod;
95//- /submod.rs
96mod inner<|> {
97 fn f() {}
98}
99fn g() {}
100"#,
101 r#"
102//- /submod.rs
103mod inner;
104fn g() {}
105//- /submod/inner.rs
106fn f() {}
107"#,
108 );
109 }
110
111 #[test]
112 fn extract_from_mod_rs() {
113 check_assist(
114 extract_module_to_file,
115 r#"
116//- /main.rs
117mod submodule;
118//- /submodule/mod.rs
119mod inner<|> {
120 fn f() {}
121}
122fn g() {}
123"#,
124 r#"
125//- /submodule/mod.rs
126mod inner;
127fn g() {}
128//- /submodule/inner.rs
129fn f() {}
130"#,
131 );
132 }
133}
diff --git a/crates/assists/src/handlers/extract_variable.rs b/crates/assists/src/handlers/extract_variable.rs
index d2ae137cd..9957012fe 100644
--- a/crates/assists/src/handlers/extract_variable.rs
+++ b/crates/assists/src/handlers/extract_variable.rs
@@ -91,7 +91,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
91 // extra newlines in the indent block 91 // extra newlines in the indent block
92 let text = indent.text(); 92 let text = indent.text();
93 if text.starts_with('\n') { 93 if text.starts_with('\n') {
94 buf.push_str("\n"); 94 buf.push('\n');
95 buf.push_str(text.trim_start_matches('\n')); 95 buf.push_str(text.trim_start_matches('\n'));
96 } else { 96 } else {
97 buf.push_str(text); 97 buf.push_str(text);
diff --git a/crates/assists/src/handlers/invert_if.rs b/crates/assists/src/handlers/invert_if.rs
index 91e2f5c8c..f9c33b3f7 100644
--- a/crates/assists/src/handlers/invert_if.rs
+++ b/crates/assists/src/handlers/invert_if.rs
@@ -78,6 +78,15 @@ mod tests {
78 } 78 }
79 79
80 #[test] 80 #[test]
81 fn invert_if_remove_not_parentheses() {
82 check_assist(
83 invert_if,
84 "fn f() { i<|>f !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
85 "fn f() { if x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
86 )
87 }
88
89 #[test]
81 fn invert_if_remove_inequality() { 90 fn invert_if_remove_inequality() {
82 check_assist( 91 check_assist(
83 invert_if, 92 invert_if,
diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs
index 1ff5e92b0..f72dd49ed 100644
--- a/crates/assists/src/handlers/remove_unused_param.rs
+++ b/crates/assists/src/handlers/remove_unused_param.rs
@@ -2,9 +2,10 @@ use ide_db::{defs::Definition, search::Reference};
2use syntax::{ 2use syntax::{
3 algo::find_node_at_range, 3 algo::find_node_at_range,
4 ast::{self, ArgListOwner}, 4 ast::{self, ArgListOwner},
5 AstNode, SyntaxNode, TextRange, T, 5 AstNode, SyntaxKind, SyntaxNode, TextRange, T,
6}; 6};
7use test_utils::mark; 7use test_utils::mark;
8use SyntaxKind::WHITESPACE;
8 9
9use crate::{ 10use crate::{
10 assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists, 11 assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
@@ -56,7 +57,7 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Opt
56 "Remove unused parameter", 57 "Remove unused parameter",
57 param.syntax().text_range(), 58 param.syntax().text_range(),
58 |builder| { 59 |builder| {
59 builder.delete(range_with_coma(param.syntax())); 60 builder.delete(range_to_remove(param.syntax()));
60 for usage in fn_def.usages(&ctx.sema).all() { 61 for usage in fn_def.usages(&ctx.sema).all() {
61 process_usage(ctx, builder, usage, param_position); 62 process_usage(ctx, builder, usage, param_position);
62 } 63 }
@@ -80,19 +81,34 @@ fn process_usage(
80 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; 81 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
81 82
82 builder.edit_file(usage.file_range.file_id); 83 builder.edit_file(usage.file_range.file_id);
83 builder.delete(range_with_coma(arg.syntax())); 84 builder.delete(range_to_remove(arg.syntax()));
84 85
85 Some(()) 86 Some(())
86} 87}
87 88
88fn range_with_coma(node: &SyntaxNode) -> TextRange { 89fn range_to_remove(node: &SyntaxNode) -> TextRange {
89 let up_to = next_prev().find_map(|dir| { 90 let up_to_comma = next_prev().find_map(|dir| {
90 node.siblings_with_tokens(dir) 91 node.siblings_with_tokens(dir)
91 .filter_map(|it| it.into_token()) 92 .filter_map(|it| it.into_token())
92 .find(|it| it.kind() == T![,]) 93 .find(|it| it.kind() == T![,])
94 .map(|it| (dir, it))
93 }); 95 });
94 let up_to = up_to.map_or(node.text_range(), |it| it.text_range()); 96 if let Some((dir, token)) = up_to_comma {
95 node.text_range().cover(up_to) 97 if node.next_sibling().is_some() {
98 let up_to_space = token
99 .siblings_with_tokens(dir)
100 .skip(1)
101 .take_while(|it| it.kind() == WHITESPACE)
102 .last()
103 .and_then(|it| it.into_token());
104 return node
105 .text_range()
106 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
107 }
108 node.text_range().cover(token.text_range())
109 } else {
110 node.text_range()
111 }
96} 112}
97 113
98#[cfg(test)] 114#[cfg(test)]
@@ -119,6 +135,57 @@ fn b() { foo(9, ) }
119 } 135 }
120 136
121 #[test] 137 #[test]
138 fn remove_unused_first_param() {
139 check_assist(
140 remove_unused_param,
141 r#"
142fn foo(<|>x: i32, y: i32) { y; }
143fn a() { foo(1, 2) }
144fn b() { foo(1, 2,) }
145"#,
146 r#"
147fn foo(y: i32) { y; }
148fn a() { foo(2) }
149fn b() { foo(2,) }
150"#,
151 );
152 }
153
154 #[test]
155 fn remove_unused_single_param() {
156 check_assist(
157 remove_unused_param,
158 r#"
159fn foo(<|>x: i32) { 0; }
160fn a() { foo(1) }
161fn b() { foo(1, ) }
162"#,
163 r#"
164fn foo() { 0; }
165fn a() { foo() }
166fn b() { foo( ) }
167"#,
168 );
169 }
170
171 #[test]
172 fn remove_unused_surrounded_by_parms() {
173 check_assist(
174 remove_unused_param,
175 r#"
176fn foo(x: i32, <|>y: i32, z: i32) { x; }
177fn a() { foo(1, 2, 3) }
178fn b() { foo(1, 2, 3,) }
179"#,
180 r#"
181fn foo(x: i32, z: i32) { x; }
182fn a() { foo(1, 3) }
183fn b() { foo(1, 3,) }
184"#,
185 );
186 }
187
188 #[test]
122 fn remove_unused_qualified_call() { 189 fn remove_unused_qualified_call() {
123 check_assist( 190 check_assist(
124 remove_unused_param, 191 remove_unused_param,
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
index 4d6a1956b..cb7a5c104 100644
--- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -62,21 +62,22 @@ pub(crate) fn replace_derive_with_manual_impl(
62 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; 62 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
63 let current_crate = current_module.krate(); 63 let current_crate = current_module.krate();
64 64
65 let found_traits = 65 let found_traits = imports_locator::find_exact_imports(
66 imports_locator::find_exact_imports(&ctx.sema, current_crate, trait_token.text()) 66 &ctx.sema,
67 .filter_map( 67 current_crate,
68 |candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate { 68 trait_token.text().to_string(),
69 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), 69 )
70 _ => None, 70 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
71 }, 71 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
72 ) 72 _ => None,
73 .flat_map(|trait_| { 73 })
74 current_module 74 .flat_map(|trait_| {
75 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) 75 current_module
76 .as_ref() 76 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
77 .map(mod_path_to_ast) 77 .as_ref()
78 .zip(Some(trait_)) 78 .map(mod_path_to_ast)
79 }); 79 .zip(Some(trait_))
80 });
80 81
81 let mut no_traits_found = true; 82 let mut no_traits_found = true;
82 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { 83 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 6e736ccb3..fdec886e9 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -73,45 +73,32 @@ pub struct Assist {
73 /// Target ranges are used to sort assists: the smaller the target range, 73 /// Target ranges are used to sort assists: the smaller the target range,
74 /// the more specific assist is, and so it should be sorted first. 74 /// the more specific assist is, and so it should be sorted first.
75 pub target: TextRange, 75 pub target: TextRange,
76} 76 /// Computing source change sometimes is much more costly then computing the
77 77 /// other fields. Additionally, the actual change is not required to show
78#[derive(Debug, Clone)] 78 /// the lightbulb UI, it only is needed when the user tries to apply an
79pub struct ResolvedAssist { 79 /// assist. So, we compute it lazily: the API allow requesting assists with
80 pub assist: Assist, 80 /// or without source change. We could (and in fact, used to) distinguish
81 pub source_change: SourceChange, 81 /// between resolved and unresolved assists at the type level, but this is
82 /// cumbersome, especially if you want to embed an assist into another data
83 /// structure, such as a diagnostic.
84 pub source_change: Option<SourceChange>,
82} 85}
83 86
84impl Assist { 87impl Assist {
85 /// Return all the assists applicable at the given position. 88 /// Return all the assists applicable at the given position.
86 /// 89 pub fn get(
87 /// Assists are returned in the "unresolved" state, that is only labels are
88 /// returned, without actual edits.
89 pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> {
90 let sema = Semantics::new(db);
91 let ctx = AssistContext::new(sema, config, range);
92 let mut acc = Assists::new_unresolved(&ctx);
93 handlers::all().iter().for_each(|handler| {
94 handler(&mut acc, &ctx);
95 });
96 acc.finish_unresolved()
97 }
98
99 /// Return all the assists applicable at the given position.
100 ///
101 /// Assists are returned in the "resolved" state, that is with edit fully
102 /// computed.
103 pub fn resolved(
104 db: &RootDatabase, 90 db: &RootDatabase,
105 config: &AssistConfig, 91 config: &AssistConfig,
92 resolve: bool,
106 range: FileRange, 93 range: FileRange,
107 ) -> Vec<ResolvedAssist> { 94 ) -> Vec<Assist> {
108 let sema = Semantics::new(db); 95 let sema = Semantics::new(db);
109 let ctx = AssistContext::new(sema, config, range); 96 let ctx = AssistContext::new(sema, config, range);
110 let mut acc = Assists::new_resolved(&ctx); 97 let mut acc = Assists::new(&ctx, resolve);
111 handlers::all().iter().for_each(|handler| { 98 handlers::all().iter().for_each(|handler| {
112 handler(&mut acc, &ctx); 99 handler(&mut acc, &ctx);
113 }); 100 });
114 acc.finish_resolved() 101 acc.finish()
115 } 102 }
116} 103}
117 104
@@ -129,6 +116,7 @@ mod handlers {
129 mod convert_integer_literal; 116 mod convert_integer_literal;
130 mod early_return; 117 mod early_return;
131 mod expand_glob_import; 118 mod expand_glob_import;
119 mod extract_module_to_file;
132 mod extract_struct_from_enum_variant; 120 mod extract_struct_from_enum_variant;
133 mod extract_variable; 121 mod extract_variable;
134 mod fill_match_arms; 122 mod fill_match_arms;
@@ -179,6 +167,7 @@ mod handlers {
179 convert_integer_literal::convert_integer_literal, 167 convert_integer_literal::convert_integer_literal,
180 early_return::convert_to_guarded_return, 168 early_return::convert_to_guarded_return,
181 expand_glob_import::expand_glob_import, 169 expand_glob_import::expand_glob_import,
170 extract_module_to_file::extract_module_to_file,
182 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 171 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
183 extract_variable::extract_variable, 172 extract_variable::extract_variable,
184 fill_match_arms::fill_match_arms, 173 fill_match_arms::fill_match_arms,
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index 709a34d03..21e448fb8 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -2,6 +2,7 @@ mod generated;
2 2
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; 4use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
5use ide_db::source_change::FileSystemEdit;
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
6use syntax::TextRange; 7use syntax::TextRange;
7use test_utils::{assert_eq_text, extract_offset, extract_range}; 8use test_utils::{assert_eq_text, extract_offset, extract_range};
@@ -47,25 +48,29 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
47 let before = db.file_text(file_id).to_string(); 48 let before = db.file_text(file_id).to_string();
48 let frange = FileRange { file_id, range: selection.into() }; 49 let frange = FileRange { file_id, range: selection.into() };
49 50
50 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) 51 let assist = Assist::get(&db, &AssistConfig::default(), true, frange)
51 .into_iter() 52 .into_iter()
52 .find(|assist| assist.assist.id.0 == assist_id) 53 .find(|assist| assist.id.0 == assist_id)
53 .unwrap_or_else(|| { 54 .unwrap_or_else(|| {
54 panic!( 55 panic!(
55 "\n\nAssist is not applicable: {}\nAvailable assists: {}", 56 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
56 assist_id, 57 assist_id,
57 Assist::resolved(&db, &AssistConfig::default(), frange) 58 Assist::get(&db, &AssistConfig::default(), false, frange)
58 .into_iter() 59 .into_iter()
59 .map(|assist| assist.assist.id.0) 60 .map(|assist| assist.id.0)
60 .collect::<Vec<_>>() 61 .collect::<Vec<_>>()
61 .join(", ") 62 .join(", ")
62 ) 63 )
63 }); 64 });
64 65
65 let actual = { 66 let actual = {
66 let change = assist.source_change.source_file_edits.pop().unwrap(); 67 let source_change = assist.source_change.unwrap();
67 let mut actual = before; 68 let mut actual = before;
68 change.edit.apply(&mut actual); 69 for source_file_edit in source_change.source_file_edits {
70 if source_file_edit.file_id == file_id {
71 source_file_edit.edit.apply(&mut actual)
72 }
73 }
69 actual 74 actual
70 }; 75 };
71 assert_eq_text!(&after, &actual); 76 assert_eq_text!(&after, &actual);
@@ -86,20 +91,21 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
86 let sema = Semantics::new(&db); 91 let sema = Semantics::new(&db);
87 let config = AssistConfig::default(); 92 let config = AssistConfig::default();
88 let ctx = AssistContext::new(sema, &config, frange); 93 let ctx = AssistContext::new(sema, &config, frange);
89 let mut acc = Assists::new_resolved(&ctx); 94 let mut acc = Assists::new(&ctx, true);
90 handler(&mut acc, &ctx); 95 handler(&mut acc, &ctx);
91 let mut res = acc.finish_resolved(); 96 let mut res = acc.finish();
92 97
93 let assist = match assist_label { 98 let assist = match assist_label {
94 Some(label) => res.into_iter().find(|resolved| resolved.assist.label == label), 99 Some(label) => res.into_iter().find(|resolved| resolved.label == label),
95 None => res.pop(), 100 None => res.pop(),
96 }; 101 };
97 102
98 match (assist, expected) { 103 match (assist, expected) {
99 (Some(assist), ExpectedResult::After(after)) => { 104 (Some(assist), ExpectedResult::After(after)) => {
100 let mut source_change = assist.source_change; 105 let mut source_change = assist.source_change.unwrap();
101 assert!(!source_change.source_file_edits.is_empty()); 106 assert!(!source_change.source_file_edits.is_empty());
102 let skip_header = source_change.source_file_edits.len() == 1; 107 let skip_header = source_change.source_file_edits.len() == 1
108 && source_change.file_system_edits.len() == 0;
103 source_change.source_file_edits.sort_by_key(|it| it.file_id); 109 source_change.source_file_edits.sort_by_key(|it| it.file_id);
104 110
105 let mut buf = String::new(); 111 let mut buf = String::new();
@@ -115,10 +121,25 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
115 buf.push_str(&text); 121 buf.push_str(&text);
116 } 122 }
117 123
124 for file_system_edit in source_change.file_system_edits.clone() {
125 match file_system_edit {
126 FileSystemEdit::CreateFile { dst, initial_contents } => {
127 let sr = db.file_source_root(dst.anchor);
128 let sr = db.source_root(sr);
129 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
130 base.pop();
131 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
132 format_to!(buf, "//- {}\n", created_file_path);
133 buf.push_str(&initial_contents);
134 }
135 _ => (),
136 }
137 }
138
118 assert_eq_text!(after, &buf); 139 assert_eq_text!(after, &buf);
119 } 140 }
120 (Some(assist), ExpectedResult::Target(target)) => { 141 (Some(assist), ExpectedResult::Target(target)) => {
121 let range = assist.assist.target; 142 let range = assist.target;
122 assert_eq_text!(&text_without_caret[range], target); 143 assert_eq_text!(&text_without_caret[range], target);
123 } 144 }
124 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), 145 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
@@ -135,14 +156,11 @@ fn assist_order_field_struct() {
135 let (before_cursor_pos, before) = extract_offset(before); 156 let (before_cursor_pos, before) = extract_offset(before);
136 let (db, file_id) = with_single_file(&before); 157 let (db, file_id) = with_single_file(&before);
137 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; 158 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
138 let assists = Assist::resolved(&db, &AssistConfig::default(), frange); 159 let assists = Assist::get(&db, &AssistConfig::default(), false, frange);
139 let mut assists = assists.iter(); 160 let mut assists = assists.iter();
140 161
141 assert_eq!( 162 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
142 assists.next().expect("expected assist").assist.label, 163 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
143 "Change visibility to pub(crate)"
144 );
145 assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`");
146} 164}
147 165
148#[test] 166#[test]
@@ -158,11 +176,11 @@ fn assist_order_if_expr() {
158 let (range, before) = extract_range(before); 176 let (range, before) = extract_range(before);
159 let (db, file_id) = with_single_file(&before); 177 let (db, file_id) = with_single_file(&before);
160 let frange = FileRange { file_id, range }; 178 let frange = FileRange { file_id, range };
161 let assists = Assist::resolved(&db, &AssistConfig::default(), frange); 179 let assists = Assist::get(&db, &AssistConfig::default(), false, frange);
162 let mut assists = assists.iter(); 180 let mut assists = assists.iter();
163 181
164 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 182 assert_eq!(assists.next().expect("expected assist").label, "Extract into variable");
165 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); 183 assert_eq!(assists.next().expect("expected assist").label, "Replace with match");
166} 184}
167 185
168#[test] 186#[test]
@@ -183,27 +201,27 @@ fn assist_filter_works() {
183 let mut cfg = AssistConfig::default(); 201 let mut cfg = AssistConfig::default();
184 cfg.allowed = Some(vec![AssistKind::Refactor]); 202 cfg.allowed = Some(vec![AssistKind::Refactor]);
185 203
186 let assists = Assist::resolved(&db, &cfg, frange); 204 let assists = Assist::get(&db, &cfg, false, frange);
187 let mut assists = assists.iter(); 205 let mut assists = assists.iter();
188 206
189 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 207 assert_eq!(assists.next().expect("expected assist").label, "Extract into variable");
190 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); 208 assert_eq!(assists.next().expect("expected assist").label, "Replace with match");
191 } 209 }
192 210
193 { 211 {
194 let mut cfg = AssistConfig::default(); 212 let mut cfg = AssistConfig::default();
195 cfg.allowed = Some(vec![AssistKind::RefactorExtract]); 213 cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
196 let assists = Assist::resolved(&db, &cfg, frange); 214 let assists = Assist::get(&db, &cfg, false, frange);
197 assert_eq!(assists.len(), 1); 215 assert_eq!(assists.len(), 1);
198 216
199 let mut assists = assists.iter(); 217 let mut assists = assists.iter();
200 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 218 assert_eq!(assists.next().expect("expected assist").label, "Extract into variable");
201 } 219 }
202 220
203 { 221 {
204 let mut cfg = AssistConfig::default(); 222 let mut cfg = AssistConfig::default();
205 cfg.allowed = Some(vec![AssistKind::QuickFix]); 223 cfg.allowed = Some(vec![AssistKind::QuickFix]);
206 let assists = Assist::resolved(&db, &cfg, frange); 224 let assists = Assist::get(&db, &cfg, false, frange);
207 assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out"); 225 assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
208 } 226 }
209} 227}
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index cc7c4a343..d3dfe24e7 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -42,26 +42,26 @@ fn doctest_add_impl_default_members() {
42 "add_impl_default_members", 42 "add_impl_default_members",
43 r#####" 43 r#####"
44trait Trait { 44trait Trait {
45 Type X; 45 type X;
46 fn foo(&self); 46 fn foo(&self);
47 fn bar(&self) {} 47 fn bar(&self) {}
48} 48}
49 49
50impl Trait for () { 50impl Trait for () {
51 Type X = (); 51 type X = ();
52 fn foo(&self) {}<|> 52 fn foo(&self) {}<|>
53 53
54} 54}
55"#####, 55"#####,
56 r#####" 56 r#####"
57trait Trait { 57trait Trait {
58 Type X; 58 type X;
59 fn foo(&self); 59 fn foo(&self);
60 fn bar(&self) {} 60 fn bar(&self) {}
61} 61}
62 62
63impl Trait for () { 63impl Trait for () {
64 Type X = (); 64 type X = ();
65 fn foo(&self) {} 65 fn foo(&self) {}
66 66
67 $0fn bar(&self) {} 67 $0fn bar(&self) {}
@@ -76,7 +76,7 @@ fn doctest_add_impl_missing_members() {
76 "add_impl_missing_members", 76 "add_impl_missing_members",
77 r#####" 77 r#####"
78trait Trait<T> { 78trait Trait<T> {
79 Type X; 79 type X;
80 fn foo(&self) -> T; 80 fn foo(&self) -> T;
81 fn bar(&self) {} 81 fn bar(&self) {}
82} 82}
@@ -87,14 +87,16 @@ impl Trait<u32> for () {<|>
87"#####, 87"#####,
88 r#####" 88 r#####"
89trait Trait<T> { 89trait Trait<T> {
90 Type X; 90 type X;
91 fn foo(&self) -> T; 91 fn foo(&self) -> T;
92 fn bar(&self) {} 92 fn bar(&self) {}
93} 93}
94 94
95impl Trait<u32> for () { 95impl Trait<u32> for () {
96 $0type X;
97
96 fn foo(&self) -> u32 { 98 fn foo(&self) -> u32 {
97 ${0:todo!()} 99 todo!()
98 } 100 }
99} 101}
100"#####, 102"#####,
@@ -236,6 +238,21 @@ fn qux(bar: Bar, baz: Baz) {}
236} 238}
237 239
238#[test] 240#[test]
241fn doctest_extract_module_to_file() {
242 check_doc_test(
243 "extract_module_to_file",
244 r#####"
245mod foo {<|>
246 fn t() {}
247}
248"#####,
249 r#####"
250mod foo;
251"#####,
252 )
253}
254
255#[test]
239fn doctest_extract_struct_from_enum_variant() { 256fn doctest_extract_struct_from_enum_variant() {
240 check_doc_test( 257 check_doc_test(
241 "extract_struct_from_enum_variant", 258 "extract_struct_from_enum_variant",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index f2cacf7c8..d41084b59 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -232,7 +232,13 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
232 }; 232 };
233 Some(make::expr_method_call(receiver, method, arg_list)) 233 Some(make::expr_method_call(receiver, method, arg_list))
234 } 234 }
235 ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => pe.expr(), 235 ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => {
236 if let ast::Expr::ParenExpr(parexpr) = pe.expr()? {
237 parexpr.expr()
238 } else {
239 pe.expr()
240 }
241 }
236 // FIXME: 242 // FIXME:
237 // ast::Expr::Literal(true | false ) 243 // ast::Expr::Literal(true | false )
238 _ => None, 244 _ => None,
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
index ff5c0e78e..4ce82c1ba 100644
--- a/crates/assists/src/utils/import_assets.rs
+++ b/crates/assists/src/utils/import_assets.rs
@@ -179,25 +179,24 @@ impl ImportAssets {
179 } 179 }
180 }; 180 };
181 181
182 let mut res = 182 let mut res = imports_locator::find_exact_imports(
183 imports_locator::find_exact_imports(sema, current_crate, &self.get_search_query()) 183 sema,
184 .filter_map(filter) 184 current_crate,
185 .filter_map(|candidate| { 185 self.get_search_query().to_string(),
186 let item: hir::ItemInNs = candidate.either(Into::into, Into::into); 186 )
187 if let Some(prefix_kind) = prefixed { 187 .filter_map(filter)
188 self.module_with_name_to_import.find_use_path_prefixed( 188 .filter_map(|candidate| {
189 db, 189 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
190 item, 190 if let Some(prefix_kind) = prefixed {
191 prefix_kind, 191 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
192 ) 192 } else {
193 } else { 193 self.module_with_name_to_import.find_use_path(db, item)
194 self.module_with_name_to_import.find_use_path(db, item) 194 }
195 } 195 .map(|path| (path, item))
196 .map(|path| (path, item)) 196 })
197 }) 197 .filter(|(use_path, _)| use_path.len() > 1)
198 .filter(|(use_path, _)| use_path.len() > 1) 198 .take(20)
199 .take(20) 199 .collect::<Vec<_>>();
200 .collect::<Vec<_>>();
201 res.sort_by_key(|(path, _)| path.clone()); 200 res.sort_by_key(|(path, _)| path.clone());
202 res 201 res
203 } 202 }