aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_config.rs27
-rw-r--r--crates/ra_assists/src/assist_context.rs50
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs51
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs28
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs23
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs21
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs145
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs34
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs98
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs56
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs134
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs8
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs29
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs98
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs548
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs18
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs232
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs559
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs14
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs2
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs14
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs63
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs128
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs6
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs22
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs25
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs8
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs33
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs26
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs39
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs5
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs6
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs29
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs5
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs40
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs43
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs6
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs34
-rw-r--r--crates/ra_assists/src/lib.rs20
-rw-r--r--crates/ra_assists/src/marks.rs12
-rw-r--r--crates/ra_assists/src/tests.rs30
-rw-r--r--crates/ra_assists/src/tests/generated.rs68
-rw-r--r--crates/ra_assists/src/utils.rs78
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs6
-rw-r--r--crates/ra_hir_def/src/body/lower.rs4
-rw-r--r--crates/ra_hir_def/src/body/scope.rs4
-rw-r--r--crates/ra_hir_def/src/db.rs6
-rw-r--r--crates/ra_hir_def/src/find_path.rs100
-rw-r--r--crates/ra_hir_def/src/lib.rs2
-rw-r--r--crates/ra_hir_def/src/marks.rs17
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs14
-rw-r--r--crates/ra_hir_def/src/nameres/path_resolution.rs8
-rw-r--r--crates/ra_hir_def/src/nameres/raw.rs4
-rw-r--r--crates/ra_hir_def/src/nameres/tests.rs8
-rw-r--r--crates/ra_hir_def/src/nameres/tests/globs.rs7
-rw-r--r--crates/ra_hir_def/src/nameres/tests/macros.rs9
-rw-r--r--crates/ra_hir_def/src/nameres/tests/mod_resolution.rs2
-rw-r--r--crates/ra_hir_def/src/path/lower/lower_use.rs4
-rw-r--r--crates/ra_hir_ty/src/_match.rs39
-rw-r--r--crates/ra_hir_ty/src/infer.rs1
-rw-r--r--crates/ra_hir_ty/src/infer/coerce.rs6
-rw-r--r--crates/ra_hir_ty/src/infer/expr.rs33
-rw-r--r--crates/ra_hir_ty/src/infer/pat.rs4
-rw-r--r--crates/ra_hir_ty/src/infer/unify.rs8
-rw-r--r--crates/ra_hir_ty/src/lib.rs1
-rw-r--r--crates/ra_hir_ty/src/lower.rs2
-rw-r--r--crates/ra_hir_ty/src/marks.rs12
-rw-r--r--crates/ra_hir_ty/src/method_resolution.rs2
-rw-r--r--crates/ra_hir_ty/src/tests/coercion.rs6
-rw-r--r--crates/ra_hir_ty/src/tests/method_resolution.rs2
-rw-r--r--crates/ra_hir_ty/src/tests/patterns.rs4
-rw-r--r--crates/ra_hir_ty/src/tests/regression.rs15
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs63
-rw-r--r--crates/ra_hir_ty/src/tests/traits.rs8
-rw-r--r--crates/ra_ide/src/call_info.rs8
-rw-r--r--crates/ra_ide/src/completion.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs8
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs8
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs4
-rw-r--r--crates/ra_ide/src/completion/presentation.rs24
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs2
-rw-r--r--crates/ra_ide/src/diagnostics.rs4
-rw-r--r--crates/ra_ide/src/goto_definition.rs10
-rw-r--r--crates/ra_ide/src/join_lines.rs24
-rw-r--r--crates/ra_ide/src/lib.rs25
-rw-r--r--crates/ra_ide/src/marks.rs16
-rw-r--r--crates/ra_ide/src/parent_module.rs8
-rw-r--r--crates/ra_ide/src/references.rs3
-rw-r--r--crates/ra_ide/src/references/rename.rs22
-rw-r--r--crates/ra_ide/src/runnables.rs70
-rw-r--r--crates/ra_ide/src/test_utils.rs25
-rw-r--r--crates/ra_ide/src/typing.rs19
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs29
-rw-r--r--crates/ra_ide_db/src/defs.rs10
-rw-r--r--crates/ra_ide_db/src/lib.rs2
-rw-r--r--crates/ra_ide_db/src/line_index_utils.rs302
-rw-r--r--crates/ra_ide_db/src/marks.rs12
-rw-r--r--crates/ra_ide_db/src/search.rs2
-rw-r--r--crates/ra_ide_db/src/source_change.rs27
-rw-r--r--crates/ra_syntax/src/algo.rs36
-rw-r--r--crates/ra_syntax/src/ast/edit.rs44
-rw-r--r--crates/ra_syntax/src/ast/make.rs36
-rw-r--r--crates/ra_text_edit/src/lib.rs45
-rw-r--r--crates/rust-analyzer/src/caps.rs10
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs12
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs10
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap6
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap6
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs20
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs67
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs128
-rw-r--r--crates/rust-analyzer/src/to_proto.rs166
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs120
-rw-r--r--crates/stdx/src/lib.rs8
-rw-r--r--crates/test_utils/src/lib.rs2
-rw-r--r--crates/test_utils/src/mark.rs (renamed from crates/test_utils/src/marks.rs)46
-rw-r--r--docs/dev/README.md4
-rw-r--r--docs/dev/debugging.md6
-rw-r--r--docs/dev/lsp-extensions.md121
-rw-r--r--docs/user/assists.md66
-rw-r--r--docs/user/readme.adoc2
-rw-r--r--editors/code/package.json8
-rw-r--r--editors/code/src/cargo.ts12
-rw-r--r--editors/code/src/client.ts65
-rw-r--r--editors/code/src/commands/index.ts56
-rw-r--r--editors/code/src/commands/join_lines.ts12
-rw-r--r--editors/code/src/commands/on_enter.ts5
-rw-r--r--editors/code/src/commands/ssr.ts6
-rw-r--r--editors/code/src/main.ts1
-rw-r--r--editors/code/src/rust-analyzer-api.ts8
-rw-r--r--xtask/tests/tidy.rs1
133 files changed, 2768 insertions, 2353 deletions
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
new file mode 100644
index 000000000..c0a0226fb
--- /dev/null
+++ b/crates/ra_assists/src/assist_config.rs
@@ -0,0 +1,27 @@
1//! Settings for tweaking assists.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to.
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AssistConfig {
9 pub snippet_cap: Option<SnippetCap>,
10}
11
12impl AssistConfig {
13 pub fn allow_snippets(&mut self, yes: bool) {
14 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
15 }
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct SnippetCap {
20 _private: (),
21}
22
23impl Default for AssistConfig {
24 fn default() -> Self {
25 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) }
26 }
27}
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index a680f752b..f3af70a3e 100644
--- a/crates/ra_assists/src/assist_context.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -15,7 +15,10 @@ use ra_syntax::{
15}; 15};
16use ra_text_edit::TextEditBuilder; 16use ra_text_edit::TextEditBuilder;
17 17
18use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; 18use crate::{
19 assist_config::{AssistConfig, SnippetCap},
20 Assist, AssistId, GroupLabel, ResolvedAssist,
21};
19 22
20/// `AssistContext` allows to apply an assist or check if it could be applied. 23/// `AssistContext` allows to apply an assist or check if it could be applied.
21/// 24///
@@ -48,6 +51,7 @@ use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
48/// moment, because the LSP API is pretty awkward in this place, and it's much 51/// moment, because the LSP API is pretty awkward in this place, and it's much
49/// easier to just compute the edit eagerly :-) 52/// easier to just compute the edit eagerly :-)
50pub(crate) struct AssistContext<'a> { 53pub(crate) struct AssistContext<'a> {
54 pub(crate) config: &'a AssistConfig,
51 pub(crate) sema: Semantics<'a, RootDatabase>, 55 pub(crate) sema: Semantics<'a, RootDatabase>,
52 pub(crate) db: &'a RootDatabase, 56 pub(crate) db: &'a RootDatabase,
53 pub(crate) frange: FileRange, 57 pub(crate) frange: FileRange,
@@ -55,10 +59,14 @@ pub(crate) struct AssistContext<'a> {
55} 59}
56 60
57impl<'a> AssistContext<'a> { 61impl<'a> AssistContext<'a> {
58 pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> { 62 pub(crate) fn new(
63 sema: Semantics<'a, RootDatabase>,
64 config: &'a AssistConfig,
65 frange: FileRange,
66 ) -> AssistContext<'a> {
59 let source_file = sema.parse(frange.file_id); 67 let source_file = sema.parse(frange.file_id);
60 let db = sema.db; 68 let db = sema.db;
61 AssistContext { sema, db, frange, source_file } 69 AssistContext { config, sema, db, frange, source_file }
62 } 70 }
63 71
64 // NB, this ignores active selection. 72 // NB, this ignores active selection.
@@ -163,13 +171,13 @@ impl Assists {
163 171
164pub(crate) struct AssistBuilder { 172pub(crate) struct AssistBuilder {
165 edit: TextEditBuilder, 173 edit: TextEditBuilder,
166 cursor_position: Option<TextSize>,
167 file: FileId, 174 file: FileId,
175 is_snippet: bool,
168} 176}
169 177
170impl AssistBuilder { 178impl AssistBuilder {
171 pub(crate) fn new(file: FileId) -> AssistBuilder { 179 pub(crate) fn new(file: FileId) -> AssistBuilder {
172 AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file } 180 AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false }
173 } 181 }
174 182
175 /// Remove specified `range` of text. 183 /// Remove specified `range` of text.
@@ -180,10 +188,30 @@ impl AssistBuilder {
180 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { 188 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
181 self.edit.insert(offset, text.into()) 189 self.edit.insert(offset, text.into())
182 } 190 }
191 /// Append specified `snippet` at the given `offset`
192 pub(crate) fn insert_snippet(
193 &mut self,
194 _cap: SnippetCap,
195 offset: TextSize,
196 snippet: impl Into<String>,
197 ) {
198 self.is_snippet = true;
199 self.insert(offset, snippet);
200 }
183 /// Replaces specified `range` of text with a given string. 201 /// Replaces specified `range` of text with a given string.
184 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { 202 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
185 self.edit.replace(range, replace_with.into()) 203 self.edit.replace(range, replace_with.into())
186 } 204 }
205 /// Replaces specified `range` of text with a given `snippet`.
206 pub(crate) fn replace_snippet(
207 &mut self,
208 _cap: SnippetCap,
209 range: TextRange,
210 snippet: impl Into<String>,
211 ) {
212 self.is_snippet = true;
213 self.replace(range, snippet);
214 }
187 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 215 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
188 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) 216 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
189 } 217 }
@@ -207,10 +235,6 @@ impl AssistBuilder {
207 algo::diff(&node, &new).into_text_edit(&mut self.edit) 235 algo::diff(&node, &new).into_text_edit(&mut self.edit)
208 } 236 }
209 237
210 /// Specify desired position of the cursor after the assist is applied.
211 pub(crate) fn set_cursor(&mut self, offset: TextSize) {
212 self.cursor_position = Some(offset)
213 }
214 // FIXME: better API 238 // FIXME: better API
215 pub(crate) fn set_file(&mut self, assist_file: FileId) { 239 pub(crate) fn set_file(&mut self, assist_file: FileId) {
216 self.file = assist_file; 240 self.file = assist_file;
@@ -224,10 +248,10 @@ impl AssistBuilder {
224 248
225 fn finish(self, change_label: String) -> SourceChange { 249 fn finish(self, change_label: String) -> SourceChange {
226 let edit = self.edit.finish(); 250 let edit = self.edit.finish();
227 if edit.is_empty() && self.cursor_position.is_none() { 251 let mut res = SingleFileChange { label: change_label, edit }.into_source_change(self.file);
228 panic!("Only call `add_assist` if the assist can be applied") 252 if self.is_snippet {
253 res.is_snippet = true;
229 } 254 }
230 SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } 255 res
231 .into_source_change(self.file)
232 } 256 }
233} 257}
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index 2baeb8607..fa70c8496 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -25,7 +25,7 @@ use crate::{
25// struct S; 25// struct S;
26// 26//
27// impl Debug for S { 27// impl Debug for S {
28// 28// $0
29// } 29// }
30// ``` 30// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53 53
54 let target = attr.syntax().text_range(); 54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl"), label, target, |edit| { 55 acc.add(AssistId("add_custom_impl"), label, target, |builder| {
56 let new_attr_input = input 56 let new_attr_input = input
57 .syntax() 57 .syntax()
58 .descendants_with_tokens() 58 .descendants_with_tokens()
@@ -63,20 +63,11 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
63 let has_more_derives = !new_attr_input.is_empty(); 63 let has_more_derives = !new_attr_input.is_empty();
64 let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); 64 let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
65 65
66 let mut buf = String::new(); 66 if has_more_derives {
67 buf.push_str("\n\nimpl "); 67 builder.replace(input.syntax().text_range(), new_attr_input);
68 buf.push_str(trait_token.text().as_str());
69 buf.push_str(" for ");
70 buf.push_str(annotated_name.as_str());
71 buf.push_str(" {\n");
72
73 let cursor_delta = if has_more_derives {
74 let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input);
75 edit.replace(input.syntax().text_range(), new_attr_input);
76 delta
77 } else { 68 } else {
78 let attr_range = attr.syntax().text_range(); 69 let attr_range = attr.syntax().text_range();
79 edit.delete(attr_range); 70 builder.delete(attr_range);
80 71
81 let line_break_range = attr 72 let line_break_range = attr
82 .syntax() 73 .syntax()
@@ -84,14 +75,24 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
84 .filter(|t| t.kind() == WHITESPACE) 75 .filter(|t| t.kind() == WHITESPACE)
85 .map(|t| t.text_range()) 76 .map(|t| t.text_range())
86 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); 77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
87 edit.delete(line_break_range); 78 builder.delete(line_break_range);
88 79 }
89 attr_range.len() + line_break_range.len() 80
90 }; 81 match ctx.config.snippet_cap {
91 82 Some(cap) => {
92 edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta); 83 builder.insert_snippet(
93 buf.push_str("\n}"); 84 cap,
94 edit.insert(start_offset, buf); 85 start_offset,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
87 );
88 }
89 None => {
90 builder.insert(
91 start_offset,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93 );
94 }
95 }
95 }) 96 })
96} 97}
97 98
@@ -117,7 +118,7 @@ struct Foo {
117} 118}
118 119
119impl Debug for Foo { 120impl Debug for Foo {
120<|> 121 $0
121} 122}
122 ", 123 ",
123 ) 124 )
@@ -139,7 +140,7 @@ pub struct Foo {
139} 140}
140 141
141impl Debug for Foo { 142impl Debug for Foo {
142<|> 143 $0
143} 144}
144 ", 145 ",
145 ) 146 )
@@ -158,7 +159,7 @@ struct Foo {}
158struct Foo {} 159struct Foo {}
159 160
160impl Debug for Foo { 161impl Debug for Foo {
161<|> 162 $0
162} 163}
163 ", 164 ",
164 ) 165 )
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index fb08c19e9..b123b8498 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -18,31 +18,37 @@ use crate::{AssistContext, AssistId, Assists};
18// ``` 18// ```
19// -> 19// ->
20// ``` 20// ```
21// #[derive()] 21// #[derive($0)]
22// struct Point { 22// struct Point {
23// x: u32, 23// x: u32,
24// y: u32, 24// y: u32,
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let cap = ctx.config.snippet_cap?;
28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29 let node_start = derive_insertion_offset(&nominal)?; 30 let node_start = derive_insertion_offset(&nominal)?;
30 let target = nominal.syntax().text_range(); 31 let target = nominal.syntax().text_range();
31 acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { 32 acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| {
32 let derive_attr = nominal 33 let derive_attr = nominal
33 .attrs() 34 .attrs()
34 .filter_map(|x| x.as_simple_call()) 35 .filter_map(|x| x.as_simple_call())
35 .filter(|(name, _arg)| name == "derive") 36 .filter(|(name, _arg)| name == "derive")
36 .map(|(_name, arg)| arg) 37 .map(|(_name, arg)| arg)
37 .next(); 38 .next();
38 let offset = match derive_attr { 39 match derive_attr {
39 None => { 40 None => {
40 edit.insert(node_start, "#[derive()]\n"); 41 builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
41 node_start + TextSize::of("#[derive(") 42 }
43 Some(tt) => {
44 // Just move the cursor.
45 builder.insert_snippet(
46 cap,
47 tt.syntax().text_range().end() - TextSize::of(')'),
48 "$0",
49 )
42 } 50 }
43 Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
44 }; 51 };
45 edit.set_cursor(offset)
46 }) 52 })
47} 53}
48 54
@@ -66,12 +72,12 @@ mod tests {
66 check_assist( 72 check_assist(
67 add_derive, 73 add_derive,
68 "struct Foo { a: i32, <|>}", 74 "struct Foo { a: i32, <|>}",
69 "#[derive(<|>)]\nstruct Foo { a: i32, }", 75 "#[derive($0)]\nstruct Foo { a: i32, }",
70 ); 76 );
71 check_assist( 77 check_assist(
72 add_derive, 78 add_derive,
73 "struct Foo { <|> a: i32, }", 79 "struct Foo { <|> a: i32, }",
74 "#[derive(<|>)]\nstruct Foo { a: i32, }", 80 "#[derive($0)]\nstruct Foo { a: i32, }",
75 ); 81 );
76 } 82 }
77 83
@@ -80,7 +86,7 @@ mod tests {
80 check_assist( 86 check_assist(
81 add_derive, 87 add_derive,
82 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", 88 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
83 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", 89 "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
84 ); 90 );
85 } 91 }
86 92
@@ -96,7 +102,7 @@ struct Foo { a: i32<|>, }
96 " 102 "
97/// `Foo` is a pretty important struct. 103/// `Foo` is a pretty important struct.
98/// It does stuff. 104/// It does stuff.
99#[derive(<|>)] 105#[derive($0)]
100struct Foo { a: i32, } 106struct Foo { a: i32, }
101 ", 107 ",
102 ); 108 );
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index 0c7d5e355..ab20c6649 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -25,9 +25,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let module = ctx.sema.scope(stmt.syntax()).module()?; 26 let module = ctx.sema.scope(stmt.syntax()).module()?;
27 let expr = stmt.initializer()?; 27 let expr = stmt.initializer()?;
28 let pat = stmt.pat()?;
29 // Must be a binding 28 // Must be a binding
30 let pat = match pat { 29 let pat = match stmt.pat()? {
31 ast::Pat::BindPat(bind_pat) => bind_pat, 30 ast::Pat::BindPat(bind_pat) => bind_pat,
32 _ => return None, 31 _ => return None,
33 }; 32 };
@@ -46,7 +45,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
46 // Assist not applicable if the type has already been specified 45 // Assist not applicable if the type has already been specified
47 // and it has no placeholders 46 // and it has no placeholders
48 let ascribed_ty = stmt.ascribed_type(); 47 let ascribed_ty = stmt.ascribed_type();
49 if let Some(ref ty) = ascribed_ty { 48 if let Some(ty) = &ascribed_ty {
50 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { 49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
51 return None; 50 return None;
52 } 51 }
@@ -87,11 +86,7 @@ mod tests {
87 86
88 #[test] 87 #[test]
89 fn add_explicit_type_works_for_simple_expr() { 88 fn add_explicit_type_works_for_simple_expr() {
90 check_assist( 89 check_assist(add_explicit_type, "fn f() { let a<|> = 1; }", "fn f() { let a: i32 = 1; }");
91 add_explicit_type,
92 "fn f() { let a<|> = 1; }",
93 "fn f() { let a<|>: i32 = 1; }",
94 );
95 } 90 }
96 91
97 #[test] 92 #[test]
@@ -99,7 +94,7 @@ mod tests {
99 check_assist( 94 check_assist(
100 add_explicit_type, 95 add_explicit_type,
101 "fn f() { let a<|>: _ = 1; }", 96 "fn f() { let a<|>: _ = 1; }",
102 "fn f() { let a<|>: i32 = 1; }", 97 "fn f() { let a: i32 = 1; }",
103 ); 98 );
104 } 99 }
105 100
@@ -123,7 +118,7 @@ mod tests {
123 } 118 }
124 119
125 fn f() { 120 fn f() {
126 let a<|>: Option<i32> = Option::Some(1); 121 let a: Option<i32> = Option::Some(1);
127 }"#, 122 }"#,
128 ); 123 );
129 } 124 }
@@ -133,7 +128,7 @@ mod tests {
133 check_assist( 128 check_assist(
134 add_explicit_type, 129 add_explicit_type,
135 r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }", 130 r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
136 r"macro_rules! v { () => {0u64} } fn f() { let a<|>: u64 = v!(); }", 131 r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
137 ); 132 );
138 } 133 }
139 134
@@ -141,8 +136,8 @@ mod tests {
141 fn add_explicit_type_works_for_macro_call_recursive() { 136 fn add_explicit_type_works_for_macro_call_recursive() {
142 check_assist( 137 check_assist(
143 add_explicit_type, 138 add_explicit_type,
144 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }", 139 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#,
145 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|>: u64 = v!(); }", 140 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
146 ); 141 );
147 } 142 }
148 143
@@ -209,7 +204,7 @@ struct Test<K, T = u8> {
209} 204}
210 205
211fn main() { 206fn main() {
212 let test<|>: Test<i32> = Test { t: 23, k: 33 }; 207 let test: Test<i32> = Test { t: 23, k: 33 };
213}"#, 208}"#,
214 ); 209 );
215 } 210 }
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index 6a49b7dbd..6a675e812 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -1,7 +1,6 @@
1use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
2use ra_syntax::ast::{self, AstNode, NameOwner}; 2use ra_syntax::ast::{self, AstNode, NameOwner};
3use stdx::format_to; 3use test_utils::mark;
4use test_utils::tested_by;
5 4
6use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; 5use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
7 6
@@ -35,12 +34,12 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
35 } 34 }
36 let field_type = field_list.fields().next()?.type_ref()?; 35 let field_type = field_list.fields().next()?.type_ref()?;
37 let path = match field_type { 36 let path = match field_type {
38 ast::TypeRef::PathType(p) => p, 37 ast::TypeRef::PathType(it) => it,
39 _ => return None, 38 _ => return None,
40 }; 39 };
41 40
42 if existing_from_impl(&ctx.sema, &variant).is_some() { 41 if existing_from_impl(&ctx.sema, &variant).is_some() {
43 tested_by!(test_add_from_impl_already_exists); 42 mark::hit!(test_add_from_impl_already_exists);
44 return None; 43 return None;
45 } 44 }
46 45
@@ -51,9 +50,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
51 target, 50 target,
52 |edit| { 51 |edit| {
53 let start_offset = variant.parent_enum().syntax().text_range().end(); 52 let start_offset = variant.parent_enum().syntax().text_range().end();
54 let mut buf = String::new(); 53 let buf = format!(
55 format_to!(
56 buf,
57 r#" 54 r#"
58 55
59impl From<{0}> for {1} {{ 56impl From<{0}> for {1} {{
@@ -93,7 +90,7 @@ fn existing_from_impl(
93 90
94#[cfg(test)] 91#[cfg(test)]
95mod tests { 92mod tests {
96 use test_utils::covers; 93 use test_utils::mark;
97 94
98 use crate::tests::{check_assist, check_assist_not_applicable}; 95 use crate::tests::{check_assist, check_assist_not_applicable};
99 96
@@ -104,7 +101,7 @@ mod tests {
104 check_assist( 101 check_assist(
105 add_from_impl_for_enum, 102 add_from_impl_for_enum,
106 "enum A { <|>One(u32) }", 103 "enum A { <|>One(u32) }",
107 r#"enum A { <|>One(u32) } 104 r#"enum A { One(u32) }
108 105
109impl From<u32> for A { 106impl From<u32> for A {
110 fn from(v: u32) -> Self { 107 fn from(v: u32) -> Self {
@@ -119,7 +116,7 @@ impl From<u32> for A {
119 check_assist( 116 check_assist(
120 add_from_impl_for_enum, 117 add_from_impl_for_enum,
121 r#"enum A { <|>One(foo::bar::baz::Boo) }"#, 118 r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
122 r#"enum A { <|>One(foo::bar::baz::Boo) } 119 r#"enum A { One(foo::bar::baz::Boo) }
123 120
124impl From<foo::bar::baz::Boo> for A { 121impl From<foo::bar::baz::Boo> for A {
125 fn from(v: foo::bar::baz::Boo) -> Self { 122 fn from(v: foo::bar::baz::Boo) -> Self {
@@ -152,7 +149,7 @@ impl From<foo::bar::baz::Boo> for A {
152 149
153 #[test] 150 #[test]
154 fn test_add_from_impl_already_exists() { 151 fn test_add_from_impl_already_exists() {
155 covers!(test_add_from_impl_already_exists); 152 mark::check!(test_add_from_impl_already_exists);
156 check_not_applicable( 153 check_not_applicable(
157 r#" 154 r#"
158enum A { <|>One(u32), } 155enum A { <|>One(u32), }
@@ -181,7 +178,7 @@ impl From<String> for A {
181pub trait From<T> { 178pub trait From<T> {
182 fn from(T) -> Self; 179 fn from(T) -> Self;
183}"#, 180}"#,
184 r#"enum A { <|>One(u32), Two(String), } 181 r#"enum A { One(u32), Two(String), }
185 182
186impl From<u32> for A { 183impl From<u32> for A {
187 fn from(v: u32) -> Self { 184 fn from(v: u32) -> Self {
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index de016ae4e..24f931a85 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -4,13 +4,17 @@ use ra_syntax::{
4 ast::{ 4 ast::{
5 self, 5 self,
6 edit::{AstNodeEdit, IndentLevel}, 6 edit::{AstNodeEdit, IndentLevel},
7 ArgListOwner, AstNode, ModuleItemOwner, 7 make, ArgListOwner, AstNode, ModuleItemOwner,
8 }, 8 },
9 SyntaxKind, SyntaxNode, TextSize, 9 SyntaxKind, SyntaxNode, TextSize,
10}; 10};
11use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
12 12
13use crate::{AssistContext, AssistId, Assists}; 13use crate::{
14 assist_config::SnippetCap,
15 utils::{render_snippet, Cursor},
16 AssistContext, AssistId, Assists,
17};
14 18
15// Assist: add_function 19// Assist: add_function
16// 20//
@@ -33,7 +37,7 @@ use crate::{AssistContext, AssistId, Assists};
33// } 37// }
34// 38//
35// fn bar(arg: &str, baz: Baz) { 39// fn bar(arg: &str, baz: Baz) {
36// todo!() 40// ${0:todo!()}
37// } 41// }
38// 42//
39// ``` 43// ```
@@ -58,21 +62,40 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 62 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
59 63
60 let target = call.syntax().text_range(); 64 let target = call.syntax().text_range();
61 acc.add(AssistId("add_function"), "Add function", target, |edit| { 65 acc.add(AssistId("add_function"), "Add function", target, |builder| {
62 let function_template = function_builder.render(); 66 let function_template = function_builder.render();
63 edit.set_file(function_template.file); 67 builder.set_file(function_template.file);
64 edit.set_cursor(function_template.cursor_offset); 68 let new_fn = function_template.to_string(ctx.config.snippet_cap);
65 edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); 69 match ctx.config.snippet_cap {
70 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
71 None => builder.insert(function_template.insert_offset, new_fn),
72 }
66 }) 73 })
67} 74}
68 75
69struct FunctionTemplate { 76struct FunctionTemplate {
70 insert_offset: TextSize, 77 insert_offset: TextSize,
71 cursor_offset: TextSize, 78 placeholder_expr: ast::MacroCall,
72 fn_def: ast::SourceFile, 79 leading_ws: String,
80 fn_def: ast::FnDef,
81 trailing_ws: String,
73 file: FileId, 82 file: FileId,
74} 83}
75 84
85impl FunctionTemplate {
86 fn to_string(&self, cap: Option<SnippetCap>) -> String {
87 let f = match cap {
88 Some(cap) => render_snippet(
89 cap,
90 self.fn_def.syntax(),
91 Cursor::Replace(self.placeholder_expr.syntax()),
92 ),
93 None => self.fn_def.to_string(),
94 };
95 format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
96 }
97}
98
76struct FunctionBuilder { 99struct FunctionBuilder {
77 target: GeneratedFunctionTarget, 100 target: GeneratedFunctionTarget,
78 fn_name: ast::Name, 101 fn_name: ast::Name,
@@ -110,35 +133,41 @@ impl FunctionBuilder {
110 } 133 }
111 134
112 fn render(self) -> FunctionTemplate { 135 fn render(self) -> FunctionTemplate {
113 let placeholder_expr = ast::make::expr_todo(); 136 let placeholder_expr = make::expr_todo();
114 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); 137 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
115 let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); 138 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
116 if self.needs_pub { 139 let mut fn_def =
117 fn_def = ast::make::add_pub_crate_modifier(fn_def); 140 make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body);
118 } 141 let leading_ws;
119 142 let trailing_ws;
120 let (fn_def, insert_offset) = match self.target { 143
144 let insert_offset = match self.target {
121 GeneratedFunctionTarget::BehindItem(it) => { 145 GeneratedFunctionTarget::BehindItem(it) => {
122 let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); 146 let indent = IndentLevel::from_node(&it);
123 let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it)); 147 leading_ws = format!("\n\n{}", indent);
124 (indented, it.text_range().end()) 148 fn_def = fn_def.indent(indent);
149 trailing_ws = String::new();
150 it.text_range().end()
125 } 151 }
126 GeneratedFunctionTarget::InEmptyItemList(it) => { 152 GeneratedFunctionTarget::InEmptyItemList(it) => {
127 let indent_once = IndentLevel(1);
128 let indent = IndentLevel::from_node(it.syntax()); 153 let indent = IndentLevel::from_node(it.syntax());
129 let fn_def = ast::make::add_leading_newlines(1, fn_def); 154 leading_ws = format!("\n{}", indent + 1);
130 let fn_def = fn_def.indent(indent_once); 155 fn_def = fn_def.indent(indent + 1);
131 let fn_def = ast::make::add_trailing_newlines(1, fn_def); 156 trailing_ws = format!("\n{}", indent);
132 let fn_def = fn_def.indent(indent); 157 it.syntax().text_range().start() + TextSize::of('{')
133 (fn_def, it.syntax().text_range().start() + TextSize::of('{'))
134 } 158 }
135 }; 159 };
136 160
137 let placeholder_expr = 161 let placeholder_expr =
138 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); 162 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap();
139 let cursor_offset_from_fn_start = placeholder_expr.syntax().text_range().start(); 163 FunctionTemplate {
140 let cursor_offset = insert_offset + cursor_offset_from_fn_start; 164 insert_offset,
141 FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file } 165 placeholder_expr,
166 leading_ws,
167 fn_def,
168 trailing_ws,
169 file: self.file,
170 }
142 } 171 }
143} 172}
144 173
@@ -158,7 +187,7 @@ impl GeneratedFunctionTarget {
158 187
159fn fn_name(call: &ast::Path) -> Option<ast::Name> { 188fn fn_name(call: &ast::Path) -> Option<ast::Name> {
160 let name = call.segment()?.syntax().to_string(); 189 let name = call.segment()?.syntax().to_string();
161 Some(ast::make::name(&name)) 190 Some(make::name(&name))
162} 191}
163 192
164/// Computes the type variables and arguments required for the generated function 193/// Computes the type variables and arguments required for the generated function
@@ -180,8 +209,8 @@ fn fn_args(
180 }); 209 });
181 } 210 }
182 deduplicate_arg_names(&mut arg_names); 211 deduplicate_arg_names(&mut arg_names);
183 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); 212 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty));
184 Some((None, ast::make::param_list(params))) 213 Some((None, make::param_list(params)))
185} 214}
186 215
187/// Makes duplicate argument names unique by appending incrementing numbers. 216/// Makes duplicate argument names unique by appending incrementing numbers.
@@ -316,7 +345,7 @@ fn foo() {
316} 345}
317 346
318fn bar() { 347fn bar() {
319 <|>todo!() 348 ${0:todo!()}
320} 349}
321", 350",
322 ) 351 )
@@ -343,7 +372,7 @@ impl Foo {
343} 372}
344 373
345fn bar() { 374fn bar() {
346 <|>todo!() 375 ${0:todo!()}
347} 376}
348", 377",
349 ) 378 )
@@ -367,7 +396,7 @@ fn foo1() {
367} 396}
368 397
369fn bar() { 398fn bar() {
370 <|>todo!() 399 ${0:todo!()}
371} 400}
372 401
373fn foo2() {} 402fn foo2() {}
@@ -393,7 +422,7 @@ mod baz {
393 } 422 }
394 423
395 fn bar() { 424 fn bar() {
396 <|>todo!() 425 ${0:todo!()}
397 } 426 }
398} 427}
399", 428",
@@ -419,7 +448,7 @@ fn foo() {
419} 448}
420 449
421fn bar(baz: Baz) { 450fn bar(baz: Baz) {
422 <|>todo!() 451 ${0:todo!()}
423} 452}
424", 453",
425 ); 454 );
@@ -452,7 +481,7 @@ impl Baz {
452} 481}
453 482
454fn bar(baz: Baz) { 483fn bar(baz: Baz) {
455 <|>todo!() 484 ${0:todo!()}
456} 485}
457", 486",
458 ) 487 )
@@ -473,7 +502,7 @@ fn foo() {
473} 502}
474 503
475fn bar(arg: &str) { 504fn bar(arg: &str) {
476 <|>todo!() 505 ${0:todo!()}
477} 506}
478"#, 507"#,
479 ) 508 )
@@ -494,7 +523,7 @@ fn foo() {
494} 523}
495 524
496fn bar(arg: char) { 525fn bar(arg: char) {
497 <|>todo!() 526 ${0:todo!()}
498} 527}
499"#, 528"#,
500 ) 529 )
@@ -515,7 +544,7 @@ fn foo() {
515} 544}
516 545
517fn bar(arg: i32) { 546fn bar(arg: i32) {
518 <|>todo!() 547 ${0:todo!()}
519} 548}
520", 549",
521 ) 550 )
@@ -536,7 +565,7 @@ fn foo() {
536} 565}
537 566
538fn bar(arg: u8) { 567fn bar(arg: u8) {
539 <|>todo!() 568 ${0:todo!()}
540} 569}
541", 570",
542 ) 571 )
@@ -561,7 +590,7 @@ fn foo() {
561} 590}
562 591
563fn bar(x: u8) { 592fn bar(x: u8) {
564 <|>todo!() 593 ${0:todo!()}
565} 594}
566", 595",
567 ) 596 )
@@ -584,7 +613,7 @@ fn foo() {
584} 613}
585 614
586fn bar(worble: ()) { 615fn bar(worble: ()) {
587 <|>todo!() 616 ${0:todo!()}
588} 617}
589", 618",
590 ) 619 )
@@ -613,7 +642,7 @@ fn baz() {
613} 642}
614 643
615fn bar(foo: impl Foo) { 644fn bar(foo: impl Foo) {
616 <|>todo!() 645 ${0:todo!()}
617} 646}
618", 647",
619 ) 648 )
@@ -640,7 +669,7 @@ fn foo() {
640} 669}
641 670
642fn bar(baz: &Baz) { 671fn bar(baz: &Baz) {
643 <|>todo!() 672 ${0:todo!()}
644} 673}
645", 674",
646 ) 675 )
@@ -669,7 +698,7 @@ fn foo() {
669} 698}
670 699
671fn bar(baz: Baz::Bof) { 700fn bar(baz: Baz::Bof) {
672 <|>todo!() 701 ${0:todo!()}
673} 702}
674", 703",
675 ) 704 )
@@ -692,7 +721,7 @@ fn foo<T>(t: T) {
692} 721}
693 722
694fn bar<T>(t: T) { 723fn bar<T>(t: T) {
695 <|>todo!() 724 ${0:todo!()}
696} 725}
697", 726",
698 ) 727 )
@@ -723,7 +752,7 @@ fn foo() {
723} 752}
724 753
725fn bar(arg: fn() -> Baz) { 754fn bar(arg: fn() -> Baz) {
726 <|>todo!() 755 ${0:todo!()}
727} 756}
728", 757",
729 ) 758 )
@@ -748,7 +777,7 @@ fn foo() {
748} 777}
749 778
750fn bar(closure: impl Fn(i64) -> i64) { 779fn bar(closure: impl Fn(i64) -> i64) {
751 <|>todo!() 780 ${0:todo!()}
752} 781}
753", 782",
754 ) 783 )
@@ -769,7 +798,7 @@ fn foo() {
769} 798}
770 799
771fn bar(baz: ()) { 800fn bar(baz: ()) {
772 <|>todo!() 801 ${0:todo!()}
773} 802}
774", 803",
775 ) 804 )
@@ -794,7 +823,7 @@ fn foo() {
794} 823}
795 824
796fn bar(baz_1: Baz, baz_2: Baz) { 825fn bar(baz_1: Baz, baz_2: Baz) {
797 <|>todo!() 826 ${0:todo!()}
798} 827}
799", 828",
800 ) 829 )
@@ -819,7 +848,7 @@ fn foo() {
819} 848}
820 849
821fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { 850fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
822 <|>todo!() 851 ${0:todo!()}
823} 852}
824"#, 853"#,
825 ) 854 )
@@ -839,7 +868,7 @@ fn foo() {
839 r" 868 r"
840mod bar { 869mod bar {
841 pub(crate) fn my_fn() { 870 pub(crate) fn my_fn() {
842 <|>todo!() 871 ${0:todo!()}
843 } 872 }
844} 873}
845 874
@@ -878,7 +907,7 @@ fn bar() {
878} 907}
879 908
880fn baz(foo: foo::Foo) { 909fn baz(foo: foo::Foo) {
881 <|>todo!() 910 ${0:todo!()}
882} 911}
883", 912",
884 ) 913 )
@@ -902,7 +931,7 @@ mod bar {
902 fn something_else() {} 931 fn something_else() {}
903 932
904 pub(crate) fn my_fn() { 933 pub(crate) fn my_fn() {
905 <|>todo!() 934 ${0:todo!()}
906 } 935 }
907} 936}
908 937
@@ -930,7 +959,7 @@ fn foo() {
930mod bar { 959mod bar {
931 mod baz { 960 mod baz {
932 pub(crate) fn my_fn() { 961 pub(crate) fn my_fn() {
933 <|>todo!() 962 ${0:todo!()}
934 } 963 }
935 } 964 }
936} 965}
@@ -959,7 +988,7 @@ fn main() {
959 988
960 989
961pub(crate) fn bar() { 990pub(crate) fn bar() {
962 <|>todo!() 991 ${0:todo!()}
963}", 992}",
964 ) 993 )
965 } 994 }
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index df114a0d8..eceba7d0a 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -1,7 +1,4 @@
1use ra_syntax::{ 1use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
2 ast::{self, AstNode, NameOwner, TypeParamsOwner},
3 TextSize,
4};
5use stdx::{format_to, SepBy}; 2use stdx::{format_to, SepBy};
6 3
7use crate::{AssistContext, AssistId, Assists}; 4use crate::{AssistContext, AssistId, Assists};
@@ -12,17 +9,17 @@ use crate::{AssistContext, AssistId, Assists};
12// 9//
13// ``` 10// ```
14// struct Ctx<T: Clone> { 11// struct Ctx<T: Clone> {
15// data: T,<|> 12// data: T,<|>
16// } 13// }
17// ``` 14// ```
18// -> 15// ->
19// ``` 16// ```
20// struct Ctx<T: Clone> { 17// struct Ctx<T: Clone> {
21// data: T, 18// data: T,
22// } 19// }
23// 20//
24// impl<T: Clone> Ctx<T> { 21// impl<T: Clone> Ctx<T> {
25// 22// $0
26// } 23// }
27// ``` 24// ```
28pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 25pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -50,30 +47,37 @@ pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
50 let generic_params = lifetime_params.chain(type_params).sep_by(", "); 47 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
51 format_to!(buf, "<{}>", generic_params) 48 format_to!(buf, "<{}>", generic_params)
52 } 49 }
53 buf.push_str(" {\n"); 50 match ctx.config.snippet_cap {
54 edit.set_cursor(start_offset + TextSize::of(&buf)); 51 Some(cap) => {
55 buf.push_str("\n}"); 52 buf.push_str(" {\n $0\n}");
56 edit.insert(start_offset, buf); 53 edit.insert_snippet(cap, start_offset, buf);
54 }
55 None => {
56 buf.push_str(" {\n}");
57 edit.insert(start_offset, buf);
58 }
59 }
57 }) 60 })
58} 61}
59 62
60#[cfg(test)] 63#[cfg(test)]
61mod tests { 64mod tests {
62 use super::*;
63 use crate::tests::{check_assist, check_assist_target}; 65 use crate::tests::{check_assist, check_assist_target};
64 66
67 use super::*;
68
65 #[test] 69 #[test]
66 fn test_add_impl() { 70 fn test_add_impl() {
67 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); 71 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
68 check_assist( 72 check_assist(
69 add_impl, 73 add_impl,
70 "struct Foo<T: Clone> {<|>}", 74 "struct Foo<T: Clone> {<|>}",
71 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", 75 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
72 ); 76 );
73 check_assist( 77 check_assist(
74 add_impl, 78 add_impl,
75 "struct Foo<'a, T: Foo<'a>> {<|>}", 79 "struct Foo<'a, T: Foo<'a>> {<|>}",
76 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", 80 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
77 ); 81 );
78 } 82 }
79 83
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index 22e1156d2..abacd4065 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11use crate::{ 11use crate::{
12 assist_context::{AssistContext, Assists}, 12 assist_context::{AssistContext, Assists},
13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
14 utils::{get_missing_assoc_items, resolve_target_trait}, 14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
15 AssistId, 15 AssistId,
16}; 16};
17 17
@@ -46,7 +46,7 @@ enum AddMissingImplMembersMode {
46// 46//
47// impl Trait<u32> for () { 47// impl Trait<u32> for () {
48// fn foo(&self) -> u32 { 48// fn foo(&self) -> u32 {
49// todo!() 49// ${0:todo!()}
50// } 50// }
51// 51//
52// } 52// }
@@ -89,7 +89,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
89// impl Trait for () { 89// impl Trait for () {
90// Type X = (); 90// Type X = ();
91// fn foo(&self) {} 91// fn foo(&self) {}
92// fn bar(&self) {} 92// $0fn bar(&self) {}
93// 93//
94// } 94// }
95// ``` 95// ```
@@ -147,7 +147,7 @@ fn add_missing_impl_members_inner(
147 } 147 }
148 148
149 let target = impl_def.syntax().text_range(); 149 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id), label, target, |edit| { 150 acc.add(AssistId(assist_id), label, target, |builder| {
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
@@ -162,13 +162,29 @@ fn add_missing_impl_members_inner(
162 }) 162 })
163 .map(|it| edit::remove_attrs_and_docs(&it)); 163 .map(|it| edit::remove_attrs_and_docs(&it));
164 let new_impl_item_list = impl_item_list.append_items(items); 164 let new_impl_item_list = impl_item_list.append_items(items);
165 let cursor_position = { 165 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
166 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); 166
167 first_new_item.syntax().text_range().start() 167 let original_range = impl_item_list.syntax().text_range();
168 match ctx.config.snippet_cap {
169 None => builder.replace(original_range, new_impl_item_list.to_string()),
170 Some(cap) => {
171 let mut cursor = Cursor::Before(first_new_item.syntax());
172 let placeholder;
173 if let ast::AssocItem::FnDef(func) = &first_new_item {
174 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
175 if m.syntax().text() == "todo!()" {
176 placeholder = m;
177 cursor = Cursor::Replace(placeholder.syntax());
178 }
179 }
180 }
181 builder.replace_snippet(
182 cap,
183 original_range,
184 render_snippet(cap, new_impl_item_list.syntax(), cursor),
185 )
186 }
168 }; 187 };
169
170 edit.replace_ast(impl_item_list, new_impl_item_list);
171 edit.set_cursor(cursor_position);
172 }) 188 })
173} 189}
174 190
@@ -222,7 +238,7 @@ struct S;
222 238
223impl Foo for S { 239impl Foo for S {
224 fn bar(&self) {} 240 fn bar(&self) {}
225 <|>type Output; 241 $0type Output;
226 const CONST: usize = 42; 242 const CONST: usize = 42;
227 fn foo(&self) { 243 fn foo(&self) {
228 todo!() 244 todo!()
@@ -263,8 +279,8 @@ struct S;
263 279
264impl Foo for S { 280impl Foo for S {
265 fn bar(&self) {} 281 fn bar(&self) {}
266 <|>fn foo(&self) { 282 fn foo(&self) {
267 todo!() 283 ${0:todo!()}
268 } 284 }
269 285
270}"#, 286}"#,
@@ -283,8 +299,8 @@ impl Foo for S { <|> }"#,
283trait Foo { fn foo(&self); } 299trait Foo { fn foo(&self); }
284struct S; 300struct S;
285impl Foo for S { 301impl Foo for S {
286 <|>fn foo(&self) { 302 fn foo(&self) {
287 todo!() 303 ${0:todo!()}
288 } 304 }
289}"#, 305}"#,
290 ); 306 );
@@ -302,8 +318,8 @@ impl Foo<u32> for S { <|> }"#,
302trait Foo<T> { fn foo(&self, t: T) -> &T; } 318trait Foo<T> { fn foo(&self, t: T) -> &T; }
303struct S; 319struct S;
304impl Foo<u32> for S { 320impl Foo<u32> for S {
305 <|>fn foo(&self, t: u32) -> &u32 { 321 fn foo(&self, t: u32) -> &u32 {
306 todo!() 322 ${0:todo!()}
307 } 323 }
308}"#, 324}"#,
309 ); 325 );
@@ -321,8 +337,8 @@ impl<U> Foo<U> for S { <|> }"#,
321trait Foo<T> { fn foo(&self, t: T) -> &T; } 337trait Foo<T> { fn foo(&self, t: T) -> &T; }
322struct S; 338struct S;
323impl<U> Foo<U> for S { 339impl<U> Foo<U> for S {
324 <|>fn foo(&self, t: U) -> &U { 340 fn foo(&self, t: U) -> &U {
325 todo!() 341 ${0:todo!()}
326 } 342 }
327}"#, 343}"#,
328 ); 344 );
@@ -340,8 +356,8 @@ impl Foo for S {}<|>"#,
340trait Foo { fn foo(&self); } 356trait Foo { fn foo(&self); }
341struct S; 357struct S;
342impl Foo for S { 358impl Foo for S {
343 <|>fn foo(&self) { 359 fn foo(&self) {
344 todo!() 360 ${0:todo!()}
345 } 361 }
346}"#, 362}"#,
347 ) 363 )
@@ -365,8 +381,8 @@ mod foo {
365} 381}
366struct S; 382struct S;
367impl foo::Foo for S { 383impl foo::Foo for S {
368 <|>fn foo(&self, bar: foo::Bar) { 384 fn foo(&self, bar: foo::Bar) {
369 todo!() 385 ${0:todo!()}
370 } 386 }
371}"#, 387}"#,
372 ); 388 );
@@ -390,8 +406,8 @@ mod foo {
390} 406}
391struct S; 407struct S;
392impl foo::Foo for S { 408impl foo::Foo for S {
393 <|>fn foo(&self, bar: foo::Bar<u32>) { 409 fn foo(&self, bar: foo::Bar<u32>) {
394 todo!() 410 ${0:todo!()}
395 } 411 }
396}"#, 412}"#,
397 ); 413 );
@@ -415,8 +431,8 @@ mod foo {
415} 431}
416struct S; 432struct S;
417impl foo::Foo<u32> for S { 433impl foo::Foo<u32> for S {
418 <|>fn foo(&self, bar: foo::Bar<u32>) { 434 fn foo(&self, bar: foo::Bar<u32>) {
419 todo!() 435 ${0:todo!()}
420 } 436 }
421}"#, 437}"#,
422 ); 438 );
@@ -443,8 +459,8 @@ mod foo {
443struct Param; 459struct Param;
444struct S; 460struct S;
445impl foo::Foo<Param> for S { 461impl foo::Foo<Param> for S {
446 <|>fn foo(&self, bar: Param) { 462 fn foo(&self, bar: Param) {
447 todo!() 463 ${0:todo!()}
448 } 464 }
449}"#, 465}"#,
450 ); 466 );
@@ -470,8 +486,8 @@ mod foo {
470} 486}
471struct S; 487struct S;
472impl foo::Foo for S { 488impl foo::Foo for S {
473 <|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { 489 fn foo(&self, bar: foo::Bar<u32>::Assoc) {
474 todo!() 490 ${0:todo!()}
475 } 491 }
476}"#, 492}"#,
477 ); 493 );
@@ -497,8 +513,8 @@ mod foo {
497} 513}
498struct S; 514struct S;
499impl foo::Foo for S { 515impl foo::Foo for S {
500 <|>fn foo(&self, bar: foo::Bar<foo::Baz>) { 516 fn foo(&self, bar: foo::Bar<foo::Baz>) {
501 todo!() 517 ${0:todo!()}
502 } 518 }
503}"#, 519}"#,
504 ); 520 );
@@ -522,8 +538,8 @@ mod foo {
522} 538}
523struct S; 539struct S;
524impl foo::Foo for S { 540impl foo::Foo for S {
525 <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { 541 fn foo(&self, bar: dyn Fn(u32) -> i32) {
526 todo!() 542 ${0:todo!()}
527 } 543 }
528}"#, 544}"#,
529 ); 545 );
@@ -580,7 +596,7 @@ trait Foo {
580} 596}
581struct S; 597struct S;
582impl Foo for S { 598impl Foo for S {
583 <|>type Output; 599 $0type Output;
584 fn foo(&self) { 600 fn foo(&self) {
585 todo!() 601 todo!()
586 } 602 }
@@ -614,7 +630,7 @@ trait Foo {
614} 630}
615struct S; 631struct S;
616impl Foo for S { 632impl Foo for S {
617 <|>fn valid(some: u32) -> bool { false } 633 $0fn valid(some: u32) -> bool { false }
618}"#, 634}"#,
619 ) 635 )
620 } 636 }
@@ -637,8 +653,8 @@ trait Foo<T = Self> {
637 653
638struct S; 654struct S;
639impl Foo for S { 655impl Foo for S {
640 <|>fn bar(&self, other: &Self) { 656 fn bar(&self, other: &Self) {
641 todo!() 657 ${0:todo!()}
642 } 658 }
643}"#, 659}"#,
644 ) 660 )
@@ -662,8 +678,8 @@ trait Foo<T1, T2 = Self> {
662 678
663struct S<T>; 679struct S<T>;
664impl Foo<T> for S<T> { 680impl Foo<T> for S<T> {
665 <|>fn bar(&self, this: &T, that: &Self) { 681 fn bar(&self, this: &T, that: &Self) {
666 todo!() 682 ${0:todo!()}
667 } 683 }
668}"#, 684}"#,
669 ) 685 )
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index fe7451dcf..837aa8377 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 ast::{ 3 ast::{
4 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, 4 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
5 }, 5 },
6 TextSize, T, 6 T,
7}; 7};
8use stdx::{format_to, SepBy}; 8use stdx::{format_to, SepBy};
9 9
@@ -25,7 +25,7 @@ use crate::{AssistContext, AssistId, Assists};
25// } 25// }
26// 26//
27// impl<T: Clone> Ctx<T> { 27// impl<T: Clone> Ctx<T> {
28// fn new(data: T) -> Self { Self { data } } 28// fn $0new(data: T) -> Self { Self { data } }
29// } 29// }
30// 30//
31// ``` 31// ```
@@ -42,31 +42,26 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 42 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 43
44 let target = strukt.syntax().text_range(); 44 let target = strukt.syntax().text_range();
45 acc.add(AssistId("add_new"), "Add default constructor", target, |edit| { 45 acc.add(AssistId("add_new"), "Add default constructor", target, |builder| {
46 let mut buf = String::with_capacity(512); 46 let mut buf = String::with_capacity(512);
47 47
48 if impl_def.is_some() { 48 if impl_def.is_some() {
49 buf.push('\n'); 49 buf.push('\n');
50 } 50 }
51 51
52 let vis = strukt.visibility().map(|v| format!("{} ", v)); 52 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
53 let vis = vis.as_deref().unwrap_or("");
54 53
55 let params = field_list 54 let params = field_list
56 .fields() 55 .fields()
57 .filter_map(|f| { 56 .filter_map(|f| {
58 Some(format!( 57 Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
59 "{}: {}",
60 f.name()?.syntax().text(),
61 f.ascribed_type()?.syntax().text()
62 ))
63 }) 58 })
64 .sep_by(", "); 59 .sep_by(", ");
65 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); 60 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
66 61
67 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); 62 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
68 63
69 let (start_offset, end_offset) = impl_def 64 let start_offset = impl_def
70 .and_then(|impl_def| { 65 .and_then(|impl_def| {
71 buf.push('\n'); 66 buf.push('\n');
72 let start = impl_def 67 let start = impl_def
@@ -76,17 +71,20 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
76 .text_range() 71 .text_range()
77 .end(); 72 .end();
78 73
79 Some((start, TextSize::of("\n"))) 74 Some(start)
80 }) 75 })
81 .unwrap_or_else(|| { 76 .unwrap_or_else(|| {
82 buf = generate_impl_text(&strukt, &buf); 77 buf = generate_impl_text(&strukt, &buf);
83 let start = strukt.syntax().text_range().end(); 78 strukt.syntax().text_range().end()
84
85 (start, TextSize::of("\n}\n"))
86 }); 79 });
87 80
88 edit.set_cursor(start_offset + TextSize::of(&buf) - end_offset); 81 match ctx.config.snippet_cap {
89 edit.insert(start_offset, buf); 82 None => builder.insert(start_offset, buf),
83 Some(cap) => {
84 buf = buf.replace("fn new", "fn $0new");
85 builder.insert_snippet(cap, start_offset, buf);
86 }
87 }
90 }) 88 })
91} 89}
92 90
@@ -191,7 +189,7 @@ mod tests {
191"struct Foo {} 189"struct Foo {}
192 190
193impl Foo { 191impl Foo {
194 fn new() -> Self { Self { } }<|> 192 fn $0new() -> Self { Self { } }
195} 193}
196", 194",
197 ); 195 );
@@ -201,7 +199,7 @@ impl Foo {
201"struct Foo<T: Clone> {} 199"struct Foo<T: Clone> {}
202 200
203impl<T: Clone> Foo<T> { 201impl<T: Clone> Foo<T> {
204 fn new() -> Self { Self { } }<|> 202 fn $0new() -> Self { Self { } }
205} 203}
206", 204",
207 ); 205 );
@@ -211,7 +209,7 @@ impl<T: Clone> Foo<T> {
211"struct Foo<'a, T: Foo<'a>> {} 209"struct Foo<'a, T: Foo<'a>> {}
212 210
213impl<'a, T: Foo<'a>> Foo<'a, T> { 211impl<'a, T: Foo<'a>> Foo<'a, T> {
214 fn new() -> Self { Self { } }<|> 212 fn $0new() -> Self { Self { } }
215} 213}
216", 214",
217 ); 215 );
@@ -221,7 +219,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> {
221"struct Foo { baz: String } 219"struct Foo { baz: String }
222 220
223impl Foo { 221impl Foo {
224 fn new(baz: String) -> Self { Self { baz } }<|> 222 fn $0new(baz: String) -> Self { Self { baz } }
225} 223}
226", 224",
227 ); 225 );
@@ -231,7 +229,7 @@ impl Foo {
231"struct Foo { baz: String, qux: Vec<i32> } 229"struct Foo { baz: String, qux: Vec<i32> }
232 230
233impl Foo { 231impl Foo {
234 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> 232 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
235} 233}
236", 234",
237 ); 235 );
@@ -243,7 +241,7 @@ impl Foo {
243"struct Foo { pub baz: String, pub qux: Vec<i32> } 241"struct Foo { pub baz: String, pub qux: Vec<i32> }
244 242
245impl Foo { 243impl Foo {
246 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> 244 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
247} 245}
248", 246",
249 ); 247 );
@@ -258,7 +256,7 @@ impl Foo {}
258"struct Foo {} 256"struct Foo {}
259 257
260impl Foo { 258impl Foo {
261 fn new() -> Self { Self { } }<|> 259 fn $0new() -> Self { Self { } }
262} 260}
263", 261",
264 ); 262 );
@@ -273,7 +271,7 @@ impl Foo {
273"struct Foo {} 271"struct Foo {}
274 272
275impl Foo { 273impl Foo {
276 fn new() -> Self { Self { } }<|> 274 fn $0new() -> Self { Self { } }
277 275
278 fn qux(&self) {} 276 fn qux(&self) {}
279} 277}
@@ -294,7 +292,7 @@ impl Foo {
294"struct Foo {} 292"struct Foo {}
295 293
296impl Foo { 294impl Foo {
297 fn new() -> Self { Self { } }<|> 295 fn $0new() -> Self { Self { } }
298 296
299 fn qux(&self) {} 297 fn qux(&self) {}
300 fn baz() -> i32 { 298 fn baz() -> i32 {
@@ -311,7 +309,7 @@ impl Foo {
311"pub struct Foo {} 309"pub struct Foo {}
312 310
313impl Foo { 311impl Foo {
314 pub fn new() -> Self { Self { } }<|> 312 pub fn $0new() -> Self { Self { } }
315} 313}
316", 314",
317 ); 315 );
@@ -321,7 +319,7 @@ impl Foo {
321"pub(crate) struct Foo {} 319"pub(crate) struct Foo {}
322 320
323impl Foo { 321impl Foo {
324 pub(crate) fn new() -> Self { Self { } }<|> 322 pub(crate) fn $0new() -> Self { Self { } }
325} 323}
326", 324",
327 ); 325 );
@@ -414,7 +412,7 @@ pub struct Source<T> {
414} 412}
415 413
416impl<T> Source<T> { 414impl<T> Source<T> {
417 pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|> 415 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
418 416
419 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { 417 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
420 Source { file_id: self.file_id, ast: f(self.ast) } 418 Source { file_id: self.file_id, ast: f(self.ast) }
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
new file mode 100644
index 000000000..26acf81f2
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs
@@ -0,0 +1,134 @@
1use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass};
2use ra_syntax::{ast, AstNode, SyntaxKind, T};
3use test_utils::mark;
4
5use crate::{
6 assist_context::{AssistContext, Assists},
7 AssistId,
8};
9
10// Assist: add_turbo_fish
11//
12// Adds `::<_>` to a call of a generic method or function.
13//
14// ```
15// fn make<T>() -> T { todo!() }
16// fn main() {
17// let x = make<|>();
18// }
19// ```
20// ->
21// ```
22// fn make<T>() -> T { todo!() }
23// fn main() {
24// let x = make::<${0:_}>();
25// }
26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?;
29 let next_token = ident.next_token()?;
30 if next_token.kind() == T![::] {
31 mark::hit!(add_turbo_fish_one_fish_is_enough);
32 return None;
33 }
34 let name_ref = ast::NameRef::cast(ident.parent())?;
35 let def = match classify_name_ref(&ctx.sema, &name_ref)? {
36 NameRefClass::Definition(def) => def,
37 NameRefClass::FieldShorthand { .. } => return None,
38 };
39 let fun = match def {
40 Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
41 _ => return None,
42 };
43 let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
44 if generics.is_empty() {
45 mark::hit!(add_turbo_fish_non_generic);
46 return None;
47 }
48 acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| {
49 match ctx.config.snippet_cap {
50 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
51 None => builder.insert(ident.text_range().end(), "::<_>"),
52 }
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::tests::{check_assist, check_assist_not_applicable};
59
60 use super::*;
61 use test_utils::mark;
62
63 #[test]
64 fn add_turbo_fish_function() {
65 check_assist(
66 add_turbo_fish,
67 r#"
68fn make<T>() -> T {}
69fn main() {
70 make<|>();
71}
72"#,
73 r#"
74fn make<T>() -> T {}
75fn main() {
76 make::<${0:_}>();
77}
78"#,
79 );
80 }
81
82 #[test]
83 fn add_turbo_fish_method() {
84 check_assist(
85 add_turbo_fish,
86 r#"
87struct S;
88impl S {
89 fn make<T>(&self) -> T {}
90}
91fn main() {
92 S.make<|>();
93}
94"#,
95 r#"
96struct S;
97impl S {
98 fn make<T>(&self) -> T {}
99}
100fn main() {
101 S.make::<${0:_}>();
102}
103"#,
104 );
105 }
106
107 #[test]
108 fn add_turbo_fish_one_fish_is_enough() {
109 mark::check!(add_turbo_fish_one_fish_is_enough);
110 check_assist_not_applicable(
111 add_turbo_fish,
112 r#"
113fn make<T>() -> T {}
114fn main() {
115 make<|>::<()>();
116}
117"#,
118 );
119 }
120
121 #[test]
122 fn add_turbo_fish_non_generic() {
123 mark::check!(add_turbo_fish_non_generic);
124 check_assist_not_applicable(
125 add_turbo_fish,
126 r#"
127fn make() -> () {}
128fn main() {
129 make<|>();
130}
131"#,
132 );
133 }
134}
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index 0feba5e11..233e8fb8e 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -63,22 +63,22 @@ mod tests {
63 63
64 #[test] 64 #[test]
65 fn demorgan_turns_and_into_or() { 65 fn demorgan_turns_and_into_or() {
66 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x ||<|> x) }") 66 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }")
67 } 67 }
68 68
69 #[test] 69 #[test]
70 fn demorgan_turns_or_into_and() { 70 fn demorgan_turns_or_into_and() {
71 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x &&<|> x) }") 71 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }")
72 } 72 }
73 73
74 #[test] 74 #[test]
75 fn demorgan_removes_inequality() { 75 fn demorgan_removes_inequality() {
76 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x &&<|> x) }") 76 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }")
77 } 77 }
78 78
79 #[test] 79 #[test]
80 fn demorgan_general_case() { 80 fn demorgan_general_case() {
81 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }") 81 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }")
82 } 82 }
83 83
84 #[test] 84 #[test]
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 78d23150d..edf96d50e 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -50,7 +50,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
50 format!("Import `{}`", &import), 50 format!("Import `{}`", &import),
51 range, 51 range,
52 |builder| { 52 |builder| {
53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, ctx, builder); 53 insert_use_statement(
54 &auto_import_assets.syntax_under_caret,
55 &import,
56 ctx,
57 builder.text_edit_builder(),
58 );
54 }, 59 },
55 ); 60 );
56 } 61 }
@@ -293,7 +298,7 @@ mod tests {
293 } 298 }
294 ", 299 ",
295 r" 300 r"
296 <|>use PubMod::PubStruct; 301 use PubMod::PubStruct;
297 302
298 PubStruct 303 PubStruct
299 304
@@ -324,7 +329,7 @@ mod tests {
324 macro_rules! foo { 329 macro_rules! foo {
325 ($i:ident) => { fn foo(a: $i) {} } 330 ($i:ident) => { fn foo(a: $i) {} }
326 } 331 }
327 foo!(Pub<|>Struct); 332 foo!(PubStruct);
328 333
329 pub mod PubMod { 334 pub mod PubMod {
330 pub struct PubStruct; 335 pub struct PubStruct;
@@ -355,7 +360,7 @@ mod tests {
355 use PubMod::{PubStruct2, PubStruct1}; 360 use PubMod::{PubStruct2, PubStruct1};
356 361
357 struct Test { 362 struct Test {
358 test: Pub<|>Struct2<u8>, 363 test: PubStruct2<u8>,
359 } 364 }
360 365
361 pub mod PubMod { 366 pub mod PubMod {
@@ -388,7 +393,7 @@ mod tests {
388 r" 393 r"
389 use PubMod3::PubStruct; 394 use PubMod3::PubStruct;
390 395
391 PubSt<|>ruct 396 PubStruct
392 397
393 pub mod PubMod1 { 398 pub mod PubMod1 {
394 pub struct PubStruct; 399 pub struct PubStruct;
@@ -469,7 +474,7 @@ mod tests {
469 r" 474 r"
470 use PubMod::test_function; 475 use PubMod::test_function;
471 476
472 test_function<|> 477 test_function
473 478
474 pub mod PubMod { 479 pub mod PubMod {
475 pub fn test_function() {}; 480 pub fn test_function() {};
@@ -496,7 +501,7 @@ mod tests {
496 r"use crate_with_macro::foo; 501 r"use crate_with_macro::foo;
497 502
498fn main() { 503fn main() {
499 foo<|> 504 foo
500} 505}
501", 506",
502 ); 507 );
@@ -582,7 +587,7 @@ fn main() {
582 } 587 }
583 588
584 fn main() { 589 fn main() {
585 TestStruct::test_function<|> 590 TestStruct::test_function
586 } 591 }
587 ", 592 ",
588 ); 593 );
@@ -615,7 +620,7 @@ fn main() {
615 } 620 }
616 621
617 fn main() { 622 fn main() {
618 TestStruct::TEST_CONST<|> 623 TestStruct::TEST_CONST
619 } 624 }
620 ", 625 ",
621 ); 626 );
@@ -654,7 +659,7 @@ fn main() {
654 } 659 }
655 660
656 fn main() { 661 fn main() {
657 test_mod::TestStruct::test_function<|> 662 test_mod::TestStruct::test_function
658 } 663 }
659 ", 664 ",
660 ); 665 );
@@ -725,7 +730,7 @@ fn main() {
725 } 730 }
726 731
727 fn main() { 732 fn main() {
728 test_mod::TestStruct::TEST_CONST<|> 733 test_mod::TestStruct::TEST_CONST
729 } 734 }
730 ", 735 ",
731 ); 736 );
@@ -798,7 +803,7 @@ fn main() {
798 803
799 fn main() { 804 fn main() {
800 let test_struct = test_mod::TestStruct {}; 805 let test_struct = test_mod::TestStruct {};
801 test_struct.test_meth<|>od() 806 test_struct.test_method()
802 } 807 }
803 ", 808 ",
804 ); 809 );
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
index 5c907097e..c6baa0a57 100644
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -1,8 +1,6 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast::{self, BlockExpr, Expr, LoopBodyOwner}, 2 ast::{self, BlockExpr, Expr, LoopBodyOwner},
3 AstNode, 3 AstNode, SyntaxNode,
4 SyntaxKind::{COMMENT, WHITESPACE},
5 SyntaxNode, TextSize,
6}; 4};
7 5
8use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, Assists};
@@ -16,39 +14,40 @@ use crate::{AssistContext, AssistId, Assists};
16// ``` 14// ```
17// -> 15// ->
18// ``` 16// ```
19// fn foo() -> Result<i32, > { Ok(42i32) } 17// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
20// ``` 18// ```
21pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 19pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
22 let fn_def = ctx.find_node_at_offset::<ast::FnDef>(); 20 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
23 let fn_def = &mut fn_def?; 21 // FIXME: extend to lambdas as well
24 let ret_type = &fn_def.ret_type()?.type_ref()?; 22 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?;
25 if ret_type.syntax().text().to_string().starts_with("Result<") { 23
24 let type_ref = &ret_type.type_ref()?;
25 if type_ref.syntax().text().to_string().starts_with("Result<") {
26 return None; 26 return None;
27 } 27 }
28 28
29 let block_expr = &fn_def.body()?; 29 let block_expr = &fn_def.body()?;
30 let cursor_in_ret_type =
31 fn_def.ret_type()?.syntax().text_range().contains_range(ctx.frange.range);
32 if !cursor_in_ret_type {
33 return None;
34 }
35 30
36 acc.add( 31 acc.add(
37 AssistId("change_return_type_to_result"), 32 AssistId("change_return_type_to_result"),
38 "Change return type to Result", 33 "Change return type to Result",
39 ret_type.syntax().text_range(), 34 type_ref.syntax().text_range(),
40 |edit| { 35 |builder| {
41 let mut tail_return_expr_collector = TailReturnCollector::new(); 36 let mut tail_return_expr_collector = TailReturnCollector::new();
42 tail_return_expr_collector.collect_jump_exprs(block_expr, false); 37 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
43 tail_return_expr_collector.collect_tail_exprs(block_expr); 38 tail_return_expr_collector.collect_tail_exprs(block_expr);
44 39
45 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { 40 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
46 edit.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg)); 41 builder.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg));
47 } 42 }
48 edit.replace_node_and_indent(ret_type.syntax(), format!("Result<{}, >", ret_type));
49 43
50 if let Some(node_start) = result_insertion_offset(&ret_type) { 44 match ctx.config.snippet_cap {
51 edit.set_cursor(node_start + TextSize::of(&format!("Result<{}, ", ret_type))); 45 Some(cap) => {
46 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
47 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
48 }
49 None => builder
50 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
52 } 51 }
53 }, 52 },
54 ) 53 )
@@ -250,17 +249,8 @@ fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
250 } 249 }
251} 250}
252 251
253fn result_insertion_offset(ret_type: &ast::TypeRef) -> Option<TextSize> {
254 let non_ws_child = ret_type
255 .syntax()
256 .children_with_tokens()
257 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
258 Some(non_ws_child.text_range().start())
259}
260
261#[cfg(test)] 252#[cfg(test)]
262mod tests { 253mod tests {
263
264 use crate::tests::{check_assist, check_assist_not_applicable}; 254 use crate::tests::{check_assist, check_assist_not_applicable};
265 255
266 use super::*; 256 use super::*;
@@ -273,7 +263,7 @@ mod tests {
273 let test = "test"; 263 let test = "test";
274 return 42i32; 264 return 42i32;
275 }"#, 265 }"#,
276 r#"fn foo() -> Result<i32, <|>> { 266 r#"fn foo() -> Result<i32, ${0:_}> {
277 let test = "test"; 267 let test = "test";
278 return Ok(42i32); 268 return Ok(42i32);
279 }"#, 269 }"#,
@@ -288,7 +278,7 @@ mod tests {
288 let test = "test"; 278 let test = "test";
289 return 42i32; 279 return 42i32;
290 }"#, 280 }"#,
291 r#"fn foo() -> Result<i32, <|>> { 281 r#"fn foo() -> Result<i32, ${0:_}> {
292 let test = "test"; 282 let test = "test";
293 return Ok(42i32); 283 return Ok(42i32);
294 }"#, 284 }"#,
@@ -314,7 +304,7 @@ mod tests {
314 let test = "test"; 304 let test = "test";
315 return 42i32; 305 return 42i32;
316 }"#, 306 }"#,
317 r#"fn foo() -> Result<i32, <|>> { 307 r#"fn foo() -> Result<i32, ${0:_}> {
318 let test = "test"; 308 let test = "test";
319 return Ok(42i32); 309 return Ok(42i32);
320 }"#, 310 }"#,
@@ -329,7 +319,7 @@ mod tests {
329 let test = "test"; 319 let test = "test";
330 42i32 320 42i32
331 }"#, 321 }"#,
332 r#"fn foo() -> Result<i32, <|>> { 322 r#"fn foo() -> Result<i32, ${0:_}> {
333 let test = "test"; 323 let test = "test";
334 Ok(42i32) 324 Ok(42i32)
335 }"#, 325 }"#,
@@ -343,7 +333,7 @@ mod tests {
343 r#"fn foo() -> i32<|> { 333 r#"fn foo() -> i32<|> {
344 42i32 334 42i32
345 }"#, 335 }"#,
346 r#"fn foo() -> Result<i32, <|>> { 336 r#"fn foo() -> Result<i32, ${0:_}> {
347 Ok(42i32) 337 Ok(42i32)
348 }"#, 338 }"#,
349 ); 339 );
@@ -359,7 +349,7 @@ mod tests {
359 24i32 349 24i32
360 } 350 }
361 }"#, 351 }"#,
362 r#"fn foo() -> Result<i32, <|>> { 352 r#"fn foo() -> Result<i32, ${0:_}> {
363 if true { 353 if true {
364 Ok(42i32) 354 Ok(42i32)
365 } else { 355 } else {
@@ -384,7 +374,7 @@ mod tests {
384 24i32 374 24i32
385 } 375 }
386 }"#, 376 }"#,
387 r#"fn foo() -> Result<i32, <|>> { 377 r#"fn foo() -> Result<i32, ${0:_}> {
388 if true { 378 if true {
389 if false { 379 if false {
390 Ok(1) 380 Ok(1)
@@ -413,7 +403,7 @@ mod tests {
413 24i32.await 403 24i32.await
414 } 404 }
415 }"#, 405 }"#,
416 r#"async fn foo() -> Result<i32, <|>> { 406 r#"async fn foo() -> Result<i32, ${0:_}> {
417 if true { 407 if true {
418 if false { 408 if false {
419 Ok(1.await) 409 Ok(1.await)
@@ -434,7 +424,7 @@ mod tests {
434 r#"fn foo() -> [i32;<|> 3] { 424 r#"fn foo() -> [i32;<|> 3] {
435 [1, 2, 3] 425 [1, 2, 3]
436 }"#, 426 }"#,
437 r#"fn foo() -> Result<[i32; 3], <|>> { 427 r#"fn foo() -> Result<[i32; 3], ${0:_}> {
438 Ok([1, 2, 3]) 428 Ok([1, 2, 3])
439 }"#, 429 }"#,
440 ); 430 );
@@ -455,7 +445,7 @@ mod tests {
455 24 as i32 445 24 as i32
456 } 446 }
457 }"#, 447 }"#,
458 r#"fn foo() -> Result<i32, <|>> { 448 r#"fn foo() -> Result<i32, ${0:_}> {
459 if true { 449 if true {
460 if false { 450 if false {
461 Ok(1 as i32) 451 Ok(1 as i32)
@@ -480,7 +470,7 @@ mod tests {
480 _ => 24i32, 470 _ => 24i32,
481 } 471 }
482 }"#, 472 }"#,
483 r#"fn foo() -> Result<i32, <|>> { 473 r#"fn foo() -> Result<i32, ${0:_}> {
484 let my_var = 5; 474 let my_var = 5;
485 match my_var { 475 match my_var {
486 5 => Ok(42i32), 476 5 => Ok(42i32),
@@ -503,7 +493,7 @@ mod tests {
503 493
504 my_var 494 my_var
505 }"#, 495 }"#,
506 r#"fn foo() -> Result<i32, <|>> { 496 r#"fn foo() -> Result<i32, ${0:_}> {
507 let my_var = 5; 497 let my_var = 5;
508 loop { 498 loop {
509 println!("test"); 499 println!("test");
@@ -526,7 +516,7 @@ mod tests {
526 516
527 my_var 517 my_var
528 }"#, 518 }"#,
529 r#"fn foo() -> Result<i32, <|>> { 519 r#"fn foo() -> Result<i32, ${0:_}> {
530 let my_var = let x = loop { 520 let my_var = let x = loop {
531 break 1; 521 break 1;
532 }; 522 };
@@ -549,7 +539,7 @@ mod tests {
549 539
550 res 540 res
551 }"#, 541 }"#,
552 r#"fn foo() -> Result<i32, <|>> { 542 r#"fn foo() -> Result<i32, ${0:_}> {
553 let my_var = 5; 543 let my_var = 5;
554 let res = match my_var { 544 let res = match my_var {
555 5 => 42i32, 545 5 => 42i32,
@@ -572,7 +562,7 @@ mod tests {
572 562
573 res 563 res
574 }"#, 564 }"#,
575 r#"fn foo() -> Result<i32, <|>> { 565 r#"fn foo() -> Result<i32, ${0:_}> {
576 let my_var = 5; 566 let my_var = 5;
577 let res = if my_var == 5 { 567 let res = if my_var == 5 {
578 42i32 568 42i32
@@ -608,7 +598,7 @@ mod tests {
608 }, 598 },
609 } 599 }
610 }"#, 600 }"#,
611 r#"fn foo() -> Result<i32, <|>> { 601 r#"fn foo() -> Result<i32, ${0:_}> {
612 let my_var = 5; 602 let my_var = 5;
613 match my_var { 603 match my_var {
614 5 => { 604 5 => {
@@ -641,7 +631,7 @@ mod tests {
641 } 631 }
642 53i32 632 53i32
643 }"#, 633 }"#,
644 r#"fn foo() -> Result<i32, <|>> { 634 r#"fn foo() -> Result<i32, ${0:_}> {
645 let test = "test"; 635 let test = "test";
646 if test == "test" { 636 if test == "test" {
647 return Ok(24i32); 637 return Ok(24i32);
@@ -672,7 +662,7 @@ mod tests {
672 662
673 the_field 663 the_field
674 }"#, 664 }"#,
675 r#"fn foo(the_field: u32) -> Result<u32, <|>> { 665 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
676 let true_closure = || { 666 let true_closure = || {
677 return true; 667 return true;
678 }; 668 };
@@ -711,7 +701,7 @@ mod tests {
711 701
712 t.unwrap_or_else(|| the_field) 702 t.unwrap_or_else(|| the_field)
713 }"#, 703 }"#,
714 r#"fn foo(the_field: u32) -> Result<u32, <|>> { 704 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
715 let true_closure = || { 705 let true_closure = || {
716 return true; 706 return true;
717 }; 707 };
@@ -749,7 +739,7 @@ mod tests {
749 i += 1; 739 i += 1;
750 } 740 }
751 }"#, 741 }"#,
752 r#"fn foo() -> Result<i32, <|>> { 742 r#"fn foo() -> Result<i32, ${0:_}> {
753 let test = "test"; 743 let test = "test";
754 if test == "test" { 744 if test == "test" {
755 return Ok(24i32); 745 return Ok(24i32);
@@ -781,7 +771,7 @@ mod tests {
781 } 771 }
782 } 772 }
783 }"#, 773 }"#,
784 r#"fn foo() -> Result<i32, <|>> { 774 r#"fn foo() -> Result<i32, ${0:_}> {
785 let test = "test"; 775 let test = "test";
786 if test == "test" { 776 if test == "test" {
787 return Ok(24i32); 777 return Ok(24i32);
@@ -819,7 +809,7 @@ mod tests {
819 } 809 }
820 } 810 }
821 }"#, 811 }"#,
822 r#"fn foo() -> Result<i32, <|>> { 812 r#"fn foo() -> Result<i32, ${0:_}> {
823 let test = "test"; 813 let test = "test";
824 let other = 5; 814 let other = 5;
825 if test == "test" { 815 if test == "test" {
@@ -860,7 +850,7 @@ mod tests {
860 850
861 the_field 851 the_field
862 }"#, 852 }"#,
863 r#"fn foo(the_field: u32) -> Result<u32, <|>> { 853 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
864 if the_field < 5 { 854 if the_field < 5 {
865 let mut i = 0; 855 let mut i = 0;
866 loop { 856 loop {
@@ -894,7 +884,7 @@ mod tests {
894 884
895 the_field 885 the_field
896 }"#, 886 }"#,
897 r#"fn foo(the_field: u32) -> Result<u32, <|>> { 887 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
898 if the_field < 5 { 888 if the_field < 5 {
899 let mut i = 0; 889 let mut i = 0;
900 890
@@ -923,7 +913,7 @@ mod tests {
923 913
924 the_field 914 the_field
925 }"#, 915 }"#,
926 r#"fn foo(the_field: u32) -> Result<u32, <|>> { 916 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
927 if the_field < 5 { 917 if the_field < 5 {
928 let mut i = 0; 918 let mut i = 0;
929 919
@@ -953,7 +943,7 @@ mod tests {
953 943
954 the_field 944 the_field
955 }"#, 945 }"#,
956 r#"fn foo(the_field: u32) -> Result<u32, <|>> { 946 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
957 if the_field < 5 { 947 if the_field < 5 {
958 let mut i = 0; 948 let mut i = 0;
959 949
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 40cf4b422..c21d75be0 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -5,14 +5,11 @@ use ra_syntax::{
5 ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, 5 ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
6 WHITESPACE, 6 WHITESPACE,
7 }, 7 },
8 SyntaxNode, TextRange, TextSize, T, 8 SyntaxNode, TextSize, T,
9}; 9};
10 10use test_utils::mark;
11use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
12use test_utils::tested_by;
13 11
14use crate::{AssistContext, AssistId, Assists}; 12use crate::{AssistContext, AssistId, Assists};
15use ra_db::FileId;
16 13
17// Assist: change_visibility 14// Assist: change_visibility
18// 15//
@@ -30,8 +27,6 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
30 return change_vis(acc, vis); 27 return change_vis(acc, vis);
31 } 28 }
32 add_vis(acc, ctx) 29 add_vis(acc, ctx)
33 .or_else(|| add_vis_to_referenced_module_def(acc, ctx))
34 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
35} 30}
36 31
37fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 32fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -55,7 +50,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
55 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() { 50 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
56 let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; 51 let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?;
57 if field.name()? != field_name { 52 if field.name()? != field_name {
58 tested_by!(change_visibility_field_false_positive); 53 mark::hit!(change_visibility_field_false_positive);
59 return None; 54 return None;
60 } 55 }
61 if field.visibility().is_some() { 56 if field.visibility().is_some() {
@@ -73,147 +68,9 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
73 68
74 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { 69 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| {
75 edit.insert(offset, "pub(crate) "); 70 edit.insert(offset, "pub(crate) ");
76 edit.set_cursor(offset);
77 })
78}
79
80fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
81 let path: ast::Path = ctx.find_node_at_offset()?;
82 let path_res = ctx.sema.resolve_path(&path)?;
83 let def = match path_res {
84 PathResolution::Def(def) => def,
85 _ => return None,
86 };
87
88 let current_module = ctx.sema.scope(&path.syntax()).module()?;
89 let target_module = def.module(ctx.db)?;
90
91 let vis = target_module.visibility_of(ctx.db, &def)?;
92 if vis.is_visible_from(ctx.db, current_module.into()) {
93 return None;
94 };
95
96 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?;
97
98 let missing_visibility =
99 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
100
101 let assist_label = match target_name {
102 None => format!("Change visibility to {}", missing_visibility),
103 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
104 };
105
106 acc.add(AssistId("change_visibility"), assist_label, target, |edit| {
107 edit.set_file(target_file);
108 edit.insert(offset, format!("{} ", missing_visibility));
109 edit.set_cursor(offset);
110 })
111}
112
113fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
114 let record_field: ast::RecordField = ctx.find_node_at_offset()?;
115 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
116
117 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
118 let visibility = record_field_def.visibility(ctx.db);
119 if visibility.is_visible_from(ctx.db, current_module.into()) {
120 return None;
121 }
122
123 let parent = record_field_def.parent_def(ctx.db);
124 let parent_name = parent.name(ctx.db);
125 let target_module = parent.module(ctx.db);
126
127 let in_file_source = record_field_def.source(ctx.db);
128 let (offset, target) = match in_file_source.value {
129 hir::FieldSource::Named(it) => {
130 let s = it.syntax();
131 (vis_offset(s), s.text_range())
132 }
133 hir::FieldSource::Pos(it) => {
134 let s = it.syntax();
135 (vis_offset(s), s.text_range())
136 }
137 };
138
139 let missing_visibility =
140 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
141 let target_file = in_file_source.file_id.original_file(ctx.db);
142
143 let target_name = record_field_def.name(ctx.db);
144 let assist_label =
145 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
146
147 acc.add(AssistId("change_visibility"), assist_label, target, |edit| {
148 edit.set_file(target_file);
149 edit.insert(offset, format!("{} ", missing_visibility));
150 edit.set_cursor(offset)
151 }) 71 })
152} 72}
153 73
154fn target_data_for_def(
155 db: &dyn HirDatabase,
156 def: hir::ModuleDef,
157) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> {
158 fn offset_target_and_file_id<S, Ast>(
159 db: &dyn HirDatabase,
160 x: S,
161 ) -> (TextSize, TextRange, FileId)
162 where
163 S: HasSource<Ast = Ast>,
164 Ast: AstNode,
165 {
166 let source = x.source(db);
167 let in_file_syntax = source.syntax();
168 let file_id = in_file_syntax.file_id;
169 let syntax = in_file_syntax.value;
170 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast()))
171 }
172
173 let target_name;
174 let (offset, target, target_file) = match def {
175 hir::ModuleDef::Function(f) => {
176 target_name = Some(f.name(db));
177 offset_target_and_file_id(db, f)
178 }
179 hir::ModuleDef::Adt(adt) => {
180 target_name = Some(adt.name(db));
181 match adt {
182 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
183 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
184 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
185 }
186 }
187 hir::ModuleDef::Const(c) => {
188 target_name = c.name(db);
189 offset_target_and_file_id(db, c)
190 }
191 hir::ModuleDef::Static(s) => {
192 target_name = s.name(db);
193 offset_target_and_file_id(db, s)
194 }
195 hir::ModuleDef::Trait(t) => {
196 target_name = Some(t.name(db));
197 offset_target_and_file_id(db, t)
198 }
199 hir::ModuleDef::TypeAlias(t) => {
200 target_name = Some(t.name(db));
201 offset_target_and_file_id(db, t)
202 }
203 hir::ModuleDef::Module(m) => {
204 target_name = m.name(db);
205 let in_file_source = m.declaration_source(db)?;
206 let file_id = in_file_source.file_id.original_file(db.upcast());
207 let syntax = in_file_source.value.syntax();
208 (vis_offset(syntax), syntax.text_range(), file_id)
209 }
210 // Enum variants can't be private, we can't modify builtin types
211 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
212 };
213
214 Some((offset, target, target_file, target_name))
215}
216
217fn vis_offset(node: &SyntaxNode) -> TextSize { 74fn vis_offset(node: &SyntaxNode) -> TextSize {
218 node.children_with_tokens() 75 node.children_with_tokens()
219 .skip_while(|it| match it.kind() { 76 .skip_while(|it| match it.kind() {
@@ -234,7 +91,6 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
234 target, 91 target,
235 |edit| { 92 |edit| {
236 edit.replace(vis.syntax().text_range(), "pub(crate)"); 93 edit.replace(vis.syntax().text_range(), "pub(crate)");
237 edit.set_cursor(vis.syntax().text_range().start())
238 }, 94 },
239 ); 95 );
240 } 96 }
@@ -246,7 +102,6 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
246 target, 102 target,
247 |edit| { 103 |edit| {
248 edit.replace(vis.syntax().text_range(), "pub"); 104 edit.replace(vis.syntax().text_range(), "pub");
249 edit.set_cursor(vis.syntax().text_range().start());
250 }, 105 },
251 ); 106 );
252 } 107 }
@@ -255,7 +110,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
255 110
256#[cfg(test)] 111#[cfg(test)]
257mod tests { 112mod tests {
258 use test_utils::covers; 113 use test_utils::mark;
259 114
260 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 115 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
261 116
@@ -263,17 +118,13 @@ mod tests {
263 118
264 #[test] 119 #[test]
265 fn change_visibility_adds_pub_crate_to_items() { 120 fn change_visibility_adds_pub_crate_to_items() {
266 check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); 121 check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}");
267 check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); 122 check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}");
268 check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); 123 check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}");
269 check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); 124 check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}");
270 check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); 125 check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}");
271 check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); 126 check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}");
272 check_assist( 127 check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}");
273 change_visibility,
274 "unsafe f<|>n foo() {}",
275 "<|>pub(crate) unsafe fn foo() {}",
276 );
277 } 128 }
278 129
279 #[test] 130 #[test]
@@ -281,14 +132,14 @@ mod tests {
281 check_assist( 132 check_assist(
282 change_visibility, 133 change_visibility,
283 r"struct S { <|>field: u32 }", 134 r"struct S { <|>field: u32 }",
284 r"struct S { <|>pub(crate) field: u32 }", 135 r"struct S { pub(crate) field: u32 }",
285 ); 136 );
286 check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( <|>pub(crate) u32 )"); 137 check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )");
287 } 138 }
288 139
289 #[test] 140 #[test]
290 fn change_visibility_field_false_positive() { 141 fn change_visibility_field_false_positive() {
291 covers!(change_visibility_field_false_positive); 142 mark::check!(change_visibility_field_false_positive);
292 check_assist_not_applicable( 143 check_assist_not_applicable(
293 change_visibility, 144 change_visibility,
294 r"struct S { field: [(); { let <|>x = ();}] }", 145 r"struct S { field: [(); { let <|>x = ();}] }",
@@ -297,17 +148,17 @@ mod tests {
297 148
298 #[test] 149 #[test]
299 fn change_visibility_pub_to_pub_crate() { 150 fn change_visibility_pub_to_pub_crate() {
300 check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") 151 check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}")
301 } 152 }
302 153
303 #[test] 154 #[test]
304 fn change_visibility_pub_crate_to_pub() { 155 fn change_visibility_pub_crate_to_pub() {
305 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") 156 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}")
306 } 157 }
307 158
308 #[test] 159 #[test]
309 fn change_visibility_const() { 160 fn change_visibility_const() {
310 check_assist(change_visibility, "<|>const FOO = 3u8;", "<|>pub(crate) const FOO = 3u8;"); 161 check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
311 } 162 }
312 163
313 #[test] 164 #[test]
@@ -328,199 +179,12 @@ mod tests {
328 // comments 179 // comments
329 180
330 #[derive(Debug)] 181 #[derive(Debug)]
331 <|>pub(crate) struct Foo; 182 pub(crate) struct Foo;
332 ", 183 ",
333 ) 184 )
334 } 185 }
335 186
336 #[test] 187 #[test]
337 fn change_visibility_of_fn_via_path() {
338 check_assist(
339 change_visibility,
340 r"mod foo { fn foo() {} }
341 fn main() { foo::foo<|>() } ",
342 r"mod foo { <|>pub(crate) fn foo() {} }
343 fn main() { foo::foo() } ",
344 );
345 check_assist_not_applicable(
346 change_visibility,
347 r"mod foo { pub fn foo() {} }
348 fn main() { foo::foo<|>() } ",
349 )
350 }
351
352 #[test]
353 fn change_visibility_of_adt_in_submodule_via_path() {
354 check_assist(
355 change_visibility,
356 r"mod foo { struct Foo; }
357 fn main() { foo::Foo<|> } ",
358 r"mod foo { <|>pub(crate) struct Foo; }
359 fn main() { foo::Foo } ",
360 );
361 check_assist_not_applicable(
362 change_visibility,
363 r"mod foo { pub struct Foo; }
364 fn main() { foo::Foo<|> } ",
365 );
366 check_assist(
367 change_visibility,
368 r"mod foo { enum Foo; }
369 fn main() { foo::Foo<|> } ",
370 r"mod foo { <|>pub(crate) enum Foo; }
371 fn main() { foo::Foo } ",
372 );
373 check_assist_not_applicable(
374 change_visibility,
375 r"mod foo { pub enum Foo; }
376 fn main() { foo::Foo<|> } ",
377 );
378 check_assist(
379 change_visibility,
380 r"mod foo { union Foo; }
381 fn main() { foo::Foo<|> } ",
382 r"mod foo { <|>pub(crate) union Foo; }
383 fn main() { foo::Foo } ",
384 );
385 check_assist_not_applicable(
386 change_visibility,
387 r"mod foo { pub union Foo; }
388 fn main() { foo::Foo<|> } ",
389 );
390 }
391
392 #[test]
393 fn change_visibility_of_adt_in_other_file_via_path() {
394 check_assist(
395 change_visibility,
396 r"
397 //- /main.rs
398 mod foo;
399 fn main() { foo::Foo<|> }
400
401 //- /foo.rs
402 struct Foo;
403 ",
404 r"<|>pub(crate) struct Foo;
405
406",
407 );
408 }
409
410 #[test]
411 fn change_visibility_of_struct_field_via_path() {
412 check_assist(
413 change_visibility,
414 r"mod foo { pub struct Foo { bar: (), } }
415 fn main() { foo::Foo { <|>bar: () }; } ",
416 r"mod foo { pub struct Foo { <|>pub(crate) bar: (), } }
417 fn main() { foo::Foo { bar: () }; } ",
418 );
419 check_assist(
420 change_visibility,
421 r"//- /lib.rs
422 mod foo;
423 fn main() { foo::Foo { <|>bar: () }; }
424 //- /foo.rs
425 pub struct Foo { bar: () }
426 ",
427 r"pub struct Foo { <|>pub(crate) bar: () }
428
429",
430 );
431 check_assist_not_applicable(
432 change_visibility,
433 r"mod foo { pub struct Foo { pub bar: (), } }
434 fn main() { foo::Foo { <|>bar: () }; } ",
435 );
436 check_assist_not_applicable(
437 change_visibility,
438 r"//- /lib.rs
439 mod foo;
440 fn main() { foo::Foo { <|>bar: () }; }
441 //- /foo.rs
442 pub struct Foo { pub bar: () }
443 ",
444 );
445 }
446
447 #[test]
448 fn change_visibility_of_enum_variant_field_via_path() {
449 check_assist(
450 change_visibility,
451 r"mod foo { pub enum Foo { Bar { bar: () } } }
452 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
453 r"mod foo { pub enum Foo { Bar { <|>pub(crate) bar: () } } }
454 fn main() { foo::Foo::Bar { bar: () }; } ",
455 );
456 check_assist(
457 change_visibility,
458 r"//- /lib.rs
459 mod foo;
460 fn main() { foo::Foo::Bar { <|>bar: () }; }
461 //- /foo.rs
462 pub enum Foo { Bar { bar: () } }
463 ",
464 r"pub enum Foo { Bar { <|>pub(crate) bar: () } }
465
466",
467 );
468 check_assist_not_applicable(
469 change_visibility,
470 r"mod foo { pub struct Foo { pub bar: (), } }
471 fn main() { foo::Foo { <|>bar: () }; } ",
472 );
473 check_assist_not_applicable(
474 change_visibility,
475 r"//- /lib.rs
476 mod foo;
477 fn main() { foo::Foo { <|>bar: () }; }
478 //- /foo.rs
479 pub struct Foo { pub bar: () }
480 ",
481 );
482 }
483
484 #[test]
485 #[ignore]
486 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
487 fn change_visibility_of_union_field_via_path() {
488 check_assist(
489 change_visibility,
490 r"mod foo { pub union Foo { bar: (), } }
491 fn main() { foo::Foo { <|>bar: () }; } ",
492 r"mod foo { pub union Foo { <|>pub(crate) bar: (), } }
493 fn main() { foo::Foo { bar: () }; } ",
494 );
495 check_assist(
496 change_visibility,
497 r"//- /lib.rs
498 mod foo;
499 fn main() { foo::Foo { <|>bar: () }; }
500 //- /foo.rs
501 pub union Foo { bar: () }
502 ",
503 r"pub union Foo { <|>pub(crate) bar: () }
504
505",
506 );
507 check_assist_not_applicable(
508 change_visibility,
509 r"mod foo { pub union Foo { pub bar: (), } }
510 fn main() { foo::Foo { <|>bar: () }; } ",
511 );
512 check_assist_not_applicable(
513 change_visibility,
514 r"//- /lib.rs
515 mod foo;
516 fn main() { foo::Foo { <|>bar: () }; }
517 //- /foo.rs
518 pub union Foo { pub bar: () }
519 ",
520 );
521 }
522
523 #[test]
524 fn not_applicable_for_enum_variants() { 188 fn not_applicable_for_enum_variants() {
525 check_assist_not_applicable( 189 check_assist_not_applicable(
526 change_visibility, 190 change_visibility,
@@ -530,182 +194,6 @@ mod tests {
530 } 194 }
531 195
532 #[test] 196 #[test]
533 fn change_visibility_of_const_via_path() {
534 check_assist(
535 change_visibility,
536 r"mod foo { const FOO: () = (); }
537 fn main() { foo::FOO<|> } ",
538 r"mod foo { <|>pub(crate) const FOO: () = (); }
539 fn main() { foo::FOO } ",
540 );
541 check_assist_not_applicable(
542 change_visibility,
543 r"mod foo { pub const FOO: () = (); }
544 fn main() { foo::FOO<|> } ",
545 );
546 }
547
548 #[test]
549 fn change_visibility_of_static_via_path() {
550 check_assist(
551 change_visibility,
552 r"mod foo { static FOO: () = (); }
553 fn main() { foo::FOO<|> } ",
554 r"mod foo { <|>pub(crate) static FOO: () = (); }
555 fn main() { foo::FOO } ",
556 );
557 check_assist_not_applicable(
558 change_visibility,
559 r"mod foo { pub static FOO: () = (); }
560 fn main() { foo::FOO<|> } ",
561 );
562 }
563
564 #[test]
565 fn change_visibility_of_trait_via_path() {
566 check_assist(
567 change_visibility,
568 r"mod foo { trait Foo { fn foo(&self) {} } }
569 fn main() { let x: &dyn foo::<|>Foo; } ",
570 r"mod foo { <|>pub(crate) trait Foo { fn foo(&self) {} } }
571 fn main() { let x: &dyn foo::Foo; } ",
572 );
573 check_assist_not_applicable(
574 change_visibility,
575 r"mod foo { pub trait Foo { fn foo(&self) {} } }
576 fn main() { let x: &dyn foo::Foo<|>; } ",
577 );
578 }
579
580 #[test]
581 fn change_visibility_of_type_alias_via_path() {
582 check_assist(
583 change_visibility,
584 r"mod foo { type Foo = (); }
585 fn main() { let x: foo::Foo<|>; } ",
586 r"mod foo { <|>pub(crate) type Foo = (); }
587 fn main() { let x: foo::Foo; } ",
588 );
589 check_assist_not_applicable(
590 change_visibility,
591 r"mod foo { pub type Foo = (); }
592 fn main() { let x: foo::Foo<|>; } ",
593 );
594 }
595
596 #[test]
597 fn change_visibility_of_module_via_path() {
598 check_assist(
599 change_visibility,
600 r"mod foo { mod bar { fn bar() {} } }
601 fn main() { foo::bar<|>::bar(); } ",
602 r"mod foo { <|>pub(crate) mod bar { fn bar() {} } }
603 fn main() { foo::bar::bar(); } ",
604 );
605
606 check_assist(
607 change_visibility,
608 r"
609 //- /main.rs
610 mod foo;
611 fn main() { foo::bar<|>::baz(); }
612
613 //- /foo.rs
614 mod bar {
615 pub fn baz() {}
616 }
617 ",
618 r"<|>pub(crate) mod bar {
619 pub fn baz() {}
620}
621
622",
623 );
624
625 check_assist_not_applicable(
626 change_visibility,
627 r"mod foo { pub mod bar { pub fn bar() {} } }
628 fn main() { foo::bar<|>::bar(); } ",
629 );
630 }
631
632 #[test]
633 fn change_visibility_of_inline_module_in_other_file_via_path() {
634 check_assist(
635 change_visibility,
636 r"
637 //- /main.rs
638 mod foo;
639 fn main() { foo::bar<|>::baz(); }
640
641 //- /foo.rs
642 mod bar;
643
644 //- /foo/bar.rs
645 pub fn baz() {}
646 }
647 ",
648 r"<|>pub(crate) mod bar;
649",
650 );
651 }
652
653 #[test]
654 fn change_visibility_of_module_declaration_in_other_file_via_path() {
655 check_assist(
656 change_visibility,
657 r"//- /main.rs
658 mod foo;
659 fn main() { foo::bar<|>>::baz(); }
660
661 //- /foo.rs
662 mod bar {
663 pub fn baz() {}
664 }",
665 r"<|>pub(crate) mod bar {
666 pub fn baz() {}
667}
668",
669 );
670 }
671
672 #[test]
673 #[ignore]
674 // FIXME handle reexports properly
675 fn change_visibility_of_reexport() {
676 check_assist(
677 change_visibility,
678 r"
679 mod foo {
680 use bar::Baz;
681 mod bar { pub(super) struct Baz; }
682 }
683 foo::Baz<|>
684 ",
685 r"
686 mod foo {
687 <|>pub(crate) use bar::Baz;
688 mod bar { pub(super) struct Baz; }
689 }
690 foo::Baz
691 ",
692 )
693 }
694
695 #[test]
696 fn adds_pub_when_target_is_in_another_crate() {
697 check_assist(
698 change_visibility,
699 r"//- /main.rs crate:a deps:foo
700 foo::Bar<|>
701 //- /lib.rs crate:foo
702 struct Bar;",
703 r"<|>pub struct Bar;
704",
705 )
706 }
707
708 #[test]
709 fn change_visibility_target() { 197 fn change_visibility_target() {
710 check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); 198 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
711 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); 199 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 66b296081..4cc75a7ce 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -97,7 +97,6 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
97 } 97 }
98 98
99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
100 let cursor_position = ctx.offset();
101 100
102 let target = if_expr.syntax().text_range(); 101 let target = if_expr.syntax().text_range();
103 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { 102 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| {
@@ -148,7 +147,6 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
148 } 147 }
149 }; 148 };
150 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); 149 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
151 edit.set_cursor(cursor_position);
152 150
153 fn replace( 151 fn replace(
154 new_expr: &SyntaxNode, 152 new_expr: &SyntaxNode,
@@ -207,7 +205,7 @@ mod tests {
207 r#" 205 r#"
208 fn main() { 206 fn main() {
209 bar(); 207 bar();
210 if<|> !true { 208 if !true {
211 return; 209 return;
212 } 210 }
213 foo(); 211 foo();
@@ -237,7 +235,7 @@ mod tests {
237 r#" 235 r#"
238 fn main(n: Option<String>) { 236 fn main(n: Option<String>) {
239 bar(); 237 bar();
240 le<|>t n = match n { 238 let n = match n {
241 Some(it) => it, 239 Some(it) => it,
242 _ => return, 240 _ => return,
243 }; 241 };
@@ -263,7 +261,7 @@ mod tests {
263 "#, 261 "#,
264 r#" 262 r#"
265 fn main() { 263 fn main() {
266 le<|>t x = match Err(92) { 264 let x = match Err(92) {
267 Ok(it) => it, 265 Ok(it) => it,
268 _ => return, 266 _ => return,
269 }; 267 };
@@ -291,7 +289,7 @@ mod tests {
291 r#" 289 r#"
292 fn main(n: Option<String>) { 290 fn main(n: Option<String>) {
293 bar(); 291 bar();
294 le<|>t n = match n { 292 let n = match n {
295 Ok(it) => it, 293 Ok(it) => it,
296 _ => return, 294 _ => return,
297 }; 295 };
@@ -321,7 +319,7 @@ mod tests {
321 r#" 319 r#"
322 fn main() { 320 fn main() {
323 while true { 321 while true {
324 if<|> !true { 322 if !true {
325 continue; 323 continue;
326 } 324 }
327 foo(); 325 foo();
@@ -349,7 +347,7 @@ mod tests {
349 r#" 347 r#"
350 fn main() { 348 fn main() {
351 while true { 349 while true {
352 le<|>t n = match n { 350 let n = match n {
353 Some(it) => it, 351 Some(it) => it,
354 _ => continue, 352 _ => continue,
355 }; 353 };
@@ -378,7 +376,7 @@ mod tests {
378 r#" 376 r#"
379 fn main() { 377 fn main() {
380 loop { 378 loop {
381 if<|> !true { 379 if !true {
382 continue; 380 continue;
383 } 381 }
384 foo(); 382 foo();
@@ -406,7 +404,7 @@ mod tests {
406 r#" 404 r#"
407 fn main() { 405 fn main() {
408 loop { 406 loop {
409 le<|>t n = match n { 407 let n = match n {
410 Some(it) => it, 408 Some(it) => it,
411 _ => continue, 409 _ => continue,
412 }; 410 };
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 13c1e7e80..cc303285b 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -4,8 +4,12 @@ use hir::{Adt, HasSource, ModuleDef, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark;
7 8
8use crate::{AssistContext, AssistId, Assists}; 9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, Assists,
12};
9 13
10// Assist: fill_match_arms 14// Assist: fill_match_arms
11// 15//
@@ -26,7 +30,7 @@ use crate::{AssistContext, AssistId, Assists};
26// 30//
27// fn handle(action: Action) { 31// fn handle(action: Action) {
28// match action { 32// match action {
29// Action::Move { distance } => {} 33// $0Action::Move { distance } => {}
30// Action::Stop => {} 34// Action::Stop => {}
31// } 35// }
32// } 36// }
@@ -49,12 +53,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
49 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { 53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
50 let variants = enum_def.variants(ctx.db); 54 let variants = enum_def.variants(ctx.db);
51 55
52 variants 56 let mut variants = variants
53 .into_iter() 57 .into_iter()
54 .filter_map(|variant| build_pat(ctx.db, module, variant)) 58 .filter_map(|variant| build_pat(ctx.db, module, variant))
55 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
56 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
57 .collect() 61 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() {
63 // Match `Some` variant first.
64 mark::hit!(option_order);
65 variants.reverse()
66 }
67 variants
58 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { 68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
59 // Partial fill not currently supported for tuple of enums. 69 // Partial fill not currently supported for tuple of enums.
60 if !arms.is_empty() { 70 if !arms.is_empty() {
@@ -93,10 +103,23 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
93 } 103 }
94 104
95 let target = match_expr.syntax().text_range(); 105 let target = match_expr.syntax().text_range();
96 acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |edit| { 106 acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| {
97 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); 107 let new_arm_list = match_arm_list.remove_placeholder();
98 edit.set_cursor(expr.syntax().text_range().start()); 108 let n_old_arms = new_arm_list.arms().count();
99 edit.replace_ast(match_arm_list, new_arm_list); 109 let new_arm_list = new_arm_list.append_arms(missing_arms);
110 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
111 let old_range = match_arm_list.syntax().text_range();
112 match (first_new_arm, ctx.config.snippet_cap) {
113 (Some(first_new_arm), Some(cap)) => {
114 let snippet = render_snippet(
115 cap,
116 new_arm_list.syntax(),
117 Cursor::Before(first_new_arm.syntax()),
118 );
119 builder.replace_snippet(cap, old_range, snippet);
120 }
121 _ => builder.replace(old_range, new_arm_list.to_string()),
122 }
100 }) 123 })
101} 124}
102 125
@@ -167,7 +190,12 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O
167 190
168#[cfg(test)] 191#[cfg(test)]
169mod tests { 192mod tests {
170 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 193 use test_utils::mark;
194
195 use crate::{
196 tests::{check_assist, check_assist_not_applicable, check_assist_target},
197 utils::FamousDefs,
198 };
171 199
172 use super::fill_match_arms; 200 use super::fill_match_arms;
173 201
@@ -214,12 +242,12 @@ mod tests {
214 r#" 242 r#"
215 enum A { 243 enum A {
216 As, 244 As,
217 Bs{x:i32, y:Option<i32>}, 245 Bs { x: i32, y: Option<i32> },
218 Cs(i32, Option<i32>), 246 Cs(i32, Option<i32>),
219 } 247 }
220 fn main() { 248 fn main() {
221 match A::As<|> { 249 match A::As<|> {
222 A::Bs{x,y:Some(_)} => {} 250 A::Bs { x, y: Some(_) } => {}
223 A::Cs(_, Some(_)) => {} 251 A::Cs(_, Some(_)) => {}
224 } 252 }
225 } 253 }
@@ -227,14 +255,14 @@ mod tests {
227 r#" 255 r#"
228 enum A { 256 enum A {
229 As, 257 As,
230 Bs{x:i32, y:Option<i32>}, 258 Bs { x: i32, y: Option<i32> },
231 Cs(i32, Option<i32>), 259 Cs(i32, Option<i32>),
232 } 260 }
233 fn main() { 261 fn main() {
234 match <|>A::As { 262 match A::As {
235 A::Bs{x,y:Some(_)} => {} 263 A::Bs { x, y: Some(_) } => {}
236 A::Cs(_, Some(_)) => {} 264 A::Cs(_, Some(_)) => {}
237 A::As => {} 265 $0A::As => {}
238 } 266 }
239 } 267 }
240 "#, 268 "#,
@@ -264,9 +292,9 @@ mod tests {
264 Cs(Option<i32>), 292 Cs(Option<i32>),
265 } 293 }
266 fn main() { 294 fn main() {
267 match <|>A::As { 295 match A::As {
268 A::Cs(_) | A::Bs => {} 296 A::Cs(_) | A::Bs => {}
269 A::As => {} 297 $0A::As => {}
270 } 298 }
271 } 299 }
272 "#, 300 "#,
@@ -310,11 +338,11 @@ mod tests {
310 Ys, 338 Ys,
311 } 339 }
312 fn main() { 340 fn main() {
313 match <|>A::As { 341 match A::As {
314 A::Bs if 0 < 1 => {} 342 A::Bs if 0 < 1 => {}
315 A::Ds(_value) => { let x = 1; } 343 A::Ds(_value) => { let x = 1; }
316 A::Es(B::Xs) => (), 344 A::Es(B::Xs) => (),
317 A::As => {} 345 $0A::As => {}
318 A::Cs => {} 346 A::Cs => {}
319 } 347 }
320 } 348 }
@@ -332,7 +360,7 @@ mod tests {
332 Bs, 360 Bs,
333 Cs(String), 361 Cs(String),
334 Ds(String, String), 362 Ds(String, String),
335 Es{ x: usize, y: usize } 363 Es { x: usize, y: usize }
336 } 364 }
337 365
338 fn main() { 366 fn main() {
@@ -346,13 +374,13 @@ mod tests {
346 Bs, 374 Bs,
347 Cs(String), 375 Cs(String),
348 Ds(String, String), 376 Ds(String, String),
349 Es{ x: usize, y: usize } 377 Es { x: usize, y: usize }
350 } 378 }
351 379
352 fn main() { 380 fn main() {
353 let a = A::As; 381 let a = A::As;
354 match <|>a { 382 match a {
355 A::As => {} 383 $0A::As => {}
356 A::Bs => {} 384 A::Bs => {}
357 A::Cs(_) => {} 385 A::Cs(_) => {}
358 A::Ds(_, _) => {} 386 A::Ds(_, _) => {}
@@ -368,14 +396,8 @@ mod tests {
368 check_assist( 396 check_assist(
369 fill_match_arms, 397 fill_match_arms,
370 r#" 398 r#"
371 enum A { 399 enum A { One, Two }
372 One, 400 enum B { One, Two }
373 Two,
374 }
375 enum B {
376 One,
377 Two,
378 }
379 401
380 fn main() { 402 fn main() {
381 let a = A::One; 403 let a = A::One;
@@ -384,20 +406,14 @@ mod tests {
384 } 406 }
385 "#, 407 "#,
386 r#" 408 r#"
387 enum A { 409 enum A { One, Two }
388 One, 410 enum B { One, Two }
389 Two,
390 }
391 enum B {
392 One,
393 Two,
394 }
395 411
396 fn main() { 412 fn main() {
397 let a = A::One; 413 let a = A::One;
398 let b = B::One; 414 let b = B::One;
399 match <|>(a, b) { 415 match (a, b) {
400 (A::One, B::One) => {} 416 $0(A::One, B::One) => {}
401 (A::One, B::Two) => {} 417 (A::One, B::Two) => {}
402 (A::Two, B::One) => {} 418 (A::Two, B::One) => {}
403 (A::Two, B::Two) => {} 419 (A::Two, B::Two) => {}
@@ -412,14 +428,8 @@ mod tests {
412 check_assist( 428 check_assist(
413 fill_match_arms, 429 fill_match_arms,
414 r#" 430 r#"
415 enum A { 431 enum A { One, Two }
416 One, 432 enum B { One, Two }
417 Two,
418 }
419 enum B {
420 One,
421 Two,
422 }
423 433
424 fn main() { 434 fn main() {
425 let a = A::One; 435 let a = A::One;
@@ -428,20 +438,14 @@ mod tests {
428 } 438 }
429 "#, 439 "#,
430 r#" 440 r#"
431 enum A { 441 enum A { One, Two }
432 One, 442 enum B { One, Two }
433 Two,
434 }
435 enum B {
436 One,
437 Two,
438 }
439 443
440 fn main() { 444 fn main() {
441 let a = A::One; 445 let a = A::One;
442 let b = B::One; 446 let b = B::One;
443 match <|>(&a, &b) { 447 match (&a, &b) {
444 (A::One, B::One) => {} 448 $0(A::One, B::One) => {}
445 (A::One, B::Two) => {} 449 (A::One, B::Two) => {}
446 (A::Two, B::One) => {} 450 (A::Two, B::One) => {}
447 (A::Two, B::Two) => {} 451 (A::Two, B::Two) => {}
@@ -456,14 +460,8 @@ mod tests {
456 check_assist_not_applicable( 460 check_assist_not_applicable(
457 fill_match_arms, 461 fill_match_arms,
458 r#" 462 r#"
459 enum A { 463 enum A { One, Two }
460 One, 464 enum B { One, Two }
461 Two,
462 }
463 enum B {
464 One,
465 Two,
466 }
467 465
468 fn main() { 466 fn main() {
469 let a = A::One; 467 let a = A::One;
@@ -481,14 +479,8 @@ mod tests {
481 check_assist_not_applicable( 479 check_assist_not_applicable(
482 fill_match_arms, 480 fill_match_arms,
483 r#" 481 r#"
484 enum A { 482 enum A { One, Two }
485 One, 483 enum B { One, Two }
486 Two,
487 }
488 enum B {
489 One,
490 Two,
491 }
492 484
493 fn main() { 485 fn main() {
494 let a = A::One; 486 let a = A::One;
@@ -512,10 +504,7 @@ mod tests {
512 check_assist_not_applicable( 504 check_assist_not_applicable(
513 fill_match_arms, 505 fill_match_arms,
514 r#" 506 r#"
515 enum A { 507 enum A { One, Two }
516 One,
517 Two,
518 }
519 508
520 fn main() { 509 fn main() {
521 let a = A::One; 510 let a = A::One;
@@ -531,9 +520,7 @@ mod tests {
531 check_assist( 520 check_assist(
532 fill_match_arms, 521 fill_match_arms,
533 r#" 522 r#"
534 enum A { 523 enum A { As }
535 As,
536 }
537 524
538 fn foo(a: &A) { 525 fn foo(a: &A) {
539 match a<|> { 526 match a<|> {
@@ -541,13 +528,11 @@ mod tests {
541 } 528 }
542 "#, 529 "#,
543 r#" 530 r#"
544 enum A { 531 enum A { As }
545 As,
546 }
547 532
548 fn foo(a: &A) { 533 fn foo(a: &A) {
549 match <|>a { 534 match a {
550 A::As => {} 535 $0A::As => {}
551 } 536 }
552 } 537 }
553 "#, 538 "#,
@@ -557,7 +542,7 @@ mod tests {
557 fill_match_arms, 542 fill_match_arms,
558 r#" 543 r#"
559 enum A { 544 enum A {
560 Es{ x: usize, y: usize } 545 Es { x: usize, y: usize }
561 } 546 }
562 547
563 fn foo(a: &mut A) { 548 fn foo(a: &mut A) {
@@ -567,12 +552,12 @@ mod tests {
567 "#, 552 "#,
568 r#" 553 r#"
569 enum A { 554 enum A {
570 Es{ x: usize, y: usize } 555 Es { x: usize, y: usize }
571 } 556 }
572 557
573 fn foo(a: &mut A) { 558 fn foo(a: &mut A) {
574 match <|>a { 559 match a {
575 A::Es { x, y } => {} 560 $0A::Es { x, y } => {}
576 } 561 }
577 } 562 }
578 "#, 563 "#,
@@ -611,8 +596,8 @@ mod tests {
611 enum E { X, Y } 596 enum E { X, Y }
612 597
613 fn main() { 598 fn main() {
614 match <|>E::X { 599 match E::X {
615 E::X => {} 600 $0E::X => {}
616 E::Y => {} 601 E::Y => {}
617 } 602 }
618 } 603 }
@@ -639,8 +624,8 @@ mod tests {
639 use foo::E::X; 624 use foo::E::X;
640 625
641 fn main() { 626 fn main() {
642 match <|>X { 627 match X {
643 X => {} 628 $0X => {}
644 foo::E::Y => {} 629 foo::E::Y => {}
645 } 630 }
646 } 631 }
@@ -653,10 +638,7 @@ mod tests {
653 check_assist( 638 check_assist(
654 fill_match_arms, 639 fill_match_arms,
655 r#" 640 r#"
656 enum A { 641 enum A { One, Two }
657 One,
658 Two,
659 }
660 fn foo(a: A) { 642 fn foo(a: A) {
661 match a { 643 match a {
662 // foo bar baz<|> 644 // foo bar baz<|>
@@ -666,16 +648,13 @@ mod tests {
666 } 648 }
667 "#, 649 "#,
668 r#" 650 r#"
669 enum A { 651 enum A { One, Two }
670 One,
671 Two,
672 }
673 fn foo(a: A) { 652 fn foo(a: A) {
674 match <|>a { 653 match a {
675 // foo bar baz 654 // foo bar baz
676 A::One => {} 655 A::One => {}
677 // This is where the rest should be 656 // This is where the rest should be
678 A::Two => {} 657 $0A::Two => {}
679 } 658 }
680 } 659 }
681 "#, 660 "#,
@@ -687,10 +666,7 @@ mod tests {
687 check_assist( 666 check_assist(
688 fill_match_arms, 667 fill_match_arms,
689 r#" 668 r#"
690 enum A { 669 enum A { One, Two }
691 One,
692 Two,
693 }
694 fn foo(a: A) { 670 fn foo(a: A) {
695 match a { 671 match a {
696 // foo bar baz<|> 672 // foo bar baz<|>
@@ -698,14 +674,11 @@ mod tests {
698 } 674 }
699 "#, 675 "#,
700 r#" 676 r#"
701 enum A { 677 enum A { One, Two }
702 One,
703 Two,
704 }
705 fn foo(a: A) { 678 fn foo(a: A) {
706 match <|>a { 679 match a {
707 // foo bar baz 680 // foo bar baz
708 A::One => {} 681 $0A::One => {}
709 A::Two => {} 682 A::Two => {}
710 } 683 }
711 } 684 }
@@ -728,12 +701,37 @@ mod tests {
728 r#" 701 r#"
729 enum A { One, Two, } 702 enum A { One, Two, }
730 fn foo(a: A) { 703 fn foo(a: A) {
731 match <|>a { 704 match a {
732 A::One => {} 705 $0A::One => {}
733 A::Two => {} 706 A::Two => {}
734 } 707 }
735 } 708 }
736 "#, 709 "#,
737 ); 710 );
738 } 711 }
712
713 #[test]
714 fn option_order() {
715 mark::check!(option_order);
716 let before = r#"
717fn foo(opt: Option<i32>) {
718 match opt<|> {
719 }
720}"#;
721 let before =
722 &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE);
723
724 check_assist(
725 fill_match_arms,
726 before,
727 r#"
728fn foo(opt: Option<i32>) {
729 match opt {
730 $0Some(_) => {}
731 None => {}
732 }
733}
734"#,
735 );
736 }
739} 737}
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..9ec42f568
--- /dev/null
+++ b/crates/ra_assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,559 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ra_db::FileId;
3use ra_syntax::{
4 ast, AstNode,
5 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
6 SyntaxNode, TextRange, TextSize,
7};
8
9use crate::{AssistContext, AssistId, Assists};
10
11// FIXME: this really should be a fix for diagnostic, rather than an assist.
12
13// Assist: fix_visibility
14//
15// Makes inaccessible item public.
16//
17// ```
18// mod m {
19// fn frobnicate() {}
20// }
21// fn main() {
22// m::frobnicate<|>() {}
23// }
24// ```
25// ->
26// ```
27// mod m {
28// $0pub(crate) fn frobnicate() {}
29// }
30// fn main() {
31// m::frobnicate() {}
32// }
33// ```
34pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 add_vis_to_referenced_module_def(acc, ctx)
36 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
37}
38
39fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let path: ast::Path = ctx.find_node_at_offset()?;
41 let path_res = ctx.sema.resolve_path(&path)?;
42 let def = match path_res {
43 PathResolution::Def(def) => def,
44 _ => return None,
45 };
46
47 let current_module = ctx.sema.scope(&path.syntax()).module()?;
48 let target_module = def.module(ctx.db)?;
49
50 let vis = target_module.visibility_of(ctx.db, &def)?;
51 if vis.is_visible_from(ctx.db, current_module.into()) {
52 return None;
53 };
54
55 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?;
56
57 let missing_visibility =
58 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
59
60 let assist_label = match target_name {
61 None => format!("Change visibility to {}", missing_visibility),
62 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
63 };
64
65 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
66 builder.set_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
69 None => builder.insert(offset, format!("{} ", missing_visibility)),
70 }
71 })
72}
73
74fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
75 let record_field: ast::RecordField = ctx.find_node_at_offset()?;
76 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
77
78 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
79 let visibility = record_field_def.visibility(ctx.db);
80 if visibility.is_visible_from(ctx.db, current_module.into()) {
81 return None;
82 }
83
84 let parent = record_field_def.parent_def(ctx.db);
85 let parent_name = parent.name(ctx.db);
86 let target_module = parent.module(ctx.db);
87
88 let in_file_source = record_field_def.source(ctx.db);
89 let (offset, target) = match in_file_source.value {
90 hir::FieldSource::Named(it) => {
91 let s = it.syntax();
92 (vis_offset(s), s.text_range())
93 }
94 hir::FieldSource::Pos(it) => {
95 let s = it.syntax();
96 (vis_offset(s), s.text_range())
97 }
98 };
99
100 let missing_visibility =
101 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
102 let target_file = in_file_source.file_id.original_file(ctx.db);
103
104 let target_name = record_field_def.name(ctx.db);
105 let assist_label =
106 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
107
108 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
109 builder.set_file(target_file);
110 match ctx.config.snippet_cap {
111 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
112 None => builder.insert(offset, format!("{} ", missing_visibility)),
113 }
114 })
115}
116
117fn target_data_for_def(
118 db: &dyn HirDatabase,
119 def: hir::ModuleDef,
120) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> {
121 fn offset_target_and_file_id<S, Ast>(
122 db: &dyn HirDatabase,
123 x: S,
124 ) -> (TextSize, TextRange, FileId)
125 where
126 S: HasSource<Ast = Ast>,
127 Ast: AstNode,
128 {
129 let source = x.source(db);
130 let in_file_syntax = source.syntax();
131 let file_id = in_file_syntax.file_id;
132 let syntax = in_file_syntax.value;
133 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast()))
134 }
135
136 let target_name;
137 let (offset, target, target_file) = match def {
138 hir::ModuleDef::Function(f) => {
139 target_name = Some(f.name(db));
140 offset_target_and_file_id(db, f)
141 }
142 hir::ModuleDef::Adt(adt) => {
143 target_name = Some(adt.name(db));
144 match adt {
145 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
146 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
147 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
148 }
149 }
150 hir::ModuleDef::Const(c) => {
151 target_name = c.name(db);
152 offset_target_and_file_id(db, c)
153 }
154 hir::ModuleDef::Static(s) => {
155 target_name = s.name(db);
156 offset_target_and_file_id(db, s)
157 }
158 hir::ModuleDef::Trait(t) => {
159 target_name = Some(t.name(db));
160 offset_target_and_file_id(db, t)
161 }
162 hir::ModuleDef::TypeAlias(t) => {
163 target_name = Some(t.name(db));
164 offset_target_and_file_id(db, t)
165 }
166 hir::ModuleDef::Module(m) => {
167 target_name = m.name(db);
168 let in_file_source = m.declaration_source(db)?;
169 let file_id = in_file_source.file_id.original_file(db.upcast());
170 let syntax = in_file_source.value.syntax();
171 (vis_offset(syntax), syntax.text_range(), file_id)
172 }
173 // Enum variants can't be private, we can't modify builtin types
174 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
175 };
176
177 Some((offset, target, target_file, target_name))
178}
179
180fn vis_offset(node: &SyntaxNode) -> TextSize {
181 node.children_with_tokens()
182 .skip_while(|it| match it.kind() {
183 WHITESPACE | COMMENT | ATTR => true,
184 _ => false,
185 })
186 .next()
187 .map(|it| it.text_range().start())
188 .unwrap_or_else(|| node.text_range().start())
189}
190
191#[cfg(test)]
192mod tests {
193 use crate::tests::{check_assist, check_assist_not_applicable};
194
195 use super::*;
196
197 #[test]
198 fn fix_visibility_of_fn() {
199 check_assist(
200 fix_visibility,
201 r"mod foo { fn foo() {} }
202 fn main() { foo::foo<|>() } ",
203 r"mod foo { $0pub(crate) fn foo() {} }
204 fn main() { foo::foo() } ",
205 );
206 check_assist_not_applicable(
207 fix_visibility,
208 r"mod foo { pub fn foo() {} }
209 fn main() { foo::foo<|>() } ",
210 )
211 }
212
213 #[test]
214 fn fix_visibility_of_adt_in_submodule() {
215 check_assist(
216 fix_visibility,
217 r"mod foo { struct Foo; }
218 fn main() { foo::Foo<|> } ",
219 r"mod foo { $0pub(crate) struct Foo; }
220 fn main() { foo::Foo } ",
221 );
222 check_assist_not_applicable(
223 fix_visibility,
224 r"mod foo { pub struct Foo; }
225 fn main() { foo::Foo<|> } ",
226 );
227 check_assist(
228 fix_visibility,
229 r"mod foo { enum Foo; }
230 fn main() { foo::Foo<|> } ",
231 r"mod foo { $0pub(crate) enum Foo; }
232 fn main() { foo::Foo } ",
233 );
234 check_assist_not_applicable(
235 fix_visibility,
236 r"mod foo { pub enum Foo; }
237 fn main() { foo::Foo<|> } ",
238 );
239 check_assist(
240 fix_visibility,
241 r"mod foo { union Foo; }
242 fn main() { foo::Foo<|> } ",
243 r"mod foo { $0pub(crate) union Foo; }
244 fn main() { foo::Foo } ",
245 );
246 check_assist_not_applicable(
247 fix_visibility,
248 r"mod foo { pub union Foo; }
249 fn main() { foo::Foo<|> } ",
250 );
251 }
252
253 #[test]
254 fn fix_visibility_of_adt_in_other_file() {
255 check_assist(
256 fix_visibility,
257 r"
258 //- /main.rs
259 mod foo;
260 fn main() { foo::Foo<|> }
261
262 //- /foo.rs
263 struct Foo;
264 ",
265 r"$0pub(crate) struct Foo;
266
267",
268 );
269 }
270
271 #[test]
272 fn fix_visibility_of_struct_field() {
273 check_assist(
274 fix_visibility,
275 r"mod foo { pub struct Foo { bar: (), } }
276 fn main() { foo::Foo { <|>bar: () }; } ",
277 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
278 fn main() { foo::Foo { bar: () }; } ",
279 );
280 check_assist(
281 fix_visibility,
282 r"//- /lib.rs
283 mod foo;
284 fn main() { foo::Foo { <|>bar: () }; }
285 //- /foo.rs
286 pub struct Foo { bar: () }
287 ",
288 r"pub struct Foo { $0pub(crate) bar: () }
289
290",
291 );
292 check_assist_not_applicable(
293 fix_visibility,
294 r"mod foo { pub struct Foo { pub bar: (), } }
295 fn main() { foo::Foo { <|>bar: () }; } ",
296 );
297 check_assist_not_applicable(
298 fix_visibility,
299 r"//- /lib.rs
300 mod foo;
301 fn main() { foo::Foo { <|>bar: () }; }
302 //- /foo.rs
303 pub struct Foo { pub bar: () }
304 ",
305 );
306 }
307
308 #[test]
309 fn fix_visibility_of_enum_variant_field() {
310 check_assist(
311 fix_visibility,
312 r"mod foo { pub enum Foo { Bar { bar: () } } }
313 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
314 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
315 fn main() { foo::Foo::Bar { bar: () }; } ",
316 );
317 check_assist(
318 fix_visibility,
319 r"//- /lib.rs
320 mod foo;
321 fn main() { foo::Foo::Bar { <|>bar: () }; }
322 //- /foo.rs
323 pub enum Foo { Bar { bar: () } }
324 ",
325 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
326
327",
328 );
329 check_assist_not_applicable(
330 fix_visibility,
331 r"mod foo { pub struct Foo { pub bar: (), } }
332 fn main() { foo::Foo { <|>bar: () }; } ",
333 );
334 check_assist_not_applicable(
335 fix_visibility,
336 r"//- /lib.rs
337 mod foo;
338 fn main() { foo::Foo { <|>bar: () }; }
339 //- /foo.rs
340 pub struct Foo { pub bar: () }
341 ",
342 );
343 }
344
345 #[test]
346 #[ignore]
347 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
348 fn fix_visibility_of_union_field() {
349 check_assist(
350 fix_visibility,
351 r"mod foo { pub union Foo { bar: (), } }
352 fn main() { foo::Foo { <|>bar: () }; } ",
353 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
354 fn main() { foo::Foo { bar: () }; } ",
355 );
356 check_assist(
357 fix_visibility,
358 r"//- /lib.rs
359 mod foo;
360 fn main() { foo::Foo { <|>bar: () }; }
361 //- /foo.rs
362 pub union Foo { bar: () }
363 ",
364 r"pub union Foo { $0pub(crate) bar: () }
365
366",
367 );
368 check_assist_not_applicable(
369 fix_visibility,
370 r"mod foo { pub union Foo { pub bar: (), } }
371 fn main() { foo::Foo { <|>bar: () }; } ",
372 );
373 check_assist_not_applicable(
374 fix_visibility,
375 r"//- /lib.rs
376 mod foo;
377 fn main() { foo::Foo { <|>bar: () }; }
378 //- /foo.rs
379 pub union Foo { pub bar: () }
380 ",
381 );
382 }
383
384 #[test]
385 fn fix_visibility_of_const() {
386 check_assist(
387 fix_visibility,
388 r"mod foo { const FOO: () = (); }
389 fn main() { foo::FOO<|> } ",
390 r"mod foo { $0pub(crate) const FOO: () = (); }
391 fn main() { foo::FOO } ",
392 );
393 check_assist_not_applicable(
394 fix_visibility,
395 r"mod foo { pub const FOO: () = (); }
396 fn main() { foo::FOO<|> } ",
397 );
398 }
399
400 #[test]
401 fn fix_visibility_of_static() {
402 check_assist(
403 fix_visibility,
404 r"mod foo { static FOO: () = (); }
405 fn main() { foo::FOO<|> } ",
406 r"mod foo { $0pub(crate) static FOO: () = (); }
407 fn main() { foo::FOO } ",
408 );
409 check_assist_not_applicable(
410 fix_visibility,
411 r"mod foo { pub static FOO: () = (); }
412 fn main() { foo::FOO<|> } ",
413 );
414 }
415
416 #[test]
417 fn fix_visibility_of_trait() {
418 check_assist(
419 fix_visibility,
420 r"mod foo { trait Foo { fn foo(&self) {} } }
421 fn main() { let x: &dyn foo::<|>Foo; } ",
422 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
423 fn main() { let x: &dyn foo::Foo; } ",
424 );
425 check_assist_not_applicable(
426 fix_visibility,
427 r"mod foo { pub trait Foo { fn foo(&self) {} } }
428 fn main() { let x: &dyn foo::Foo<|>; } ",
429 );
430 }
431
432 #[test]
433 fn fix_visibility_of_type_alias() {
434 check_assist(
435 fix_visibility,
436 r"mod foo { type Foo = (); }
437 fn main() { let x: foo::Foo<|>; } ",
438 r"mod foo { $0pub(crate) type Foo = (); }
439 fn main() { let x: foo::Foo; } ",
440 );
441 check_assist_not_applicable(
442 fix_visibility,
443 r"mod foo { pub type Foo = (); }
444 fn main() { let x: foo::Foo<|>; } ",
445 );
446 }
447
448 #[test]
449 fn fix_visibility_of_module() {
450 check_assist(
451 fix_visibility,
452 r"mod foo { mod bar { fn bar() {} } }
453 fn main() { foo::bar<|>::bar(); } ",
454 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
455 fn main() { foo::bar::bar(); } ",
456 );
457
458 check_assist(
459 fix_visibility,
460 r"
461 //- /main.rs
462 mod foo;
463 fn main() { foo::bar<|>::baz(); }
464
465 //- /foo.rs
466 mod bar {
467 pub fn baz() {}
468 }
469 ",
470 r"$0pub(crate) mod bar {
471 pub fn baz() {}
472}
473
474",
475 );
476
477 check_assist_not_applicable(
478 fix_visibility,
479 r"mod foo { pub mod bar { pub fn bar() {} } }
480 fn main() { foo::bar<|>::bar(); } ",
481 );
482 }
483
484 #[test]
485 fn fix_visibility_of_inline_module_in_other_file() {
486 check_assist(
487 fix_visibility,
488 r"
489 //- /main.rs
490 mod foo;
491 fn main() { foo::bar<|>::baz(); }
492
493 //- /foo.rs
494 mod bar;
495
496 //- /foo/bar.rs
497 pub fn baz() {}
498 }
499 ",
500 r"$0pub(crate) mod bar;
501",
502 );
503 }
504
505 #[test]
506 fn fix_visibility_of_module_declaration_in_other_file() {
507 check_assist(
508 fix_visibility,
509 r"//- /main.rs
510 mod foo;
511 fn main() { foo::bar<|>>::baz(); }
512
513 //- /foo.rs
514 mod bar {
515 pub fn baz() {}
516 }",
517 r"$0pub(crate) mod bar {
518 pub fn baz() {}
519}
520",
521 );
522 }
523
524 #[test]
525 fn adds_pub_when_target_is_in_another_crate() {
526 check_assist(
527 fix_visibility,
528 r"//- /main.rs crate:a deps:foo
529 foo::Bar<|>
530 //- /lib.rs crate:foo
531 struct Bar;",
532 r"$0pub struct Bar;
533",
534 )
535 }
536
537 #[test]
538 #[ignore]
539 // FIXME handle reexports properly
540 fn fix_visibility_of_reexport() {
541 check_assist(
542 fix_visibility,
543 r"
544 mod foo {
545 use bar::Baz;
546 mod bar { pub(super) struct Baz; }
547 }
548 foo::Baz<|>
549 ",
550 r"
551 mod foo {
552 $0pub(crate) use bar::Baz;
553 mod bar { pub(super) struct Baz; }
554 }
555 foo::Baz
556 ",
557 )
558 }
559}
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 692ba4895..573196576 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -85,17 +85,13 @@ mod tests {
85 check_assist( 85 check_assist(
86 flip_binexpr, 86 flip_binexpr,
87 "fn f() { let res = 1 ==<|> 2; }", 87 "fn f() { let res = 1 ==<|> 2; }",
88 "fn f() { let res = 2 ==<|> 1; }", 88 "fn f() { let res = 2 == 1; }",
89 ) 89 )
90 } 90 }
91 91
92 #[test] 92 #[test]
93 fn flip_binexpr_works_for_gt() { 93 fn flip_binexpr_works_for_gt() {
94 check_assist( 94 check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }")
95 flip_binexpr,
96 "fn f() { let res = 1 ><|> 2; }",
97 "fn f() { let res = 2 <<|> 1; }",
98 )
99 } 95 }
100 96
101 #[test] 97 #[test]
@@ -103,7 +99,7 @@ mod tests {
103 check_assist( 99 check_assist(
104 flip_binexpr, 100 flip_binexpr,
105 "fn f() { let res = 1 <=<|> 2; }", 101 "fn f() { let res = 1 <=<|> 2; }",
106 "fn f() { let res = 2 >=<|> 1; }", 102 "fn f() { let res = 2 >= 1; }",
107 ) 103 )
108 } 104 }
109 105
@@ -112,7 +108,7 @@ mod tests {
112 check_assist( 108 check_assist(
113 flip_binexpr, 109 flip_binexpr,
114 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", 110 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
115 "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", 111 "fn f() { let res = (2 + 2) == (1 + 1); }",
116 ) 112 )
117 } 113 }
118 114
@@ -132,7 +128,7 @@ mod tests {
132 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { 128 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
133 match other.downcast_ref::<Self>() { 129 match other.downcast_ref::<Self>() {
134 None => false, 130 None => false,
135 Some(it) => self ==<|> it, 131 Some(it) => self == it,
136 } 132 }
137 } 133 }
138 "#, 134 "#,
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index dfe2a7fed..a57a1c463 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -45,7 +45,7 @@ mod tests {
45 check_assist( 45 check_assist(
46 flip_comma, 46 flip_comma,
47 "fn foo(x: i32,<|> y: Result<(), ()>) {}", 47 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
48 "fn foo(y: Result<(), ()>,<|> x: i32) {}", 48 "fn foo(y: Result<(), ()>, x: i32) {}",
49 ) 49 )
50 } 50 }
51 51
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index 8a08702ab..0115adc8b 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -60,7 +60,7 @@ mod tests {
60 check_assist( 60 check_assist(
61 flip_trait_bound, 61 flip_trait_bound,
62 "struct S<T> where T: A <|>+ B { }", 62 "struct S<T> where T: A <|>+ B { }",
63 "struct S<T> where T: B <|>+ A { }", 63 "struct S<T> where T: B + A { }",
64 ) 64 )
65 } 65 }
66 66
@@ -69,13 +69,13 @@ mod tests {
69 check_assist( 69 check_assist(
70 flip_trait_bound, 70 flip_trait_bound,
71 "impl X for S<T> where T: A +<|> B { }", 71 "impl X for S<T> where T: A +<|> B { }",
72 "impl X for S<T> where T: B +<|> A { }", 72 "impl X for S<T> where T: B + A { }",
73 ) 73 )
74 } 74 }
75 75
76 #[test] 76 #[test]
77 fn flip_trait_bound_works_for_fn() { 77 fn flip_trait_bound_works_for_fn() {
78 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }") 78 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
79 } 79 }
80 80
81 #[test] 81 #[test]
@@ -83,7 +83,7 @@ mod tests {
83 check_assist( 83 check_assist(
84 flip_trait_bound, 84 flip_trait_bound,
85 "fn f<T>(t: T) where T: A +<|> B { }", 85 "fn f<T>(t: T) where T: A +<|> B { }",
86 "fn f<T>(t: T) where T: B +<|> A { }", 86 "fn f<T>(t: T) where T: B + A { }",
87 ) 87 )
88 } 88 }
89 89
@@ -92,7 +92,7 @@ mod tests {
92 check_assist( 92 check_assist(
93 flip_trait_bound, 93 flip_trait_bound,
94 "fn f<T>(t: T) where T: A <|>+ 'static { }", 94 "fn f<T>(t: T) where T: A <|>+ 'static { }",
95 "fn f<T>(t: T) where T: 'static <|>+ A { }", 95 "fn f<T>(t: T) where T: 'static + A { }",
96 ) 96 )
97 } 97 }
98 98
@@ -101,7 +101,7 @@ mod tests {
101 check_assist( 101 check_assist(
102 flip_trait_bound, 102 flip_trait_bound,
103 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", 103 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
104 "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }", 104 "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
105 ) 105 )
106 } 106 }
107 107
@@ -110,7 +110,7 @@ mod tests {
110 check_assist( 110 check_assist(
111 flip_trait_bound, 111 flip_trait_bound,
112 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", 112 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
113 "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }", 113 "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
114 ) 114 )
115 } 115 }
116} 116}
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index 5b26814d3..d26e68847 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 ast::{self, AstNode, AstToken}, 3 ast::{self, AstNode, AstToken},
4 TextRange, 4 TextRange,
5}; 5};
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::{ 8use crate::{
9 assist_context::{AssistContext, Assists}, 9 assist_context::{AssistContext, Assists},
@@ -33,11 +33,11 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
33 _ => return None, 33 _ => return None,
34 }; 34 };
35 if bind_pat.mut_token().is_some() { 35 if bind_pat.mut_token().is_some() {
36 tested_by!(test_not_inline_mut_variable); 36 mark::hit!(test_not_inline_mut_variable);
37 return None; 37 return None;
38 } 38 }
39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { 39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
40 tested_by!(not_applicable_outside_of_bind_pat); 40 mark::hit!(not_applicable_outside_of_bind_pat);
41 return None; 41 return None;
42 } 42 }
43 let initializer_expr = let_stmt.initializer()?; 43 let initializer_expr = let_stmt.initializer()?;
@@ -46,7 +46,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
46 let def = Definition::Local(def); 46 let def = Definition::Local(def);
47 let refs = def.find_usages(ctx.db, None); 47 let refs = def.find_usages(ctx.db, None);
48 if refs.is_empty() { 48 if refs.is_empty() {
49 tested_by!(test_not_applicable_if_variable_unused); 49 mark::hit!(test_not_applicable_if_variable_unused);
50 return None; 50 return None;
51 }; 51 };
52 52
@@ -116,13 +116,12 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
116 let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; 116 let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() };
117 builder.replace(desc.file_range.range, replacement) 117 builder.replace(desc.file_range.range, replacement)
118 } 118 }
119 builder.set_cursor(delete_range.start())
120 }) 119 })
121} 120}
122 121
123#[cfg(test)] 122#[cfg(test)]
124mod tests { 123mod tests {
125 use test_utils::covers; 124 use test_utils::mark;
126 125
127 use crate::tests::{check_assist, check_assist_not_applicable}; 126 use crate::tests::{check_assist, check_assist_not_applicable};
128 127
@@ -149,7 +148,7 @@ fn foo() {
149 r" 148 r"
150fn bar(a: usize) {} 149fn bar(a: usize) {}
151fn foo() { 150fn foo() {
152 <|>1 + 1; 151 1 + 1;
153 if 1 > 10 { 152 if 1 > 10 {
154 } 153 }
155 154
@@ -183,7 +182,7 @@ fn foo() {
183 r" 182 r"
184fn bar(a: usize) {} 183fn bar(a: usize) {}
185fn foo() { 184fn foo() {
186 <|>(1 + 1) + 1; 185 (1 + 1) + 1;
187 if (1 + 1) > 10 { 186 if (1 + 1) > 10 {
188 } 187 }
189 188
@@ -217,7 +216,7 @@ fn foo() {
217 r" 216 r"
218fn bar(a: usize) {} 217fn bar(a: usize) {}
219fn foo() { 218fn foo() {
220 <|>bar(1) + 1; 219 bar(1) + 1;
221 if bar(1) > 10 { 220 if bar(1) > 10 {
222 } 221 }
223 222
@@ -251,7 +250,7 @@ fn foo() {
251 r" 250 r"
252fn bar(a: usize): usize { a } 251fn bar(a: usize): usize { a }
253fn foo() { 252fn foo() {
254 <|>(bar(1) as u64) + 1; 253 (bar(1) as u64) + 1;
255 if (bar(1) as u64) > 10 { 254 if (bar(1) as u64) > 10 {
256 } 255 }
257 256
@@ -283,7 +282,7 @@ fn foo() {
283}", 282}",
284 r" 283 r"
285fn foo() { 284fn foo() {
286 <|>{ 10 + 1 } + 1; 285 { 10 + 1 } + 1;
287 if { 10 + 1 } > 10 { 286 if { 10 + 1 } > 10 {
288 } 287 }
289 288
@@ -315,7 +314,7 @@ fn foo() {
315}", 314}",
316 r" 315 r"
317fn foo() { 316fn foo() {
318 <|>( 10 + 1 ) + 1; 317 ( 10 + 1 ) + 1;
319 if ( 10 + 1 ) > 10 { 318 if ( 10 + 1 ) > 10 {
320 } 319 }
321 320
@@ -330,7 +329,7 @@ fn foo() {
330 329
331 #[test] 330 #[test]
332 fn test_not_inline_mut_variable() { 331 fn test_not_inline_mut_variable() {
333 covers!(test_not_inline_mut_variable); 332 mark::check!(test_not_inline_mut_variable);
334 check_assist_not_applicable( 333 check_assist_not_applicable(
335 inline_local_variable, 334 inline_local_variable,
336 r" 335 r"
@@ -353,7 +352,7 @@ fn foo() {
353}", 352}",
354 r" 353 r"
355fn foo() { 354fn foo() {
356 <|>let b = bar(10 + 1) * 10; 355 let b = bar(10 + 1) * 10;
357 let c = bar(10 + 1) as usize; 356 let c = bar(10 + 1) as usize;
358}", 357}",
359 ); 358 );
@@ -373,7 +372,7 @@ fn foo() {
373 r" 372 r"
374fn foo() { 373fn foo() {
375 let x = vec![1, 2, 3]; 374 let x = vec![1, 2, 3];
376 <|>let b = x[0] * 10; 375 let b = x[0] * 10;
377 let c = x[0] as usize; 376 let c = x[0] as usize;
378}", 377}",
379 ); 378 );
@@ -393,7 +392,7 @@ fn foo() {
393 r" 392 r"
394fn foo() { 393fn foo() {
395 let bar = vec![1]; 394 let bar = vec![1];
396 <|>let b = bar.len() * 10; 395 let b = bar.len() * 10;
397 let c = bar.len() as usize; 396 let c = bar.len() as usize;
398}", 397}",
399 ); 398 );
@@ -421,7 +420,7 @@ struct Bar {
421 420
422fn foo() { 421fn foo() {
423 let bar = Bar { foo: 1 }; 422 let bar = Bar { foo: 1 };
424 <|>let b = bar.foo * 10; 423 let b = bar.foo * 10;
425 let c = bar.foo as usize; 424 let c = bar.foo as usize;
426}", 425}",
427 ); 426 );
@@ -442,7 +441,7 @@ fn foo() -> Option<usize> {
442 r" 441 r"
443fn foo() -> Option<usize> { 442fn foo() -> Option<usize> {
444 let bar = Some(1); 443 let bar = Some(1);
445 <|>let b = bar? * 10; 444 let b = bar? * 10;
446 let c = bar? as usize; 445 let c = bar? as usize;
447 None 446 None
448}", 447}",
@@ -462,7 +461,7 @@ fn foo() {
462 r" 461 r"
463fn foo() { 462fn foo() {
464 let bar = 10; 463 let bar = 10;
465 <|>let b = &bar * 10; 464 let b = &bar * 10;
466}", 465}",
467 ); 466 );
468 } 467 }
@@ -478,7 +477,7 @@ fn foo() {
478}", 477}",
479 r" 478 r"
480fn foo() { 479fn foo() {
481 <|>let b = (10, 20)[0]; 480 let b = (10, 20)[0];
482}", 481}",
483 ); 482 );
484 } 483 }
@@ -494,7 +493,7 @@ fn foo() {
494}", 493}",
495 r" 494 r"
496fn foo() { 495fn foo() {
497 <|>let b = [1, 2, 3].len(); 496 let b = [1, 2, 3].len();
498}", 497}",
499 ); 498 );
500 } 499 }
@@ -511,7 +510,7 @@ fn foo() {
511}", 510}",
512 r" 511 r"
513fn foo() { 512fn foo() {
514 <|>let b = (10 + 20) * 10; 513 let b = (10 + 20) * 10;
515 let c = (10 + 20) as usize; 514 let c = (10 + 20) as usize;
516}", 515}",
517 ); 516 );
@@ -531,7 +530,7 @@ fn foo() {
531 r" 530 r"
532fn foo() { 531fn foo() {
533 let d = 10; 532 let d = 10;
534 <|>let b = d * 10; 533 let b = d * 10;
535 let c = d as usize; 534 let c = d as usize;
536}", 535}",
537 ); 536 );
@@ -549,7 +548,7 @@ fn foo() {
549}", 548}",
550 r" 549 r"
551fn foo() { 550fn foo() {
552 <|>let b = { 10 } * 10; 551 let b = { 10 } * 10;
553 let c = { 10 } as usize; 552 let c = { 10 } as usize;
554}", 553}",
555 ); 554 );
@@ -569,7 +568,7 @@ fn foo() {
569}", 568}",
570 r" 569 r"
571fn foo() { 570fn foo() {
572 <|>let b = (10 + 20) * 10; 571 let b = (10 + 20) * 10;
573 let c = (10 + 20, 20); 572 let c = (10 + 20, 20);
574 let d = [10 + 20, 10]; 573 let d = [10 + 20, 10];
575 let e = (10 + 20); 574 let e = (10 + 20);
@@ -588,7 +587,7 @@ fn foo() {
588}", 587}",
589 r" 588 r"
590fn foo() { 589fn foo() {
591 <|>for i in vec![10, 20] {} 590 for i in vec![10, 20] {}
592}", 591}",
593 ); 592 );
594 } 593 }
@@ -604,7 +603,7 @@ fn foo() {
604}", 603}",
605 r" 604 r"
606fn foo() { 605fn foo() {
607 <|>while 1 > 0 {} 606 while 1 > 0 {}
608}", 607}",
609 ); 608 );
610 } 609 }
@@ -622,7 +621,7 @@ fn foo() {
622}", 621}",
623 r" 622 r"
624fn foo() { 623fn foo() {
625 <|>loop { 624 loop {
626 break 1 + 1; 625 break 1 + 1;
627 } 626 }
628}", 627}",
@@ -640,7 +639,7 @@ fn foo() {
640}", 639}",
641 r" 640 r"
642fn foo() { 641fn foo() {
643 <|>return 1 > 0; 642 return 1 > 0;
644}", 643}",
645 ); 644 );
646 } 645 }
@@ -656,14 +655,14 @@ fn foo() {
656}", 655}",
657 r" 656 r"
658fn foo() { 657fn foo() {
659 <|>match 1 > 0 {} 658 match 1 > 0 {}
660}", 659}",
661 ); 660 );
662 } 661 }
663 662
664 #[test] 663 #[test]
665 fn test_not_applicable_if_variable_unused() { 664 fn test_not_applicable_if_variable_unused() {
666 covers!(test_not_applicable_if_variable_unused); 665 mark::check!(test_not_applicable_if_variable_unused);
667 check_assist_not_applicable( 666 check_assist_not_applicable(
668 inline_local_variable, 667 inline_local_variable,
669 r" 668 r"
@@ -676,7 +675,7 @@ fn foo() {
676 675
677 #[test] 676 #[test]
678 fn not_applicable_outside_of_bind_pat() { 677 fn not_applicable_outside_of_bind_pat() {
679 covers!(not_applicable_outside_of_bind_pat); 678 mark::check!(not_applicable_outside_of_bind_pat);
680 check_assist_not_applicable( 679 check_assist_not_applicable(
681 inline_local_variable, 680 inline_local_variable,
682 r" 681 r"
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index fdf3ada0d..31d6539f7 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -4,10 +4,10 @@ use ra_syntax::{
4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, 4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
5 WHITESPACE, 5 WHITESPACE,
6 }, 6 },
7 SyntaxNode, TextSize, 7 SyntaxNode,
8}; 8};
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::tested_by; 10use test_utils::mark;
11 11
12use crate::{AssistContext, AssistId, Assists}; 12use crate::{AssistContext, AssistId, Assists};
13 13
@@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
23// -> 23// ->
24// ``` 24// ```
25// fn main() { 25// fn main() {
26// let var_name = (1 + 2); 26// let $0var_name = (1 + 2);
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
@@ -33,7 +33,7 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
33 } 33 }
34 let node = ctx.covering_element(); 34 let node = ctx.covering_element();
35 if node.kind() == COMMENT { 35 if node.kind() == COMMENT {
36 tested_by!(introduce_var_in_comment_is_not_applicable); 36 mark::hit!(introduce_var_in_comment_is_not_applicable);
37 return None; 37 return None;
38 } 38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 39 let expr = node.ancestors().find_map(valid_target_expr)?;
@@ -46,14 +46,13 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { 46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
47 let mut buf = String::new(); 47 let mut buf = String::new();
48 48
49 let cursor_offset = if wrap_in_block { 49 if wrap_in_block {
50 buf.push_str("{ let var_name = "); 50 buf.push_str("{ let var_name = ");
51 TextSize::of("{ let ")
52 } else { 51 } else {
53 buf.push_str("let var_name = "); 52 buf.push_str("let var_name = ");
54 TextSize::of("let ")
55 }; 53 };
56 format_to!(buf, "{}", expr.syntax()); 54 format_to!(buf, "{}", expr.syntax());
55
57 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
58 let is_full_stmt = if let Some(expr_stmt) = &full_stmt { 57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
59 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) 58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
@@ -61,32 +60,47 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
61 false 60 false
62 }; 61 };
63 if is_full_stmt { 62 if is_full_stmt {
64 tested_by!(test_introduce_var_expr_stmt); 63 mark::hit!(test_introduce_var_expr_stmt);
65 if full_stmt.unwrap().semicolon_token().is_none() { 64 if full_stmt.unwrap().semicolon_token().is_none() {
66 buf.push_str(";"); 65 buf.push_str(";");
67 } 66 }
68 edit.replace(expr.syntax().text_range(), buf); 67 let offset = expr.syntax().text_range();
69 } else { 68 match ctx.config.snippet_cap {
70 buf.push_str(";"); 69 Some(cap) => {
71 70 let snip = buf.replace("let var_name", "let $0var_name");
72 // We want to maintain the indent level, 71 edit.replace_snippet(cap, offset, snip)
73 // but we do not want to duplicate possible 72 }
74 // extra newlines in the indent block 73 None => edit.replace(offset, buf),
75 let text = indent.text();
76 if text.starts_with('\n') {
77 buf.push_str("\n");
78 buf.push_str(text.trim_start_matches('\n'));
79 } else {
80 buf.push_str(text);
81 } 74 }
75 return;
76 }
82 77
83 edit.replace(expr.syntax().text_range(), "var_name".to_string()); 78 buf.push_str(";");
84 edit.insert(anchor_stmt.text_range().start(), buf); 79
85 if wrap_in_block { 80 // We want to maintain the indent level,
86 edit.insert(anchor_stmt.text_range().end(), " }"); 81 // but we do not want to duplicate possible
82 // extra newlines in the indent block
83 let text = indent.text();
84 if text.starts_with('\n') {
85 buf.push_str("\n");
86 buf.push_str(text.trim_start_matches('\n'));
87 } else {
88 buf.push_str(text);
89 }
90
91 edit.replace(expr.syntax().text_range(), "var_name".to_string());
92 let offset = anchor_stmt.text_range().start();
93 match ctx.config.snippet_cap {
94 Some(cap) => {
95 let snip = buf.replace("let var_name", "let $0var_name");
96 edit.insert_snippet(cap, offset, snip)
87 } 97 }
98 None => edit.insert(offset, buf),
99 }
100
101 if wrap_in_block {
102 edit.insert(anchor_stmt.text_range().end(), " }");
88 } 103 }
89 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
90 }) 104 })
91} 105}
92 106
@@ -113,7 +127,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
113 expr.syntax().ancestors().find_map(|node| { 127 expr.syntax().ancestors().find_map(|node| {
114 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { 128 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
115 if expr.syntax() == &node { 129 if expr.syntax() == &node {
116 tested_by!(test_introduce_var_last_expr); 130 mark::hit!(test_introduce_var_last_expr);
117 return Some((node, false)); 131 return Some((node, false));
118 } 132 }
119 } 133 }
@@ -134,7 +148,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
134 148
135#[cfg(test)] 149#[cfg(test)]
136mod tests { 150mod tests {
137 use test_utils::covers; 151 use test_utils::mark;
138 152
139 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 153 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
140 154
@@ -144,37 +158,37 @@ mod tests {
144 fn test_introduce_var_simple() { 158 fn test_introduce_var_simple() {
145 check_assist( 159 check_assist(
146 introduce_variable, 160 introduce_variable,
147 " 161 r#"
148fn foo() { 162fn foo() {
149 foo(<|>1 + 1<|>); 163 foo(<|>1 + 1<|>);
150}", 164}"#,
151 " 165 r#"
152fn foo() { 166fn foo() {
153 let <|>var_name = 1 + 1; 167 let $0var_name = 1 + 1;
154 foo(var_name); 168 foo(var_name);
155}", 169}"#,
156 ); 170 );
157 } 171 }
158 172
159 #[test] 173 #[test]
160 fn introduce_var_in_comment_is_not_applicable() { 174 fn introduce_var_in_comment_is_not_applicable() {
161 covers!(introduce_var_in_comment_is_not_applicable); 175 mark::check!(introduce_var_in_comment_is_not_applicable);
162 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); 176 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
163 } 177 }
164 178
165 #[test] 179 #[test]
166 fn test_introduce_var_expr_stmt() { 180 fn test_introduce_var_expr_stmt() {
167 covers!(test_introduce_var_expr_stmt); 181 mark::check!(test_introduce_var_expr_stmt);
168 check_assist( 182 check_assist(
169 introduce_variable, 183 introduce_variable,
170 " 184 r#"
171fn foo() { 185fn foo() {
172 <|>1 + 1<|>; 186 <|>1 + 1<|>;
173}", 187}"#,
174 " 188 r#"
175fn foo() { 189fn foo() {
176 let <|>var_name = 1 + 1; 190 let $0var_name = 1 + 1;
177}", 191}"#,
178 ); 192 );
179 check_assist( 193 check_assist(
180 introduce_variable, 194 introduce_variable,
@@ -185,7 +199,7 @@ fn foo() {
185}", 199}",
186 " 200 "
187fn foo() { 201fn foo() {
188 let <|>var_name = { let x = 0; x }; 202 let $0var_name = { let x = 0; x };
189 something_else(); 203 something_else();
190}", 204}",
191 ); 205 );
@@ -201,7 +215,7 @@ fn foo() {
201}", 215}",
202 " 216 "
203fn foo() { 217fn foo() {
204 let <|>var_name = 1; 218 let $0var_name = 1;
205 var_name + 1; 219 var_name + 1;
206}", 220}",
207 ); 221 );
@@ -209,7 +223,7 @@ fn foo() {
209 223
210 #[test] 224 #[test]
211 fn test_introduce_var_last_expr() { 225 fn test_introduce_var_last_expr() {
212 covers!(test_introduce_var_last_expr); 226 mark::check!(test_introduce_var_last_expr);
213 check_assist( 227 check_assist(
214 introduce_variable, 228 introduce_variable,
215 " 229 "
@@ -218,7 +232,7 @@ fn foo() {
218}", 232}",
219 " 233 "
220fn foo() { 234fn foo() {
221 let <|>var_name = 1 + 1; 235 let $0var_name = 1 + 1;
222 bar(var_name) 236 bar(var_name)
223}", 237}",
224 ); 238 );
@@ -230,7 +244,7 @@ fn foo() {
230}", 244}",
231 " 245 "
232fn foo() { 246fn foo() {
233 let <|>var_name = bar(1 + 1); 247 let $0var_name = bar(1 + 1);
234 var_name 248 var_name
235}", 249}",
236 ) 250 )
@@ -253,7 +267,7 @@ fn main() {
253fn main() { 267fn main() {
254 let x = true; 268 let x = true;
255 let tuple = match x { 269 let tuple = match x {
256 true => { let <|>var_name = 2 + 2; (var_name, true) } 270 true => { let $0var_name = 2 + 2; (var_name, true) }
257 _ => (0, false) 271 _ => (0, false)
258 }; 272 };
259} 273}
@@ -283,7 +297,7 @@ fn main() {
283 let tuple = match x { 297 let tuple = match x {
284 true => { 298 true => {
285 let y = 1; 299 let y = 1;
286 let <|>var_name = 2 + y; 300 let $0var_name = 2 + y;
287 (var_name, true) 301 (var_name, true)
288 } 302 }
289 _ => (0, false) 303 _ => (0, false)
@@ -304,7 +318,7 @@ fn main() {
304", 318",
305 " 319 "
306fn main() { 320fn main() {
307 let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; 321 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
308} 322}
309", 323",
310 ); 324 );
@@ -321,7 +335,7 @@ fn main() {
321", 335",
322 " 336 "
323fn main() { 337fn main() {
324 let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; 338 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
325} 339}
326", 340",
327 ); 341 );
@@ -338,7 +352,7 @@ fn main() {
338", 352",
339 " 353 "
340fn main() { 354fn main() {
341 let <|>var_name = Some(true); 355 let $0var_name = Some(true);
342 let o = var_name; 356 let o = var_name;
343} 357}
344", 358",
@@ -356,7 +370,7 @@ fn main() {
356", 370",
357 " 371 "
358fn main() { 372fn main() {
359 let <|>var_name = bar.foo(); 373 let $0var_name = bar.foo();
360 let v = var_name; 374 let v = var_name;
361} 375}
362", 376",
@@ -374,7 +388,7 @@ fn foo() -> u32 {
374", 388",
375 " 389 "
376fn foo() -> u32 { 390fn foo() -> u32 {
377 let <|>var_name = 2 + 2; 391 let $0var_name = 2 + 2;
378 return var_name; 392 return var_name;
379} 393}
380", 394",
@@ -396,7 +410,7 @@ fn foo() -> u32 {
396fn foo() -> u32 { 410fn foo() -> u32 {
397 411
398 412
399 let <|>var_name = 2 + 2; 413 let $0var_name = 2 + 2;
400 return var_name; 414 return var_name;
401} 415}
402", 416",
@@ -413,7 +427,7 @@ fn foo() -> u32 {
413 " 427 "
414fn foo() -> u32 { 428fn foo() -> u32 {
415 429
416 let <|>var_name = 2 + 2; 430 let $0var_name = 2 + 2;
417 return var_name; 431 return var_name;
418} 432}
419", 433",
@@ -438,7 +452,7 @@ fn foo() -> u32 {
438 // bar 452 // bar
439 453
440 454
441 let <|>var_name = 2 + 2; 455 let $0var_name = 2 + 2;
442 return var_name; 456 return var_name;
443} 457}
444", 458",
@@ -459,7 +473,7 @@ fn main() {
459 " 473 "
460fn main() { 474fn main() {
461 let result = loop { 475 let result = loop {
462 let <|>var_name = 2 + 2; 476 let $0var_name = 2 + 2;
463 break var_name; 477 break var_name;
464 }; 478 };
465} 479}
@@ -478,7 +492,7 @@ fn main() {
478", 492",
479 " 493 "
480fn main() { 494fn main() {
481 let <|>var_name = 0f32 as u32; 495 let $0var_name = 0f32 as u32;
482 let v = var_name; 496 let v = var_name;
483} 497}
484", 498",
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index 527c7caef..59d278eb9 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -72,7 +72,7 @@ mod tests {
72 check_assist( 72 check_assist(
73 invert_if, 73 invert_if,
74 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", 74 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }",
75 "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", 75 "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
76 ) 76 )
77 } 77 }
78 78
@@ -81,7 +81,7 @@ mod tests {
81 check_assist( 81 check_assist(
82 invert_if, 82 invert_if,
83 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", 83 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }",
84 "fn f() { <|>if cond { 1 } else { 3 * 2 } }", 84 "fn f() { if cond { 1 } else { 3 * 2 } }",
85 ) 85 )
86 } 86 }
87 87
@@ -90,7 +90,7 @@ mod tests {
90 check_assist( 90 check_assist(
91 invert_if, 91 invert_if,
92 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }", 92 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
93 "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }", 93 "fn f() { if !cond { 1 } else { 3 * 2 } }",
94 ) 94 )
95 } 95 }
96 96
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index ac3e53c27..972d16241 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -58,8 +58,6 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
58 let target = tree.syntax().text_range(); 58 let target = tree.syntax().text_range();
59 acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { 59 acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| {
60 builder.rewrite(rewriter); 60 builder.rewrite(rewriter);
61 // FIXME: we only need because our diff is imprecise
62 builder.set_cursor(offset);
63 }) 61 })
64} 62}
65 63
@@ -142,7 +140,7 @@ use std::fmt<|>::Debug;
142use std::fmt::Display; 140use std::fmt::Display;
143", 141",
144 r" 142 r"
145use std::fmt<|>::{Debug, Display}; 143use std::fmt::{Debug, Display};
146", 144",
147 ) 145 )
148 } 146 }
@@ -156,7 +154,7 @@ use std::fmt::Debug;
156use std::fmt<|>::Display; 154use std::fmt<|>::Display;
157", 155",
158 r" 156 r"
159use std::fmt:<|>:{Display, Debug}; 157use std::fmt::{Display, Debug};
160", 158",
161 ); 159 );
162 } 160 }
@@ -169,7 +167,7 @@ use std::fmt:<|>:{Display, Debug};
169use std::{fmt<|>::Debug, fmt::Display}; 167use std::{fmt<|>::Debug, fmt::Display};
170", 168",
171 r" 169 r"
172use std::{fmt<|>::{Debug, Display}}; 170use std::{fmt::{Debug, Display}};
173", 171",
174 ); 172 );
175 check_assist( 173 check_assist(
@@ -178,7 +176,7 @@ use std::{fmt<|>::{Debug, Display}};
178use std::{fmt::Debug, fmt<|>::Display}; 176use std::{fmt::Debug, fmt<|>::Display};
179", 177",
180 r" 178 r"
181use std::{fmt::<|>{Display, Debug}}; 179use std::{fmt::{Display, Debug}};
182", 180",
183 ); 181 );
184 } 182 }
@@ -192,7 +190,7 @@ use std<|>::cell::*;
192use std::str; 190use std::str;
193", 191",
194 r" 192 r"
195use std<|>::{cell::*, str}; 193use std::{cell::*, str};
196", 194",
197 ) 195 )
198 } 196 }
@@ -206,7 +204,7 @@ use std<|>::cell::*;
206use std::str::*; 204use std::str::*;
207", 205",
208 r" 206 r"
209use std<|>::{cell::*, str::*}; 207use std::{cell::*, str::*};
210", 208",
211 ) 209 )
212 } 210 }
@@ -222,7 +220,7 @@ use foo::baz;
222/// Doc comment 220/// Doc comment
223", 221",
224 r" 222 r"
225use foo<|>::{bar, baz}; 223use foo::{bar, baz};
226 224
227/// Doc comment 225/// Doc comment
228", 226",
@@ -241,7 +239,7 @@ use {
241", 239",
242 r" 240 r"
243use { 241use {
244 foo<|>::{bar, baz}, 242 foo::{bar, baz},
245}; 243};
246", 244",
247 ); 245 );
@@ -255,7 +253,7 @@ use {
255", 253",
256 r" 254 r"
257use { 255use {
258 foo::{bar<|>, baz}, 256 foo::{bar, baz},
259}; 257};
260", 258",
261 ); 259 );
@@ -272,7 +270,7 @@ use foo::<|>{
272}; 270};
273", 271",
274 r" 272 r"
275use foo::{<|> 273use foo::{
276 FooBar, 274 FooBar,
277bar::baz}; 275bar::baz};
278", 276",
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index d4e38aa6a..ca04ec671 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -3,7 +3,7 @@ use std::iter::successors;
3use ra_syntax::{ 3use ra_syntax::{
4 algo::neighbor, 4 algo::neighbor,
5 ast::{self, AstNode}, 5 ast::{self, AstNode},
6 Direction, TextSize, 6 Direction,
7}; 7};
8 8
9use crate::{AssistContext, AssistId, Assists, TextRange}; 9use crate::{AssistContext, AssistId, Assists, TextRange};
@@ -41,17 +41,6 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
41 let current_expr = current_arm.expr()?; 41 let current_expr = current_arm.expr()?;
42 let current_text_range = current_arm.syntax().text_range(); 42 let current_text_range = current_arm.syntax().text_range();
43 43
44 enum CursorPos {
45 InExpr(TextSize),
46 InPat(TextSize),
47 }
48 let cursor_pos = ctx.offset();
49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
50 CursorPos::InExpr(current_text_range.end() - cursor_pos)
51 } else {
52 CursorPos::InPat(cursor_pos)
53 };
54
55 // We check if the following match arms match this one. We could, but don't, 44 // We check if the following match arms match this one. We could, but don't,
56 // compare to the previous match arm as well. 45 // compare to the previous match arm as well.
57 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next)) 46 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
@@ -87,10 +76,6 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
87 let start = arms_to_merge.first().unwrap().syntax().text_range().start(); 76 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
88 let end = arms_to_merge.last().unwrap().syntax().text_range().end(); 77 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
89 78
90 edit.set_cursor(match cursor_pos {
91 CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset,
92 CursorPos::InPat(offset) => offset,
93 });
94 edit.replace(TextRange::new(start, end), arm); 79 edit.replace(TextRange::new(start, end), arm);
95 }) 80 })
96} 81}
@@ -132,7 +117,7 @@ mod tests {
132 fn main() { 117 fn main() {
133 let x = X::A; 118 let x = X::A;
134 let y = match x { 119 let y = match x {
135 X::A | X::B => { 1i32<|> } 120 X::A | X::B => { 1i32 }
136 X::C => { 2i32 } 121 X::C => { 2i32 }
137 } 122 }
138 } 123 }
@@ -164,7 +149,7 @@ mod tests {
164 fn main() { 149 fn main() {
165 let x = X::A; 150 let x = X::A;
166 let y = match x { 151 let y = match x {
167 X::A | X::B | X::C | X::D => {<|> 1i32 }, 152 X::A | X::B | X::C | X::D => { 1i32 },
168 X::E => { 2i32 }, 153 X::E => { 2i32 },
169 } 154 }
170 } 155 }
@@ -197,7 +182,7 @@ mod tests {
197 let x = X::A; 182 let x = X::A;
198 let y = match x { 183 let y = match x {
199 X::A => { 1i32 }, 184 X::A => { 1i32 },
200 _ => { 2i<|>32 } 185 _ => { 2i32 }
201 } 186 }
202 } 187 }
203 "#, 188 "#,
@@ -226,7 +211,7 @@ mod tests {
226 211
227 fn main() { 212 fn main() {
228 match X::A { 213 match X::A {
229 X::A<|> | X::B | X::C => 92, 214 X::A | X::B | X::C => 92,
230 X::D => 62, 215 X::D => 62,
231 _ => panic!(), 216 _ => panic!(),
232 } 217 }
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index a41aacfc3..be2a7eddc 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -99,7 +99,7 @@ mod tests {
99 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {} 99 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
100 "#, 100 "#,
101 r#" 101 r#"
102 fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {} 102 fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
103 "#, 103 "#,
104 ); 104 );
105 } 105 }
@@ -112,7 +112,7 @@ mod tests {
112 impl<U: u32, <|>T> A<U, T> {} 112 impl<U: u32, <|>T> A<U, T> {}
113 "#, 113 "#,
114 r#" 114 r#"
115 impl<U, <|>T> A<U, T> where U: u32 {} 115 impl<U, T> A<U, T> where U: u32 {}
116 "#, 116 "#,
117 ); 117 );
118 } 118 }
@@ -125,7 +125,7 @@ mod tests {
125 struct A<<|>T: Iterator<Item = u32>> {} 125 struct A<<|>T: Iterator<Item = u32>> {}
126 "#, 126 "#,
127 r#" 127 r#"
128 struct A<<|>T> where T: Iterator<Item = u32> {} 128 struct A<T> where T: Iterator<Item = u32> {}
129 "#, 129 "#,
130 ); 130 );
131 } 131 }
@@ -138,7 +138,7 @@ mod tests {
138 struct Pair<<|>T: u32>(T, T); 138 struct Pair<<|>T: u32>(T, T);
139 "#, 139 "#,
140 r#" 140 r#"
141 struct Pair<<|>T>(T, T) where T: u32; 141 struct Pair<T>(T, T) where T: u32;
142 "#, 142 "#,
143 ); 143 );
144 } 144 }
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index fc0335b57..7edcf0748 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -1,7 +1,6 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast, 2 ast::{AstNode, IfExpr, MatchArm},
3 ast::{AstNode, AstToken, IfExpr, MatchArm}, 3 SyntaxKind::WHITESPACE,
4 TextSize,
5}; 4};
6 5
7use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, Assists};
@@ -42,24 +41,15 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
42 41
43 let target = guard.syntax().text_range(); 42 let target = guard.syntax().text_range();
44 acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { 43 acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { 44 match space_before_guard {
46 Some(tok) => { 45 Some(element) if element.kind() == WHITESPACE => {
47 if ast::Whitespace::cast(tok.clone()).is_some() { 46 edit.delete(element.text_range());
48 let ele = tok.text_range();
49 edit.delete(ele);
50 ele.len()
51 } else {
52 TextSize::from(0)
53 }
54 } 47 }
55 _ => TextSize::from(0), 48 _ => (),
56 }; 49 };
57 50
58 edit.delete(guard.syntax().text_range()); 51 edit.delete(guard.syntax().text_range());
59 edit.replace_node_and_indent(arm_expr.syntax(), buf); 52 edit.replace_node_and_indent(arm_expr.syntax(), buf);
60 edit.set_cursor(
61 arm_expr.syntax().text_range().start() + TextSize::from(3) - offseting_amount,
62 );
63 }) 53 })
64} 54}
65 55
@@ -124,7 +114,6 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex
124 } 114 }
125 115
126 edit.insert(match_pat.syntax().text_range().end(), buf); 116 edit.insert(match_pat.syntax().text_range().end(), buf);
127 edit.set_cursor(match_pat.syntax().text_range().end() + TextSize::from(1));
128 }, 117 },
129 ) 118 )
130} 119}
@@ -172,7 +161,7 @@ mod tests {
172 let t = 'a'; 161 let t = 'a';
173 let chars = "abcd"; 162 let chars = "abcd";
174 match t { 163 match t {
175 '\r' => if chars.clone().next() == Some('\n') { <|>false }, 164 '\r' => if chars.clone().next() == Some('\n') { false },
176 _ => true 165 _ => true
177 } 166 }
178 } 167 }
@@ -195,7 +184,7 @@ mod tests {
195 r#" 184 r#"
196 fn f() { 185 fn f() {
197 match x { 186 match x {
198 y @ 4 | y @ 5 => if y > 5 { <|>true }, 187 y @ 4 | y @ 5 => if y > 5 { true },
199 _ => false 188 _ => false
200 } 189 }
201 } 190 }
@@ -222,7 +211,7 @@ mod tests {
222 let t = 'a'; 211 let t = 'a';
223 let chars = "abcd"; 212 let chars = "abcd";
224 match t { 213 match t {
225 '\r' <|>if chars.clone().next() == Some('\n') => false, 214 '\r' if chars.clone().next() == Some('\n') => false,
226 _ => true 215 _ => true
227 } 216 }
228 } 217 }
@@ -266,7 +255,7 @@ mod tests {
266 let t = 'a'; 255 let t = 'a';
267 let chars = "abcd"; 256 let chars = "abcd";
268 match t { 257 match t {
269 '\r' <|>if chars.clone().next().is_some() => { }, 258 '\r' if chars.clone().next().is_some() => { },
270 _ => true 259 _ => true
271 } 260 }
272 } 261 }
@@ -296,7 +285,7 @@ mod tests {
296 let mut t = 'a'; 285 let mut t = 'a';
297 let chars = "abcd"; 286 let chars = "abcd";
298 match t { 287 match t {
299 '\r' <|>if chars.clone().next().is_some() => { 288 '\r' if chars.clone().next().is_some() => {
300 t = 'e'; 289 t = 'e';
301 false 290 false
302 }, 291 },
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index c20ffe0b3..16002d2ac 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -164,7 +164,7 @@ mod test {
164 "#, 164 "#,
165 r##" 165 r##"
166 fn f() { 166 fn f() {
167 let s = <|>r#"random 167 let s = r#"random
168string"#; 168string"#;
169 } 169 }
170 "##, 170 "##,
@@ -182,7 +182,7 @@ string"#;
182 "#, 182 "#,
183 r##" 183 r##"
184 fn f() { 184 fn f() {
185 format!(<|>r#"x = {}"#, 92) 185 format!(r#"x = {}"#, 92)
186 } 186 }
187 "##, 187 "##,
188 ) 188 )
@@ -199,7 +199,7 @@ string"#;
199 "###, 199 "###,
200 r####" 200 r####"
201 fn f() { 201 fn f() {
202 let s = <|>r#"#random## 202 let s = r#"#random##
203string"#; 203string"#;
204 } 204 }
205 "####, 205 "####,
@@ -217,7 +217,7 @@ string"#;
217 "###, 217 "###,
218 r####" 218 r####"
219 fn f() { 219 fn f() {
220 let s = <|>r###"#random"## 220 let s = r###"#random"##
221string"###; 221string"###;
222 } 222 }
223 "####, 223 "####,
@@ -235,7 +235,7 @@ string"###;
235 "#, 235 "#,
236 r##" 236 r##"
237 fn f() { 237 fn f() {
238 let s = <|>r#"random string"#; 238 let s = r#"random string"#;
239 } 239 }
240 "##, 240 "##,
241 ) 241 )
@@ -289,7 +289,7 @@ string"###;
289 "#, 289 "#,
290 r##" 290 r##"
291 fn f() { 291 fn f() {
292 let s = <|>r#"random string"#; 292 let s = r#"random string"#;
293 } 293 }
294 "##, 294 "##,
295 ) 295 )
@@ -306,7 +306,7 @@ string"###;
306 "##, 306 "##,
307 r###" 307 r###"
308 fn f() { 308 fn f() {
309 let s = <|>r##"random"string"##; 309 let s = r##"random"string"##;
310 } 310 }
311 "###, 311 "###,
312 ) 312 )
@@ -348,7 +348,7 @@ string"###;
348 "##, 348 "##,
349 r#" 349 r#"
350 fn f() { 350 fn f() {
351 let s = <|>r"random string"; 351 let s = r"random string";
352 } 352 }
353 "#, 353 "#,
354 ) 354 )
@@ -365,7 +365,7 @@ string"###;
365 "##, 365 "##,
366 r#" 366 r#"
367 fn f() { 367 fn f() {
368 let s = <|>r"random\"str\"ing"; 368 let s = r"random\"str\"ing";
369 } 369 }
370 "#, 370 "#,
371 ) 371 )
@@ -382,7 +382,7 @@ string"###;
382 "###, 382 "###,
383 r##" 383 r##"
384 fn f() { 384 fn f() {
385 let s = <|>r#"random string"#; 385 let s = r#"random string"#;
386 } 386 }
387 "##, 387 "##,
388 ) 388 )
@@ -436,7 +436,7 @@ string"###;
436 "##, 436 "##,
437 r#" 437 r#"
438 fn f() { 438 fn f() {
439 let s = <|>"random string"; 439 let s = "random string";
440 } 440 }
441 "#, 441 "#,
442 ) 442 )
@@ -453,7 +453,7 @@ string"###;
453 "##, 453 "##,
454 r#" 454 r#"
455 fn f() { 455 fn f() {
456 let s = <|>"random\"str\"ing"; 456 let s = "random\"str\"ing";
457 } 457 }
458 "#, 458 "#,
459 ) 459 )
@@ -470,7 +470,7 @@ string"###;
470 "###, 470 "###,
471 r##" 471 r##"
472 fn f() { 472 fn f() {
473 let s = <|>"random string"; 473 let s = "random string";
474 } 474 }
475 "##, 475 "##,
476 ) 476 )
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index 8eef578cf..961ee1731 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -29,26 +29,6 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 29
30 let macro_range = macro_call.syntax().text_range(); 30 let macro_range = macro_call.syntax().text_range();
31 31
32 // If the cursor is inside the macro call, we'll try to maintain the cursor
33 // position by subtracting the length of dbg!( from the start of the file
34 // range, otherwise we'll default to using the start of the macro call
35 let cursor_pos = {
36 let file_range = ctx.frange.range;
37
38 let offset_start = file_range
39 .start()
40 .checked_sub(macro_range.start())
41 .unwrap_or_else(|| TextSize::from(0));
42
43 let dbg_size = TextSize::of("dbg!(");
44
45 if offset_start > dbg_size {
46 file_range.start() - dbg_size
47 } else {
48 macro_range.start()
49 }
50 };
51
52 let macro_content = { 32 let macro_content = {
53 let macro_args = macro_call.token_tree()?.syntax().clone(); 33 let macro_args = macro_call.token_tree()?.syntax().clone();
54 34
@@ -58,9 +38,8 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
58 }; 38 };
59 39
60 let target = macro_call.syntax().text_range(); 40 let target = macro_call.syntax().text_range();
61 acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| { 41 acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| {
62 edit.replace(macro_range, macro_content); 42 builder.replace(macro_range, macro_content);
63 edit.set_cursor(cursor_pos);
64 }) 43 })
65} 44}
66 45
@@ -94,13 +73,13 @@ mod tests {
94 73
95 #[test] 74 #[test]
96 fn test_remove_dbg() { 75 fn test_remove_dbg() {
97 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); 76 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1");
98 77
99 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); 78 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)");
100 79
101 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); 80 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1");
102 81
103 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); 82 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1");
104 83
105 check_assist( 84 check_assist(
106 remove_dbg, 85 remove_dbg,
@@ -113,7 +92,7 @@ fn foo(n: usize) {
113", 92",
114 " 93 "
115fn foo(n: usize) { 94fn foo(n: usize) {
116 if let Some(_) = n.<|>checked_sub(4) { 95 if let Some(_) = n.checked_sub(4) {
117 // ... 96 // ...
118 } 97 }
119} 98}
@@ -122,8 +101,8 @@ fn foo(n: usize) {
122 } 101 }
123 #[test] 102 #[test]
124 fn test_remove_dbg_with_brackets_and_braces() { 103 fn test_remove_dbg_with_brackets_and_braces() {
125 check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); 104 check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
126 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); 105 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1");
127 } 106 }
128 107
129 #[test] 108 #[test]
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index dce546db7..fe4eada03 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -26,8 +26,7 @@ pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 }; 26 };
27 27
28 let target = mut_token.text_range(); 28 let target = mut_token.text_range();
29 acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| { 29 acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| {
30 edit.set_cursor(delete_from); 30 builder.delete(TextRange::new(delete_from, delete_to));
31 edit.delete(TextRange::new(delete_from, delete_to));
32 }) 31 })
33} 32}
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index 757f6406e..30229edc2 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -140,7 +140,7 @@ mod tests {
140 "#, 140 "#,
141 r#" 141 r#"
142 struct Foo {foo: i32, bar: i32}; 142 struct Foo {foo: i32, bar: i32};
143 const test: Foo = <|>Foo {foo: 1, bar: 0} 143 const test: Foo = Foo {foo: 1, bar: 0}
144 "#, 144 "#,
145 ) 145 )
146 } 146 }
@@ -164,7 +164,7 @@ mod tests {
164 164
165 fn f(f: Foo) -> { 165 fn f(f: Foo) -> {
166 match f { 166 match f {
167 <|>Foo { ref mut bar, baz: 0, .. } => (), 167 Foo { ref mut bar, baz: 0, .. } => (),
168 _ => () 168 _ => ()
169 } 169 }
170 } 170 }
@@ -202,7 +202,7 @@ mod tests {
202 impl Foo { 202 impl Foo {
203 fn new() -> Foo { 203 fn new() -> Foo {
204 let foo = String::new(); 204 let foo = String::new();
205 <|>Foo { 205 Foo {
206 foo, 206 foo,
207 bar: foo.clone(), 207 bar: foo.clone(),
208 extra: "Extra field", 208 extra: "Extra field",
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index 65f5fc6ab..e016f51c3 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -68,7 +68,6 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
68 .indent(IndentLevel::from_node(if_expr.syntax())) 68 .indent(IndentLevel::from_node(if_expr.syntax()))
69 }; 69 };
70 70
71 edit.set_cursor(if_expr.syntax().text_range().start());
72 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 71 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
73 }) 72 })
74} 73}
@@ -83,7 +82,7 @@ mod tests {
83 fn test_replace_if_let_with_match_unwraps_simple_expressions() { 82 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
84 check_assist( 83 check_assist(
85 replace_if_let_with_match, 84 replace_if_let_with_match,
86 " 85 r#"
87impl VariantData { 86impl VariantData {
88 pub fn is_struct(&self) -> bool { 87 pub fn is_struct(&self) -> bool {
89 if <|>let VariantData::Struct(..) = *self { 88 if <|>let VariantData::Struct(..) = *self {
@@ -92,16 +91,16 @@ impl VariantData {
92 false 91 false
93 } 92 }
94 } 93 }
95} ", 94} "#,
96 " 95 r#"
97impl VariantData { 96impl VariantData {
98 pub fn is_struct(&self) -> bool { 97 pub fn is_struct(&self) -> bool {
99 <|>match *self { 98 match *self {
100 VariantData::Struct(..) => true, 99 VariantData::Struct(..) => true,
101 _ => false, 100 _ => false,
102 } 101 }
103 } 102 }
104} ", 103} "#,
105 ) 104 )
106 } 105 }
107 106
@@ -109,7 +108,7 @@ impl VariantData {
109 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() { 108 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
110 check_assist( 109 check_assist(
111 replace_if_let_with_match, 110 replace_if_let_with_match,
112 " 111 r#"
113fn foo() { 112fn foo() {
114 if <|>let VariantData::Struct(..) = a { 113 if <|>let VariantData::Struct(..) = a {
115 bar( 114 bar(
@@ -118,10 +117,10 @@ fn foo() {
118 } else { 117 } else {
119 false 118 false
120 } 119 }
121} ", 120} "#,
122 " 121 r#"
123fn foo() { 122fn foo() {
124 <|>match a { 123 match a {
125 VariantData::Struct(..) => { 124 VariantData::Struct(..) => {
126 bar( 125 bar(
127 123 126 123
@@ -129,7 +128,7 @@ fn foo() {
129 } 128 }
130 _ => false, 129 _ => false,
131 } 130 }
132} ", 131} "#,
133 ) 132 )
134 } 133 }
135 134
@@ -137,7 +136,7 @@ fn foo() {
137 fn replace_if_let_with_match_target() { 136 fn replace_if_let_with_match_target() {
138 check_assist_target( 137 check_assist_target(
139 replace_if_let_with_match, 138 replace_if_let_with_match,
140 " 139 r#"
141impl VariantData { 140impl VariantData {
142 pub fn is_struct(&self) -> bool { 141 pub fn is_struct(&self) -> bool {
143 if <|>let VariantData::Struct(..) = *self { 142 if <|>let VariantData::Struct(..) = *self {
@@ -146,7 +145,7 @@ impl VariantData {
146 false 145 false
147 } 146 }
148 } 147 }
149} ", 148} "#,
150 "if let VariantData::Struct(..) = *self { 149 "if let VariantData::Struct(..) = *self {
151 true 150 true
152 } else { 151 } else {
@@ -176,7 +175,7 @@ enum Option<T> { Some(T), None }
176use Option::*; 175use Option::*;
177 176
178fn foo(x: Option<i32>) { 177fn foo(x: Option<i32>) {
179 <|>match x { 178 match x {
180 Some(x) => println!("{}", x), 179 Some(x) => println!("{}", x),
181 None => println!("none"), 180 None => println!("none"),
182 } 181 }
@@ -206,7 +205,7 @@ enum Result<T, E> { Ok(T), Err(E) }
206use Result::*; 205use Result::*;
207 206
208fn foo(x: Result<i32, ()>) { 207fn foo(x: Result<i32, ()>) {
209 <|>match x { 208 match x {
210 Ok(x) => println!("{}", x), 209 Ok(x) => println!("{}", x),
211 Err(_) => println!("none"), 210 Err(_) => println!("none"),
212 } 211 }
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index 482957dc6..761557ac0 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -58,12 +58,9 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
58 let stmt = make::expr_stmt(if_); 58 let stmt = make::expr_stmt(if_);
59 59
60 let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); 60 let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
61 let target_offset =
62 let_stmt.syntax().text_range().start() + placeholder.syntax().text_range().start();
63 let stmt = stmt.replace_descendant(placeholder.into(), original_pat); 61 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
64 62
65 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 63 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
66 edit.set_cursor(target_offset);
67 }) 64 })
68} 65}
69 66
@@ -88,7 +85,7 @@ fn main() {
88enum E<T> { X(T), Y(T) } 85enum E<T> { X(T), Y(T) }
89 86
90fn main() { 87fn main() {
91 if let <|>x = E::X(92) { 88 if let x = E::X(92) {
92 } 89 }
93} 90}
94 ", 91 ",
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index 1a81d8a0e..0197a8cf0 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -39,7 +39,7 @@ pub(crate) fn replace_qualified_name_with_use(
39 target, 39 target,
40 |builder| { 40 |builder| {
41 let path_to_import = hir_path.mod_path().clone(); 41 let path_to_import = hir_path.mod_path().clone();
42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder); 42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());
43 43
44 if let Some(last) = path.segment() { 44 if let Some(last) = path.segment() {
45 // Here we are assuming the assist will provide a correct use statement 45 // Here we are assuming the assist will provide a correct use statement
@@ -89,7 +89,7 @@ std::fmt::Debug<|>
89 " 89 "
90use std::fmt::Debug; 90use std::fmt::Debug;
91 91
92Debug<|> 92Debug
93 ", 93 ",
94 ); 94 );
95 } 95 }
@@ -106,7 +106,7 @@ fn main() {
106 " 106 "
107use std::fmt::Debug; 107use std::fmt::Debug;
108 108
109Debug<|> 109Debug
110 110
111fn main() { 111fn main() {
112} 112}
@@ -130,7 +130,7 @@ use std::fmt::Debug;
130fn main() { 130fn main() {
131} 131}
132 132
133Debug<|> 133Debug
134 ", 134 ",
135 ); 135 );
136 } 136 }
@@ -145,7 +145,7 @@ std::fmt<|>::Debug
145 " 145 "
146use std::fmt; 146use std::fmt;
147 147
148fmt<|>::Debug 148fmt::Debug
149 ", 149 ",
150 ); 150 );
151 } 151 }
@@ -164,7 +164,7 @@ impl std::fmt::Debug<|> for Foo {
164use stdx; 164use stdx;
165use std::fmt::Debug; 165use std::fmt::Debug;
166 166
167impl Debug<|> for Foo { 167impl Debug for Foo {
168} 168}
169 ", 169 ",
170 ); 170 );
@@ -181,7 +181,7 @@ impl std::fmt::Debug<|> for Foo {
181 " 181 "
182use std::fmt::Debug; 182use std::fmt::Debug;
183 183
184impl Debug<|> for Foo { 184impl Debug for Foo {
185} 185}
186 ", 186 ",
187 ); 187 );
@@ -198,7 +198,7 @@ impl Debug<|> for Foo {
198 " 198 "
199 use std::fmt::Debug; 199 use std::fmt::Debug;
200 200
201 impl Debug<|> for Foo { 201 impl Debug for Foo {
202 } 202 }
203 ", 203 ",
204 ); 204 );
@@ -217,7 +217,7 @@ impl std::io<|> for Foo {
217 " 217 "
218use std::{io, fmt}; 218use std::{io, fmt};
219 219
220impl io<|> for Foo { 220impl io for Foo {
221} 221}
222 ", 222 ",
223 ); 223 );
@@ -236,7 +236,7 @@ impl std::fmt::Debug<|> for Foo {
236 " 236 "
237use std::fmt::{self, Debug, }; 237use std::fmt::{self, Debug, };
238 238
239impl Debug<|> for Foo { 239impl Debug for Foo {
240} 240}
241 ", 241 ",
242 ); 242 );
@@ -255,7 +255,7 @@ impl std::fmt<|> for Foo {
255 " 255 "
256use std::fmt::{self, Debug}; 256use std::fmt::{self, Debug};
257 257
258impl fmt<|> for Foo { 258impl fmt for Foo {
259} 259}
260 ", 260 ",
261 ); 261 );
@@ -274,7 +274,7 @@ impl std::fmt::nested<|> for Foo {
274 " 274 "
275use std::fmt::{Debug, nested::{Display, self}}; 275use std::fmt::{Debug, nested::{Display, self}};
276 276
277impl nested<|> for Foo { 277impl nested for Foo {
278} 278}
279", 279",
280 ); 280 );
@@ -293,7 +293,7 @@ impl std::fmt::nested<|> for Foo {
293 " 293 "
294use std::fmt::{Debug, nested::{self, Display}}; 294use std::fmt::{Debug, nested::{self, Display}};
295 295
296impl nested<|> for Foo { 296impl nested for Foo {
297} 297}
298", 298",
299 ); 299 );
@@ -312,7 +312,7 @@ impl std::fmt::nested::Debug<|> for Foo {
312 " 312 "
313use std::fmt::{Debug, nested::{Display, Debug}}; 313use std::fmt::{Debug, nested::{Display, Debug}};
314 314
315impl Debug<|> for Foo { 315impl Debug for Foo {
316} 316}
317", 317",
318 ); 318 );
@@ -331,7 +331,7 @@ impl std::fmt::nested::Display<|> for Foo {
331 " 331 "
332use std::fmt::{nested::Display, Debug}; 332use std::fmt::{nested::Display, Debug};
333 333
334impl Display<|> for Foo { 334impl Display for Foo {
335} 335}
336", 336",
337 ); 337 );
@@ -350,7 +350,7 @@ impl std::fmt::Display<|> for Foo {
350 " 350 "
351use std::fmt::{Display, nested::Debug}; 351use std::fmt::{Display, nested::Debug};
352 352
353impl Display<|> for Foo { 353impl Display for Foo {
354} 354}
355", 355",
356 ); 356 );
@@ -374,7 +374,7 @@ use crate::{
374 AssocItem, 374 AssocItem,
375}; 375};
376 376
377fn foo() { lower<|>::trait_env() } 377fn foo() { lower::trait_env() }
378", 378",
379 ); 379 );
380 } 380 }
@@ -392,7 +392,7 @@ impl foo::Debug<|> for Foo {
392 " 392 "
393use std::fmt as foo; 393use std::fmt as foo;
394 394
395impl Debug<|> for Foo { 395impl Debug for Foo {
396} 396}
397", 397",
398 ); 398 );
@@ -435,7 +435,7 @@ mod foo {
435 mod bar { 435 mod bar {
436 use std::fmt::Debug; 436 use std::fmt::Debug;
437 437
438 Debug<|> 438 Debug
439 } 439 }
440} 440}
441 ", 441 ",
@@ -458,7 +458,7 @@ fn main() {
458use std::fmt::Debug; 458use std::fmt::Debug;
459 459
460fn main() { 460fn main() {
461 Debug<|> 461 Debug
462} 462}
463 ", 463 ",
464 ); 464 );
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index c4b56f6e9..cff7dfb81 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -9,7 +9,10 @@ use ra_syntax::{
9 AstNode, 9 AstNode,
10}; 10};
11 11
12use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; 12use crate::{
13 utils::{render_snippet, Cursor, TryEnum},
14 AssistContext, AssistId, Assists,
15};
13 16
14// Assist: replace_unwrap_with_match 17// Assist: replace_unwrap_with_match
15// 18//
@@ -29,7 +32,7 @@ use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
29// let x: Result<i32, i32> = Result::Ok(92); 32// let x: Result<i32, i32> = Result::Ok(92);
30// let y = match x { 33// let y = match x {
31// Ok(a) => a, 34// Ok(a) => a,
32// _ => unreachable!(), 35// $0_ => unreachable!(),
33// }; 36// };
34// } 37// }
35// ``` 38// ```
@@ -43,7 +46,7 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
43 let ty = ctx.sema.type_of_expr(&caller)?; 46 let ty = ctx.sema.type_of_expr(&caller)?;
44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); 47 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
45 let target = method_call.syntax().text_range(); 48 let target = method_call.syntax().text_range();
46 acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |edit| { 49 acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| {
47 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 50 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
48 let it = make::bind_pat(make::name("a")).into(); 51 let it = make::bind_pat(make::name("a")).into();
49 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); 52 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
@@ -51,23 +54,37 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 54 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 55 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
53 56
54 let unreachable_call = make::unreachable_macro_call().into(); 57 let unreachable_call = make::expr_unreachable();
55 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 58 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
56 59
57 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 60 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
58 let match_expr = make::expr_match(caller.clone(), match_arm_list) 61 let match_expr = make::expr_match(caller.clone(), match_arm_list)
59 .indent(IndentLevel::from_node(method_call.syntax())); 62 .indent(IndentLevel::from_node(method_call.syntax()));
60 63
61 edit.set_cursor(caller.syntax().text_range().start()); 64 let range = method_call.syntax().text_range();
62 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); 65 match ctx.config.snippet_cap {
66 Some(cap) => {
67 let err_arm = match_expr
68 .syntax()
69 .descendants()
70 .filter_map(ast::MatchArm::cast)
71 .last()
72 .unwrap();
73 let snippet =
74 render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
75 builder.replace_snippet(cap, range, snippet)
76 }
77 None => builder.replace(range, match_expr.to_string()),
78 }
63 }) 79 })
64} 80}
65 81
66#[cfg(test)] 82#[cfg(test)]
67mod tests { 83mod tests {
68 use super::*;
69 use crate::tests::{check_assist, check_assist_target}; 84 use crate::tests::{check_assist, check_assist_target};
70 85
86 use super::*;
87
71 #[test] 88 #[test]
72 fn test_replace_result_unwrap_with_match() { 89 fn test_replace_result_unwrap_with_match() {
73 check_assist( 90 check_assist(
@@ -85,9 +102,9 @@ enum Result<T, E> { Ok(T), Err(E) }
85fn i<T>(a: T) -> T { a } 102fn i<T>(a: T) -> T { a }
86fn main() { 103fn main() {
87 let x: Result<i32, i32> = Result::Ok(92); 104 let x: Result<i32, i32> = Result::Ok(92);
88 let y = <|>match i(x) { 105 let y = match i(x) {
89 Ok(a) => a, 106 Ok(a) => a,
90 _ => unreachable!(), 107 $0_ => unreachable!(),
91 }; 108 };
92} 109}
93 ", 110 ",
@@ -111,9 +128,9 @@ enum Option<T> { Some(T), None }
111fn i<T>(a: T) -> T { a } 128fn i<T>(a: T) -> T { a }
112fn main() { 129fn main() {
113 let x = Option::Some(92); 130 let x = Option::Some(92);
114 let y = <|>match i(x) { 131 let y = match i(x) {
115 Some(a) => a, 132 Some(a) => a,
116 _ => unreachable!(), 133 $0_ => unreachable!(),
117 }; 134 };
118} 135}
119 ", 136 ",
@@ -137,9 +154,9 @@ enum Result<T, E> { Ok(T), Err(E) }
137fn i<T>(a: T) -> T { a } 154fn i<T>(a: T) -> T { a }
138fn main() { 155fn main() {
139 let x: Result<i32, i32> = Result::Ok(92); 156 let x: Result<i32, i32> = Result::Ok(92);
140 let y = <|>match i(x) { 157 let y = match i(x) {
141 Ok(a) => a, 158 Ok(a) => a,
142 _ => unreachable!(), 159 $0_ => unreachable!(),
143 }.count_zeroes(); 160 }.count_zeroes();
144} 161}
145 ", 162 ",
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index b2757e50c..c7a874480 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -26,12 +26,10 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
26 if new_tree == use_tree { 26 if new_tree == use_tree {
27 return None; 27 return None;
28 } 28 }
29 let cursor = ctx.offset();
30 29
31 let target = colon_colon.text_range(); 30 let target = colon_colon.text_range();
32 acc.add(AssistId("split_import"), "Split import", target, |edit| { 31 acc.add(AssistId("split_import"), "Split import", target, |edit| {
33 edit.replace_ast(use_tree, new_tree); 32 edit.replace_ast(use_tree, new_tree);
34 edit.set_cursor(cursor);
35 }) 33 })
36} 34}
37 35
@@ -46,7 +44,7 @@ mod tests {
46 check_assist( 44 check_assist(
47 split_import, 45 split_import,
48 "use crate::<|>db::RootDatabase;", 46 "use crate::<|>db::RootDatabase;",
49 "use crate::<|>{db::RootDatabase};", 47 "use crate::{db::RootDatabase};",
50 ) 48 )
51 } 49 }
52 50
@@ -55,7 +53,7 @@ mod tests {
55 check_assist( 53 check_assist(
56 split_import, 54 split_import,
57 "use crate:<|>:db::{RootDatabase, FileSymbol}", 55 "use crate:<|>:db::{RootDatabase, FileSymbol}",
58 "use crate:<|>:{db::{RootDatabase, FileSymbol}}", 56 "use crate::{db::{RootDatabase, FileSymbol}}",
59 ) 57 )
60 } 58 }
61 59
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index e52ec557e..8440c7d0f 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -1,8 +1,10 @@
1use crate::{AssistContext, AssistId, Assists};
2
3use ast::{ElseBranch, Expr, LoopBodyOwner};
4use ra_fmt::unwrap_trivial_block; 1use ra_fmt::unwrap_trivial_block;
5use ra_syntax::{ast, match_ast, AstNode, TextRange, T}; 2use ra_syntax::{
3 ast::{self, ElseBranch, Expr, LoopBodyOwner},
4 match_ast, AstNode, TextRange, T,
5};
6
7use crate::{AssistContext, AssistId, Assists};
6 8
7// Assist: unwrap_block 9// Assist: unwrap_block
8// 10//
@@ -60,7 +62,6 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
60 let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); 62 let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
61 let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end()); 63 let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end());
62 64
63 edit.set_cursor(ancestor_then_branch.syntax().text_range().end());
64 edit.delete(range_to_del_rest); 65 edit.delete(range_to_del_rest);
65 edit.delete(range_to_del_else_if); 66 edit.delete(range_to_del_else_if);
66 edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{'])); 67 edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{']));
@@ -77,7 +78,6 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
77 return acc.add(assist_id, assist_label, target, |edit| { 78 return acc.add(assist_id, assist_label, target, |edit| {
78 let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); 79 let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
79 80
80 edit.set_cursor(then_branch.syntax().text_range().end());
81 edit.delete(range_to_del); 81 edit.delete(range_to_del);
82 edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{'])); 82 edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{']));
83 }); 83 });
@@ -95,8 +95,6 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
95 95
96 let target = expr_to_unwrap.syntax().text_range(); 96 let target = expr_to_unwrap.syntax().text_range();
97 acc.add(assist_id, assist_label, target, |edit| { 97 acc.add(assist_id, assist_label, target, |edit| {
98 edit.set_cursor(expr.syntax().text_range().start());
99
100 edit.replace( 98 edit.replace(
101 expr.syntax().text_range(), 99 expr.syntax().text_range(),
102 update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']), 100 update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']),
@@ -152,7 +150,7 @@ mod tests {
152 r#" 150 r#"
153 fn main() { 151 fn main() {
154 bar(); 152 bar();
155 <|>foo(); 153 foo();
156 154
157 //comment 155 //comment
158 bar(); 156 bar();
@@ -186,7 +184,7 @@ mod tests {
186 184
187 //comment 185 //comment
188 bar(); 186 bar();
189 }<|> 187 }
190 println!("bar"); 188 println!("bar");
191 } 189 }
192 "#, 190 "#,
@@ -220,7 +218,7 @@ mod tests {
220 218
221 //comment 219 //comment
222 //bar(); 220 //bar();
223 }<|> 221 }
224 println!("bar"); 222 println!("bar");
225 } 223 }
226 "#, 224 "#,
@@ -256,7 +254,7 @@ mod tests {
256 //bar(); 254 //bar();
257 } else if false { 255 } else if false {
258 println!("bar"); 256 println!("bar");
259 }<|> 257 }
260 println!("foo"); 258 println!("foo");
261 } 259 }
262 "#, 260 "#,
@@ -296,7 +294,7 @@ mod tests {
296 println!("bar"); 294 println!("bar");
297 } else if true { 295 } else if true {
298 println!("foo"); 296 println!("foo");
299 }<|> 297 }
300 println!("else"); 298 println!("else");
301 } 299 }
302 "#, 300 "#,
@@ -334,7 +332,7 @@ mod tests {
334 //bar(); 332 //bar();
335 } else if false { 333 } else if false {
336 println!("bar"); 334 println!("bar");
337 }<|> 335 }
338 println!("foo"); 336 println!("foo");
339 } 337 }
340 "#, 338 "#,
@@ -381,7 +379,7 @@ mod tests {
381 "#, 379 "#,
382 r#" 380 r#"
383 fn main() { 381 fn main() {
384 <|>if true { 382 if true {
385 foo(); 383 foo();
386 384
387 //comment 385 //comment
@@ -415,7 +413,7 @@ mod tests {
415 r#" 413 r#"
416 fn main() { 414 fn main() {
417 for i in 0..5 { 415 for i in 0..5 {
418 <|>foo(); 416 foo();
419 417
420 //comment 418 //comment
421 bar(); 419 bar();
@@ -445,7 +443,7 @@ mod tests {
445 "#, 443 "#,
446 r#" 444 r#"
447 fn main() { 445 fn main() {
448 <|>if true { 446 if true {
449 foo(); 447 foo();
450 448
451 //comment 449 //comment
@@ -478,7 +476,7 @@ mod tests {
478 "#, 476 "#,
479 r#" 477 r#"
480 fn main() { 478 fn main() {
481 <|>if true { 479 if true {
482 foo(); 480 foo();
483 481
484 //comment 482 //comment
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index b6dc7cb1b..464bc03dd 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -10,8 +10,8 @@ macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; 10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11} 11}
12 12
13mod assist_config;
13mod assist_context; 14mod assist_context;
14mod marks;
15#[cfg(test)] 15#[cfg(test)]
16mod tests; 16mod tests;
17pub mod utils; 17pub mod utils;
@@ -24,6 +24,8 @@ use ra_syntax::TextRange;
24 24
25pub(crate) use crate::assist_context::{AssistContext, Assists}; 25pub(crate) use crate::assist_context::{AssistContext, Assists};
26 26
27pub use assist_config::AssistConfig;
28
27/// Unique identifier of the assist, should not be shown to the user 29/// Unique identifier of the assist, should not be shown to the user
28/// directly. 30/// directly.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)] 31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -54,9 +56,9 @@ impl Assist {
54 /// 56 ///
55 /// Assists are returned in the "unresolved" state, that is only labels are 57 /// Assists are returned in the "unresolved" state, that is only labels are
56 /// returned, without actual edits. 58 /// returned, without actual edits.
57 pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> { 59 pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> {
58 let sema = Semantics::new(db); 60 let sema = Semantics::new(db);
59 let ctx = AssistContext::new(sema, range); 61 let ctx = AssistContext::new(sema, config, range);
60 let mut acc = Assists::new_unresolved(&ctx); 62 let mut acc = Assists::new_unresolved(&ctx);
61 handlers::all().iter().for_each(|handler| { 63 handlers::all().iter().for_each(|handler| {
62 handler(&mut acc, &ctx); 64 handler(&mut acc, &ctx);
@@ -68,9 +70,13 @@ impl Assist {
68 /// 70 ///
69 /// Assists are returned in the "resolved" state, that is with edit fully 71 /// Assists are returned in the "resolved" state, that is with edit fully
70 /// computed. 72 /// computed.
71 pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { 73 pub fn resolved(
74 db: &RootDatabase,
75 config: &AssistConfig,
76 range: FileRange,
77 ) -> Vec<ResolvedAssist> {
72 let sema = Semantics::new(db); 78 let sema = Semantics::new(db);
73 let ctx = AssistContext::new(sema, range); 79 let ctx = AssistContext::new(sema, config, range);
74 let mut acc = Assists::new_resolved(&ctx); 80 let mut acc = Assists::new_resolved(&ctx);
75 handlers::all().iter().for_each(|handler| { 81 handlers::all().iter().for_each(|handler| {
76 handler(&mut acc, &ctx); 82 handler(&mut acc, &ctx);
@@ -103,12 +109,14 @@ mod handlers {
103 mod add_impl; 109 mod add_impl;
104 mod add_missing_impl_members; 110 mod add_missing_impl_members;
105 mod add_new; 111 mod add_new;
112 mod add_turbo_fish;
106 mod apply_demorgan; 113 mod apply_demorgan;
107 mod auto_import; 114 mod auto_import;
108 mod change_return_type_to_result; 115 mod change_return_type_to_result;
109 mod change_visibility; 116 mod change_visibility;
110 mod early_return; 117 mod early_return;
111 mod fill_match_arms; 118 mod fill_match_arms;
119 mod fix_visibility;
112 mod flip_binexpr; 120 mod flip_binexpr;
113 mod flip_comma; 121 mod flip_comma;
114 mod flip_trait_bound; 122 mod flip_trait_bound;
@@ -140,12 +148,14 @@ mod handlers {
140 add_function::add_function, 148 add_function::add_function,
141 add_impl::add_impl, 149 add_impl::add_impl,
142 add_new::add_new, 150 add_new::add_new,
151 add_turbo_fish::add_turbo_fish,
143 apply_demorgan::apply_demorgan, 152 apply_demorgan::apply_demorgan,
144 auto_import::auto_import, 153 auto_import::auto_import,
145 change_return_type_to_result::change_return_type_to_result, 154 change_return_type_to_result::change_return_type_to_result,
146 change_visibility::change_visibility, 155 change_visibility::change_visibility,
147 early_return::convert_to_guarded_return, 156 early_return::convert_to_guarded_return,
148 fill_match_arms::fill_match_arms, 157 fill_match_arms::fill_match_arms,
158 fix_visibility::fix_visibility,
149 flip_binexpr::flip_binexpr, 159 flip_binexpr::flip_binexpr,
150 flip_comma::flip_comma, 160 flip_comma::flip_comma,
151 flip_trait_bound::flip_trait_bound, 161 flip_trait_bound::flip_trait_bound,
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
deleted file mode 100644
index 8d910205f..000000000
--- a/crates/ra_assists/src/marks.rs
+++ /dev/null
@@ -1,12 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks![
4 introduce_var_in_comment_is_not_applicable
5 test_introduce_var_expr_stmt
6 test_introduce_var_last_expr
7 not_applicable_outside_of_bind_pat
8 test_not_inline_mut_variable
9 test_not_applicable_if_variable_unused
10 change_visibility_field_false_positive
11 test_add_from_impl_already_exists
12];
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
index a3eacb8f1..62dd3547f 100644
--- a/crates/ra_assists/src/tests.rs
+++ b/crates/ra_assists/src/tests.rs
@@ -7,11 +7,10 @@ use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
7use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 7use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
8use ra_syntax::TextRange; 8use ra_syntax::TextRange;
9use test_utils::{ 9use test_utils::{
10 add_cursor, assert_eq_text, extract_offset, extract_range, extract_range_or_offset, 10 assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset,
11 RangeOrOffset,
12}; 11};
13 12
14use crate::{handlers::Handler, Assist, AssistContext, Assists}; 13use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists};
15 14
16pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 15pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
17 let (mut db, file_id) = RootDatabase::with_single_file(text); 16 let (mut db, file_id) = RootDatabase::with_single_file(text);
@@ -41,14 +40,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
41 let (db, file_id) = crate::tests::with_single_file(&before); 40 let (db, file_id) = crate::tests::with_single_file(&before);
42 let frange = FileRange { file_id, range: selection.into() }; 41 let frange = FileRange { file_id, range: selection.into() };
43 42
44 let mut assist = Assist::resolved(&db, frange) 43 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange)
45 .into_iter() 44 .into_iter()
46 .find(|assist| assist.assist.id.0 == assist_id) 45 .find(|assist| assist.assist.id.0 == assist_id)
47 .unwrap_or_else(|| { 46 .unwrap_or_else(|| {
48 panic!( 47 panic!(
49 "\n\nAssist is not applicable: {}\nAvailable assists: {}", 48 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
50 assist_id, 49 assist_id,
51 Assist::resolved(&db, frange) 50 Assist::resolved(&db, &AssistConfig::default(), frange)
52 .into_iter() 51 .into_iter()
53 .map(|assist| assist.assist.id.0) 52 .map(|assist| assist.assist.id.0)
54 .collect::<Vec<_>>() 53 .collect::<Vec<_>>()
@@ -90,7 +89,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
90 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; 89 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
91 90
92 let sema = Semantics::new(&db); 91 let sema = Semantics::new(&db);
93 let ctx = AssistContext::new(sema, frange); 92 let config = AssistConfig::default();
93 let ctx = AssistContext::new(sema, &config, frange);
94 let mut acc = Assists::new_resolved(&ctx); 94 let mut acc = Assists::new_resolved(&ctx);
95 handler(&mut acc, &ctx); 95 handler(&mut acc, &ctx);
96 let mut res = acc.finish_resolved(); 96 let mut res = acc.finish_resolved();
@@ -102,20 +102,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
102 102
103 let mut actual = db.file_text(change.file_id).as_ref().to_owned(); 103 let mut actual = db.file_text(change.file_id).as_ref().to_owned();
104 change.edit.apply(&mut actual); 104 change.edit.apply(&mut actual);
105
106 match source_change.cursor_position {
107 None => {
108 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
109 let off = change
110 .edit
111 .apply_to_offset(before_cursor_pos)
112 .expect("cursor position is affected by the edit");
113 actual = add_cursor(&actual, off)
114 }
115 }
116 Some(off) => actual = add_cursor(&actual, off.offset),
117 };
118
119 assert_eq_text!(after, &actual); 105 assert_eq_text!(after, &actual);
120 } 106 }
121 (Some(assist), ExpectedResult::Target(target)) => { 107 (Some(assist), ExpectedResult::Target(target)) => {
@@ -136,7 +122,7 @@ fn assist_order_field_struct() {
136 let (before_cursor_pos, before) = extract_offset(before); 122 let (before_cursor_pos, before) = extract_offset(before);
137 let (db, file_id) = with_single_file(&before); 123 let (db, file_id) = with_single_file(&before);
138 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; 124 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
139 let assists = Assist::resolved(&db, frange); 125 let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
140 let mut assists = assists.iter(); 126 let mut assists = assists.iter();
141 127
142 assert_eq!( 128 assert_eq!(
@@ -159,7 +145,7 @@ fn assist_order_if_expr() {
159 let (range, before) = extract_range(before); 145 let (range, before) = extract_range(before);
160 let (db, file_id) = with_single_file(&before); 146 let (db, file_id) = with_single_file(&before);
161 let frange = FileRange { file_id, range }; 147 let frange = FileRange { file_id, range };
162 let assists = Assist::resolved(&db, frange); 148 let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
163 let mut assists = assists.iter(); 149 let mut assists = assists.iter();
164 150
165 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 151 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index 972dbd251..250e56a69 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -15,7 +15,7 @@ struct S;
15struct S; 15struct S;
16 16
17impl Debug for S { 17impl Debug for S {
18 18 $0
19} 19}
20"#####, 20"#####,
21 ) 21 )
@@ -32,7 +32,7 @@ struct Point {
32} 32}
33"#####, 33"#####,
34 r#####" 34 r#####"
35#[derive()] 35#[derive($0)]
36struct Point { 36struct Point {
37 x: u32, 37 x: u32,
38 y: u32, 38 y: u32,
@@ -78,7 +78,7 @@ fn foo() {
78} 78}
79 79
80fn bar(arg: &str, baz: Baz) { 80fn bar(arg: &str, baz: Baz) {
81 todo!() 81 ${0:todo!()}
82} 82}
83 83
84"#####, 84"#####,
@@ -108,16 +108,16 @@ fn doctest_add_impl() {
108 "add_impl", 108 "add_impl",
109 r#####" 109 r#####"
110struct Ctx<T: Clone> { 110struct Ctx<T: Clone> {
111 data: T,<|> 111 data: T,<|>
112} 112}
113"#####, 113"#####,
114 r#####" 114 r#####"
115struct Ctx<T: Clone> { 115struct Ctx<T: Clone> {
116 data: T, 116 data: T,
117} 117}
118 118
119impl<T: Clone> Ctx<T> { 119impl<T: Clone> Ctx<T> {
120 120 $0
121} 121}
122"#####, 122"#####,
123 ) 123 )
@@ -150,7 +150,7 @@ trait Trait {
150impl Trait for () { 150impl Trait for () {
151 Type X = (); 151 Type X = ();
152 fn foo(&self) {} 152 fn foo(&self) {}
153 fn bar(&self) {} 153 $0fn bar(&self) {}
154 154
155} 155}
156"#####, 156"#####,
@@ -181,7 +181,7 @@ trait Trait<T> {
181 181
182impl Trait<u32> for () { 182impl Trait<u32> for () {
183 fn foo(&self) -> u32 { 183 fn foo(&self) -> u32 {
184 todo!() 184 ${0:todo!()}
185 } 185 }
186 186
187} 187}
@@ -204,7 +204,7 @@ struct Ctx<T: Clone> {
204} 204}
205 205
206impl<T: Clone> Ctx<T> { 206impl<T: Clone> Ctx<T> {
207 fn new(data: T) -> Self { Self { data } } 207 fn $0new(data: T) -> Self { Self { data } }
208} 208}
209 209
210"#####, 210"#####,
@@ -212,6 +212,25 @@ impl<T: Clone> Ctx<T> {
212} 212}
213 213
214#[test] 214#[test]
215fn doctest_add_turbo_fish() {
216 check_doc_test(
217 "add_turbo_fish",
218 r#####"
219fn make<T>() -> T { todo!() }
220fn main() {
221 let x = make<|>();
222}
223"#####,
224 r#####"
225fn make<T>() -> T { todo!() }
226fn main() {
227 let x = make::<${0:_}>();
228}
229"#####,
230 )
231}
232
233#[test]
215fn doctest_apply_demorgan() { 234fn doctest_apply_demorgan() {
216 check_doc_test( 235 check_doc_test(
217 "apply_demorgan", 236 "apply_demorgan",
@@ -257,7 +276,7 @@ fn doctest_change_return_type_to_result() {
257fn foo() -> i32<|> { 42i32 } 276fn foo() -> i32<|> { 42i32 }
258"#####, 277"#####,
259 r#####" 278 r#####"
260fn foo() -> Result<i32, > { Ok(42i32) } 279fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
261"#####, 280"#####,
262 ) 281 )
263} 282}
@@ -317,7 +336,7 @@ enum Action { Move { distance: u32 }, Stop }
317 336
318fn handle(action: Action) { 337fn handle(action: Action) {
319 match action { 338 match action {
320 Action::Move { distance } => {} 339 $0Action::Move { distance } => {}
321 Action::Stop => {} 340 Action::Stop => {}
322 } 341 }
323} 342}
@@ -326,6 +345,29 @@ fn handle(action: Action) {
326} 345}
327 346
328#[test] 347#[test]
348fn doctest_fix_visibility() {
349 check_doc_test(
350 "fix_visibility",
351 r#####"
352mod m {
353 fn frobnicate() {}
354}
355fn main() {
356 m::frobnicate<|>() {}
357}
358"#####,
359 r#####"
360mod m {
361 $0pub(crate) fn frobnicate() {}
362}
363fn main() {
364 m::frobnicate() {}
365}
366"#####,
367 )
368}
369
370#[test]
329fn doctest_flip_binexpr() { 371fn doctest_flip_binexpr() {
330 check_doc_test( 372 check_doc_test(
331 "flip_binexpr", 373 "flip_binexpr",
@@ -401,7 +443,7 @@ fn main() {
401"#####, 443"#####,
402 r#####" 444 r#####"
403fn main() { 445fn main() {
404 let var_name = (1 + 2); 446 let $0var_name = (1 + 2);
405 var_name * 4; 447 var_name * 4;
406} 448}
407"#####, 449"#####,
@@ -722,7 +764,7 @@ fn main() {
722 let x: Result<i32, i32> = Result::Ok(92); 764 let x: Result<i32, i32> = Result::Ok(92);
723 let y = match x { 765 let y = match x {
724 Ok(a) => a, 766 Ok(a) => a,
725 _ => unreachable!(), 767 $0_ => unreachable!(),
726 }; 768 };
727} 769}
728"#####, 770"#####,
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index f3fc92ebf..0038a9764 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -1,18 +1,57 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod insert_use; 2pub(crate) mod insert_use;
3 3
4use std::iter; 4use std::{iter, ops};
5 5
6use hir::{Adt, Crate, Semantics, Trait, Type}; 6use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type};
7use ra_ide_db::RootDatabase; 7use ra_ide_db::RootDatabase;
8use ra_syntax::{ 8use ra_syntax::{
9 ast::{self, make, NameOwner}, 9 ast::{self, make, NameOwner},
10 AstNode, T, 10 AstNode, SyntaxNode, T,
11}; 11};
12use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
13 13
14use crate::assist_config::SnippetCap;
15
14pub(crate) use insert_use::insert_use_statement; 16pub(crate) use insert_use::insert_use_statement;
15 17
18#[derive(Clone, Copy, Debug)]
19pub(crate) enum Cursor<'a> {
20 Replace(&'a SyntaxNode),
21 Before(&'a SyntaxNode),
22}
23
24impl<'a> Cursor<'a> {
25 fn node(self) -> &'a SyntaxNode {
26 match self {
27 Cursor::Replace(node) | Cursor::Before(node) => node,
28 }
29 }
30}
31
32pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String {
33 assert!(cursor.node().ancestors().any(|it| it == *node));
34 let range = cursor.node().text_range() - node.text_range().start();
35 let range: ops::Range<usize> = range.into();
36
37 let mut placeholder = cursor.node().to_string();
38 escape(&mut placeholder);
39 let tab_stop = match cursor {
40 Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
41 Cursor::Before(placeholder) => format!("$0{}", placeholder),
42 };
43
44 let mut buf = node.to_string();
45 buf.replace_range(range, &tab_stop);
46 return buf;
47
48 fn escape(buf: &mut String) {
49 stdx::replace(buf, '{', r"\{");
50 stdx::replace(buf, '}', r"\}");
51 stdx::replace(buf, '$', r"\$");
52 }
53}
54
16pub fn get_missing_assoc_items( 55pub fn get_missing_assoc_items(
17 sema: &Semantics<RootDatabase>, 56 sema: &Semantics<RootDatabase>,
18 impl_def: &ast::ImplDef, 57 impl_def: &ast::ImplDef,
@@ -161,13 +200,19 @@ impl FamousDefs<'_, '_> {
161 #[cfg(test)] 200 #[cfg(test)]
162 pub(crate) const FIXTURE: &'static str = r#" 201 pub(crate) const FIXTURE: &'static str = r#"
163//- /libcore.rs crate:core 202//- /libcore.rs crate:core
164pub mod convert{ 203pub mod convert {
165 pub trait From<T> { 204 pub trait From<T> {
166 fn from(T) -> Self; 205 fn from(T) -> Self;
167 } 206 }
168} 207}
169 208
170pub mod prelude { pub use crate::convert::From } 209pub mod option {
210 pub enum Option<T> { None, Some(T)}
211}
212
213pub mod prelude {
214 pub use crate::{convert::From, option::Option::{self, *}};
215}
171#[prelude_import] 216#[prelude_import]
172pub use prelude::*; 217pub use prelude::*;
173"#; 218"#;
@@ -176,7 +221,25 @@ pub use prelude::*;
176 self.find_trait("core:convert:From") 221 self.find_trait("core:convert:From")
177 } 222 }
178 223
224 pub(crate) fn core_option_Option(&self) -> Option<Enum> {
225 self.find_enum("core:option:Option")
226 }
227
179 fn find_trait(&self, path: &str) -> Option<Trait> { 228 fn find_trait(&self, path: &str) -> Option<Trait> {
229 match self.find_def(path)? {
230 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
231 _ => None,
232 }
233 }
234
235 fn find_enum(&self, path: &str) -> Option<Enum> {
236 match self.find_def(path)? {
237 hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it),
238 _ => None,
239 }
240 }
241
242 fn find_def(&self, path: &str) -> Option<ScopeDef> {
180 let db = self.0.db; 243 let db = self.0.db;
181 let mut path = path.split(':'); 244 let mut path = path.split(':');
182 let trait_ = path.next_back()?; 245 let trait_ = path.next_back()?;
@@ -201,9 +264,6 @@ pub use prelude::*;
201 } 264 }
202 let def = 265 let def =
203 module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; 266 module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1;
204 match def { 267 Some(def)
205 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
206 _ => None,
207 }
208 } 268 }
209} 269}
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index 1214e3cd4..0ee43482f 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11}; 11};
12use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEditBuilder;
13 13
14use crate::assist_context::{AssistBuilder, AssistContext}; 14use crate::assist_context::AssistContext;
15 15
16/// Creates and inserts a use statement for the given path to import. 16/// Creates and inserts a use statement for the given path to import.
17/// The use statement is inserted in the scope most appropriate to the 17/// The use statement is inserted in the scope most appropriate to the
@@ -21,7 +21,7 @@ pub(crate) fn insert_use_statement(
21 position: &SyntaxNode, 21 position: &SyntaxNode,
22 path_to_import: &ModPath, 22 path_to_import: &ModPath,
23 ctx: &AssistContext, 23 ctx: &AssistContext,
24 builder: &mut AssistBuilder, 24 builder: &mut TextEditBuilder,
25) { 25) {
26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { 27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
@@ -33,7 +33,7 @@ pub(crate) fn insert_use_statement(
33 33
34 if let Some(container) = container { 34 if let Some(container) = container {
35 let action = best_action_for_target(container, position.clone(), &target); 35 let action = best_action_for_target(container, position.clone(), &target);
36 make_assist(&action, &target, builder.text_edit_builder()); 36 make_assist(&action, &target, builder);
37 } 37 }
38} 38}
39 39
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index c69e0efea..e08d62dd6 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -15,7 +15,7 @@ use ra_syntax::{
15 }, 15 },
16 AstNode, AstPtr, 16 AstNode, AstPtr,
17}; 17};
18use test_utils::tested_by; 18use test_utils::mark;
19 19
20use crate::{ 20use crate::{
21 adt::StructKind, 21 adt::StructKind,
@@ -226,7 +226,7 @@ impl ExprCollector<'_> {
226 None => self.collect_expr_opt(condition.expr()), 226 None => self.collect_expr_opt(condition.expr()),
227 // if let -- desugar to match 227 // if let -- desugar to match
228 Some(pat) => { 228 Some(pat) => {
229 tested_by!(infer_resolve_while_let); 229 mark::hit!(infer_resolve_while_let);
230 let pat = self.collect_pat(pat); 230 let pat = self.collect_pat(pat);
231 let match_expr = self.collect_expr_opt(condition.expr()); 231 let match_expr = self.collect_expr_opt(condition.expr());
232 let placeholder_pat = self.missing_pat(); 232 let placeholder_pat = self.missing_pat();
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs
index 86f953c80..09e92b74e 100644
--- a/crates/ra_hir_def/src/body/scope.rs
+++ b/crates/ra_hir_def/src/body/scope.rs
@@ -174,7 +174,7 @@ mod tests {
174 use hir_expand::{name::AsName, InFile}; 174 use hir_expand::{name::AsName, InFile};
175 use ra_db::{fixture::WithFixture, FileId, SourceDatabase}; 175 use ra_db::{fixture::WithFixture, FileId, SourceDatabase};
176 use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; 176 use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
177 use test_utils::{assert_eq_text, covers, extract_offset}; 177 use test_utils::{assert_eq_text, extract_offset, mark};
178 178
179 use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId}; 179 use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId};
180 180
@@ -388,7 +388,7 @@ mod tests {
388 388
389 #[test] 389 #[test]
390 fn while_let_desugaring() { 390 fn while_let_desugaring() {
391 covers!(infer_resolve_while_let); 391 mark::check!(infer_resolve_while_let);
392 do_check_local_name( 392 do_check_local_name(
393 r#" 393 r#"
394fn test() { 394fn test() {
diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs
index 498a4c917..945a0025e 100644
--- a/crates/ra_hir_def/src/db.rs
+++ b/crates/ra_hir_def/src/db.rs
@@ -17,6 +17,7 @@ use crate::{
17 item_scope::ItemInNs, 17 item_scope::ItemInNs,
18 lang_item::{LangItemTarget, LangItems}, 18 lang_item::{LangItemTarget, LangItems},
19 nameres::{raw::RawItems, CrateDefMap}, 19 nameres::{raw::RawItems, CrateDefMap},
20 path::ModPath,
20 visibility::Visibility, 21 visibility::Visibility,
21 AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc, 22 AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc,
22 GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId, 23 GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId,
@@ -112,12 +113,15 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
112 #[salsa::invoke(Documentation::documentation_query)] 113 #[salsa::invoke(Documentation::documentation_query)]
113 fn documentation(&self, def: AttrDefId) -> Option<Documentation>; 114 fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
114 115
115 #[salsa::invoke(find_path::importable_locations_in_crate)] 116 #[salsa::invoke(find_path::importable_locations_of_query)]
116 fn importable_locations_of( 117 fn importable_locations_of(
117 &self, 118 &self,
118 item: ItemInNs, 119 item: ItemInNs,
119 krate: CrateId, 120 krate: CrateId,
120 ) -> Arc<[(ModuleId, Name, Visibility)]>; 121 ) -> Arc<[(ModuleId, Name, Visibility)]>;
122
123 #[salsa::invoke(find_path::find_path_inner_query)]
124 fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option<ModPath>;
121} 125}
122 126
123fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { 127fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs
index 1ca20fabd..4db798473 100644
--- a/crates/ra_hir_def/src/find_path.rs
+++ b/crates/ra_hir_def/src/find_path.rs
@@ -1,5 +1,11 @@
1//! An algorithm to find a path to refer to a certain item. 1//! An algorithm to find a path to refer to a certain item.
2 2
3use std::sync::Arc;
4
5use hir_expand::name::{known, AsName, Name};
6use ra_prof::profile;
7use test_utils::mark;
8
3use crate::{ 9use crate::{
4 db::DefDatabase, 10 db::DefDatabase,
5 item_scope::ItemInNs, 11 item_scope::ItemInNs,
@@ -7,26 +13,28 @@ use crate::{
7 visibility::Visibility, 13 visibility::Visibility,
8 CrateId, ModuleDefId, ModuleId, 14 CrateId, ModuleDefId, ModuleId,
9}; 15};
10use hir_expand::name::{known, AsName, Name}; 16
11use std::sync::Arc; 17// FIXME: handle local items
12use test_utils::tested_by; 18
19/// Find a path that can be used to refer to a certain item. This can depend on
20/// *from where* you're referring to the item, hence the `from` parameter.
21pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
22 let _p = profile("find_path");
23 db.find_path_inner(item, from, MAX_PATH_LEN)
24}
13 25
14const MAX_PATH_LEN: usize = 15; 26const MAX_PATH_LEN: usize = 15;
15 27
16impl ModPath { 28impl ModPath {
17 fn starts_with_std(&self) -> bool { 29 fn starts_with_std(&self) -> bool {
18 self.segments.first().filter(|&first_segment| first_segment == &known::std).is_some() 30 self.segments.first() == Some(&known::std)
19 } 31 }
20 32
21 // When std library is present, paths starting with `std::` 33 // When std library is present, paths starting with `std::`
22 // should be preferred over paths starting with `core::` and `alloc::` 34 // should be preferred over paths starting with `core::` and `alloc::`
23 fn can_start_with_std(&self) -> bool { 35 fn can_start_with_std(&self) -> bool {
24 self.segments 36 let first_segment = self.segments.first();
25 .first() 37 first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
26 .filter(|&first_segment| {
27 first_segment == &known::alloc || first_segment == &known::core
28 })
29 .is_some()
30 } 38 }
31 39
32 fn len(&self) -> usize { 40 fn len(&self) -> usize {
@@ -41,16 +49,7 @@ impl ModPath {
41 } 49 }
42} 50}
43 51
44// FIXME: handle local items 52pub(crate) fn find_path_inner_query(
45
46/// Find a path that can be used to refer to a certain item. This can depend on
47/// *from where* you're referring to the item, hence the `from` parameter.
48pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
49 let _p = ra_prof::profile("find_path");
50 find_path_inner(db, item, from, MAX_PATH_LEN)
51}
52
53fn find_path_inner(
54 db: &dyn DefDatabase, 53 db: &dyn DefDatabase,
55 item: ItemInNs, 54 item: ItemInNs,
56 from: ModuleId, 55 from: ModuleId,
@@ -141,8 +140,7 @@ fn find_path_inner(
141 let mut best_path = None; 140 let mut best_path = None;
142 let mut best_path_len = max_len; 141 let mut best_path_len = max_len;
143 for (module_id, name) in importable_locations { 142 for (module_id, name) in importable_locations {
144 let mut path = match find_path_inner( 143 let mut path = match db.find_path_inner(
145 db,
146 ItemInNs::Types(ModuleDefId::ModuleId(module_id)), 144 ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
147 from, 145 from,
148 best_path_len - 1, 146 best_path_len - 1,
@@ -165,17 +163,19 @@ fn find_path_inner(
165 163
166fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { 164fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath {
167 if old_path.starts_with_std() && new_path.can_start_with_std() { 165 if old_path.starts_with_std() && new_path.can_start_with_std() {
168 tested_by!(prefer_std_paths);
169 if prefer_no_std { 166 if prefer_no_std {
167 mark::hit!(prefer_no_std_paths);
170 new_path 168 new_path
171 } else { 169 } else {
170 mark::hit!(prefer_std_paths);
172 old_path 171 old_path
173 } 172 }
174 } else if new_path.starts_with_std() && old_path.can_start_with_std() { 173 } else if new_path.starts_with_std() && old_path.can_start_with_std() {
175 tested_by!(prefer_std_paths);
176 if prefer_no_std { 174 if prefer_no_std {
175 mark::hit!(prefer_no_std_paths);
177 old_path 176 old_path
178 } else { 177 } else {
178 mark::hit!(prefer_std_paths);
179 new_path 179 new_path
180 } 180 }
181 } else if new_path.len() < old_path.len() { 181 } else if new_path.len() < old_path.len() {
@@ -215,11 +215,12 @@ fn find_importable_locations(
215/// 215///
216/// Note that the crate doesn't need to be the one in which the item is defined; 216/// Note that the crate doesn't need to be the one in which the item is defined;
217/// it might be re-exported in other crates. 217/// it might be re-exported in other crates.
218pub(crate) fn importable_locations_in_crate( 218pub(crate) fn importable_locations_of_query(
219 db: &dyn DefDatabase, 219 db: &dyn DefDatabase,
220 item: ItemInNs, 220 item: ItemInNs,
221 krate: CrateId, 221 krate: CrateId,
222) -> Arc<[(ModuleId, Name, Visibility)]> { 222) -> Arc<[(ModuleId, Name, Visibility)]> {
223 let _p = profile("importable_locations_of_query");
223 let def_map = db.crate_def_map(krate); 224 let def_map = db.crate_def_map(krate);
224 let mut result = Vec::new(); 225 let mut result = Vec::new();
225 for (local_id, data) in def_map.modules.iter() { 226 for (local_id, data) in def_map.modules.iter() {
@@ -251,12 +252,14 @@ pub(crate) fn importable_locations_in_crate(
251 252
252#[cfg(test)] 253#[cfg(test)]
253mod tests { 254mod tests {
254 use super::*;
255 use crate::test_db::TestDB;
256 use hir_expand::hygiene::Hygiene; 255 use hir_expand::hygiene::Hygiene;
257 use ra_db::fixture::WithFixture; 256 use ra_db::fixture::WithFixture;
258 use ra_syntax::ast::AstNode; 257 use ra_syntax::ast::AstNode;
259 use test_utils::covers; 258 use test_utils::mark;
259
260 use crate::test_db::TestDB;
261
262 use super::*;
260 263
261 /// `code` needs to contain a cursor marker; checks that `find_path` for the 264 /// `code` needs to contain a cursor marker; checks that `find_path` for the
262 /// item the `path` refers to returns that same path when called from the 265 /// item the `path` refers to returns that same path when called from the
@@ -511,7 +514,7 @@ mod tests {
511 514
512 #[test] 515 #[test]
513 fn prefer_std_paths_over_alloc() { 516 fn prefer_std_paths_over_alloc() {
514 covers!(prefer_std_paths); 517 mark::check!(prefer_std_paths);
515 let code = r#" 518 let code = r#"
516 //- /main.rs crate:main deps:alloc,std 519 //- /main.rs crate:main deps:alloc,std
517 <|> 520 <|>
@@ -530,51 +533,50 @@ mod tests {
530 } 533 }
531 534
532 #[test] 535 #[test]
533 fn prefer_alloc_paths_over_std() { 536 fn prefer_core_paths_over_std() {
534 covers!(prefer_std_paths); 537 mark::check!(prefer_no_std_paths);
535 let code = r#" 538 let code = r#"
536 //- /main.rs crate:main deps:alloc,std 539 //- /main.rs crate:main deps:core,std
537 #![no_std] 540 #![no_std]
538 541
539 <|> 542 <|>
540 543
541 //- /std.rs crate:std deps:alloc 544 //- /std.rs crate:std deps:core
542 545
543 pub mod sync { 546 pub mod fmt {
544 pub use alloc::sync::Arc; 547 pub use core::fmt::Error;
545 } 548 }
546 549
547 //- /zzz.rs crate:alloc 550 //- /zzz.rs crate:core
548 551
549 pub mod sync { 552 pub mod fmt {
550 pub struct Arc; 553 pub struct Error;
551 } 554 }
552 "#; 555 "#;
553 check_found_path(code, "alloc::sync::Arc"); 556 check_found_path(code, "core::fmt::Error");
554 } 557 }
555 558
556 #[test] 559 #[test]
557 fn prefer_core_paths_over_std() { 560 fn prefer_alloc_paths_over_std() {
558 covers!(prefer_std_paths);
559 let code = r#" 561 let code = r#"
560 //- /main.rs crate:main deps:core,std 562 //- /main.rs crate:main deps:alloc,std
561 #![no_std] 563 #![no_std]
562 564
563 <|> 565 <|>
564 566
565 //- /std.rs crate:std deps:core 567 //- /std.rs crate:std deps:alloc
566 568
567 pub mod fmt { 569 pub mod sync {
568 pub use core::fmt::Error; 570 pub use alloc::sync::Arc;
569 } 571 }
570 572
571 //- /zzz.rs crate:core 573 //- /zzz.rs crate:alloc
572 574
573 pub mod fmt { 575 pub mod sync {
574 pub struct Error; 576 pub struct Arc;
575 } 577 }
576 "#; 578 "#;
577 check_found_path(code, "core::fmt::Error"); 579 check_found_path(code, "alloc::sync::Arc");
578 } 580 }
579 581
580 #[test] 582 #[test]
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs
index 518772e8a..5325a2760 100644
--- a/crates/ra_hir_def/src/lib.rs
+++ b/crates/ra_hir_def/src/lib.rs
@@ -46,8 +46,6 @@ pub mod find_path;
46 46
47#[cfg(test)] 47#[cfg(test)]
48mod test_db; 48mod test_db;
49#[cfg(test)]
50mod marks;
51 49
52use std::hash::Hash; 50use std::hash::Hash;
53 51
diff --git a/crates/ra_hir_def/src/marks.rs b/crates/ra_hir_def/src/marks.rs
deleted file mode 100644
index daa49d5f1..000000000
--- a/crates/ra_hir_def/src/marks.rs
+++ /dev/null
@@ -1,17 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks!(
4 bogus_paths
5 name_res_works_for_broken_modules
6 can_import_enum_variant
7 glob_enum
8 glob_enum_group
9 glob_across_crates
10 std_prelude
11 macro_rules_from_other_crates_are_visible_with_macro_use
12 prelude_is_macro_use
13 macro_dollar_crate_self
14 macro_dollar_crate_other
15 infer_resolve_while_let
16 prefer_std_paths
17);
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index db994122a..353a31ad4 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -14,7 +14,7 @@ use ra_cfg::CfgOptions;
14use ra_db::{CrateId, FileId, ProcMacroId}; 14use ra_db::{CrateId, FileId, ProcMacroId};
15use ra_syntax::ast; 15use ra_syntax::ast;
16use rustc_hash::FxHashMap; 16use rustc_hash::FxHashMap;
17use test_utils::tested_by; 17use test_utils::mark;
18 18
19use crate::{ 19use crate::{
20 attr::Attrs, 20 attr::Attrs,
@@ -302,7 +302,7 @@ impl DefCollector<'_> {
302 ); 302 );
303 303
304 if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { 304 if let Some(ModuleDefId::ModuleId(m)) = res.take_types() {
305 tested_by!(macro_rules_from_other_crates_are_visible_with_macro_use); 305 mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use);
306 self.import_all_macros_exported(current_module_id, m.krate); 306 self.import_all_macros_exported(current_module_id, m.krate);
307 } 307 }
308 } 308 }
@@ -412,10 +412,10 @@ impl DefCollector<'_> {
412 match def.take_types() { 412 match def.take_types() {
413 Some(ModuleDefId::ModuleId(m)) => { 413 Some(ModuleDefId::ModuleId(m)) => {
414 if import.is_prelude { 414 if import.is_prelude {
415 tested_by!(std_prelude); 415 mark::hit!(std_prelude);
416 self.def_map.prelude = Some(m); 416 self.def_map.prelude = Some(m);
417 } else if m.krate != self.def_map.krate { 417 } else if m.krate != self.def_map.krate {
418 tested_by!(glob_across_crates); 418 mark::hit!(glob_across_crates);
419 // glob import from other crate => we can just import everything once 419 // glob import from other crate => we can just import everything once
420 let item_map = self.db.crate_def_map(m.krate); 420 let item_map = self.db.crate_def_map(m.krate);
421 let scope = &item_map[m.local_id].scope; 421 let scope = &item_map[m.local_id].scope;
@@ -461,7 +461,7 @@ impl DefCollector<'_> {
461 } 461 }
462 } 462 }
463 Some(ModuleDefId::AdtId(AdtId::EnumId(e))) => { 463 Some(ModuleDefId::AdtId(AdtId::EnumId(e))) => {
464 tested_by!(glob_enum); 464 mark::hit!(glob_enum);
465 // glob import from enum => just import all the variants 465 // glob import from enum => just import all the variants
466 466
467 // XXX: urgh, so this works by accident! Here, we look at 467 // XXX: urgh, so this works by accident! Here, we look at
@@ -510,7 +510,7 @@ impl DefCollector<'_> {
510 510
511 self.update(module_id, &[(name, def)], vis); 511 self.update(module_id, &[(name, def)], vis);
512 } 512 }
513 None => tested_by!(bogus_paths), 513 None => mark::hit!(bogus_paths),
514 } 514 }
515 } 515 }
516 } 516 }
@@ -683,7 +683,7 @@ impl ModCollector<'_, '_> {
683 // Prelude module is always considered to be `#[macro_use]`. 683 // Prelude module is always considered to be `#[macro_use]`.
684 if let Some(prelude_module) = self.def_collector.def_map.prelude { 684 if let Some(prelude_module) = self.def_collector.def_map.prelude {
685 if prelude_module.krate != self.def_collector.def_map.krate { 685 if prelude_module.krate != self.def_collector.def_map.krate {
686 tested_by!(prelude_is_macro_use); 686 mark::hit!(prelude_is_macro_use);
687 self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); 687 self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate);
688 } 688 }
689 } 689 }
diff --git a/crates/ra_hir_def/src/nameres/path_resolution.rs b/crates/ra_hir_def/src/nameres/path_resolution.rs
index 35a0a0c98..19692e70c 100644
--- a/crates/ra_hir_def/src/nameres/path_resolution.rs
+++ b/crates/ra_hir_def/src/nameres/path_resolution.rs
@@ -14,7 +14,7 @@ use std::iter::successors;
14 14
15use hir_expand::name::Name; 15use hir_expand::name::Name;
16use ra_db::Edition; 16use ra_db::Edition;
17use test_utils::tested_by; 17use test_utils::mark;
18 18
19use crate::{ 19use crate::{
20 db::DefDatabase, 20 db::DefDatabase,
@@ -108,7 +108,7 @@ impl CrateDefMap {
108 let mut curr_per_ns: PerNs = match path.kind { 108 let mut curr_per_ns: PerNs = match path.kind {
109 PathKind::DollarCrate(krate) => { 109 PathKind::DollarCrate(krate) => {
110 if krate == self.krate { 110 if krate == self.krate {
111 tested_by!(macro_dollar_crate_self); 111 mark::hit!(macro_dollar_crate_self);
112 PerNs::types( 112 PerNs::types(
113 ModuleId { krate: self.krate, local_id: self.root }.into(), 113 ModuleId { krate: self.krate, local_id: self.root }.into(),
114 Visibility::Public, 114 Visibility::Public,
@@ -116,7 +116,7 @@ impl CrateDefMap {
116 } else { 116 } else {
117 let def_map = db.crate_def_map(krate); 117 let def_map = db.crate_def_map(krate);
118 let module = ModuleId { krate, local_id: def_map.root }; 118 let module = ModuleId { krate, local_id: def_map.root };
119 tested_by!(macro_dollar_crate_other); 119 mark::hit!(macro_dollar_crate_other);
120 PerNs::types(module.into(), Visibility::Public) 120 PerNs::types(module.into(), Visibility::Public)
121 } 121 }
122 } 122 }
@@ -221,7 +221,7 @@ impl CrateDefMap {
221 } 221 }
222 ModuleDefId::AdtId(AdtId::EnumId(e)) => { 222 ModuleDefId::AdtId(AdtId::EnumId(e)) => {
223 // enum variant 223 // enum variant
224 tested_by!(can_import_enum_variant); 224 mark::hit!(can_import_enum_variant);
225 let enum_data = db.enum_data(e); 225 let enum_data = db.enum_data(e);
226 match enum_data.variant(&segment) { 226 match enum_data.variant(&segment) {
227 Some(local_id) => { 227 Some(local_id) => {
diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs
index f2716a295..4e628b14d 100644
--- a/crates/ra_hir_def/src/nameres/raw.rs
+++ b/crates/ra_hir_def/src/nameres/raw.rs
@@ -18,7 +18,7 @@ use ra_syntax::{
18 ast::{self, AttrsOwner, NameOwner, VisibilityOwner}, 18 ast::{self, AttrsOwner, NameOwner, VisibilityOwner},
19 AstNode, 19 AstNode,
20}; 20};
21use test_utils::tested_by; 21use test_utils::mark;
22 22
23use crate::{ 23use crate::{
24 attr::Attrs, 24 attr::Attrs,
@@ -346,7 +346,7 @@ impl RawItemsCollector {
346 self.push_item(current_module, attrs, RawItemKind::Module(item)); 346 self.push_item(current_module, attrs, RawItemKind::Module(item));
347 return; 347 return;
348 } 348 }
349 tested_by!(name_res_works_for_broken_modules); 349 mark::hit!(name_res_works_for_broken_modules);
350 } 350 }
351 351
352 fn add_use_item(&mut self, current_module: Option<Idx<ModuleData>>, use_item: ast::UseItem) { 352 fn add_use_item(&mut self, current_module: Option<Idx<ModuleData>>, use_item: ast::UseItem) {
diff --git a/crates/ra_hir_def/src/nameres/tests.rs b/crates/ra_hir_def/src/nameres/tests.rs
index 1b66c1aac..05cd0297d 100644
--- a/crates/ra_hir_def/src/nameres/tests.rs
+++ b/crates/ra_hir_def/src/nameres/tests.rs
@@ -8,7 +8,7 @@ use std::sync::Arc;
8 8
9use insta::assert_snapshot; 9use insta::assert_snapshot;
10use ra_db::{fixture::WithFixture, SourceDatabase}; 10use ra_db::{fixture::WithFixture, SourceDatabase};
11use test_utils::covers; 11use test_utils::mark;
12 12
13use crate::{db::DefDatabase, nameres::*, test_db::TestDB}; 13use crate::{db::DefDatabase, nameres::*, test_db::TestDB};
14 14
@@ -132,7 +132,7 @@ fn crate_def_map_fn_mod_same_name() {
132 132
133#[test] 133#[test]
134fn bogus_paths() { 134fn bogus_paths() {
135 covers!(bogus_paths); 135 mark::check!(bogus_paths);
136 let map = def_map( 136 let map = def_map(
137 " 137 "
138 //- /lib.rs 138 //- /lib.rs
@@ -247,7 +247,7 @@ fn re_exports() {
247 247
248#[test] 248#[test]
249fn std_prelude() { 249fn std_prelude() {
250 covers!(std_prelude); 250 mark::check!(std_prelude);
251 let map = def_map( 251 let map = def_map(
252 " 252 "
253 //- /main.rs crate:main deps:test_crate 253 //- /main.rs crate:main deps:test_crate
@@ -271,7 +271,7 @@ fn std_prelude() {
271 271
272#[test] 272#[test]
273fn can_import_enum_variant() { 273fn can_import_enum_variant() {
274 covers!(can_import_enum_variant); 274 mark::check!(can_import_enum_variant);
275 let map = def_map( 275 let map = def_map(
276 " 276 "
277 //- /lib.rs 277 //- /lib.rs
diff --git a/crates/ra_hir_def/src/nameres/tests/globs.rs b/crates/ra_hir_def/src/nameres/tests/globs.rs
index ee8df3a26..2b12c0daa 100644
--- a/crates/ra_hir_def/src/nameres/tests/globs.rs
+++ b/crates/ra_hir_def/src/nameres/tests/globs.rs
@@ -152,7 +152,7 @@ fn glob_privacy_2() {
152 152
153#[test] 153#[test]
154fn glob_across_crates() { 154fn glob_across_crates() {
155 covers!(glob_across_crates); 155 mark::check!(glob_across_crates);
156 let map = def_map( 156 let map = def_map(
157 r" 157 r"
158 //- /main.rs crate:main deps:test_crate 158 //- /main.rs crate:main deps:test_crate
@@ -171,7 +171,6 @@ fn glob_across_crates() {
171 171
172#[test] 172#[test]
173fn glob_privacy_across_crates() { 173fn glob_privacy_across_crates() {
174 covers!(glob_across_crates);
175 let map = def_map( 174 let map = def_map(
176 r" 175 r"
177 //- /main.rs crate:main deps:test_crate 176 //- /main.rs crate:main deps:test_crate
@@ -191,7 +190,7 @@ fn glob_privacy_across_crates() {
191 190
192#[test] 191#[test]
193fn glob_enum() { 192fn glob_enum() {
194 covers!(glob_enum); 193 mark::check!(glob_enum);
195 let map = def_map( 194 let map = def_map(
196 " 195 "
197 //- /lib.rs 196 //- /lib.rs
@@ -212,7 +211,7 @@ fn glob_enum() {
212 211
213#[test] 212#[test]
214fn glob_enum_group() { 213fn glob_enum_group() {
215 covers!(glob_enum_group); 214 mark::check!(glob_enum_group);
216 let map = def_map( 215 let map = def_map(
217 r" 216 r"
218 //- /lib.rs 217 //- /lib.rs
diff --git a/crates/ra_hir_def/src/nameres/tests/macros.rs b/crates/ra_hir_def/src/nameres/tests/macros.rs
index 40289e3ca..84480d9f6 100644
--- a/crates/ra_hir_def/src/nameres/tests/macros.rs
+++ b/crates/ra_hir_def/src/nameres/tests/macros.rs
@@ -212,7 +212,7 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
212 212
213#[test] 213#[test]
214fn macro_rules_from_other_crates_are_visible_with_macro_use() { 214fn macro_rules_from_other_crates_are_visible_with_macro_use() {
215 covers!(macro_rules_from_other_crates_are_visible_with_macro_use); 215 mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use);
216 let map = def_map( 216 let map = def_map(
217 " 217 "
218 //- /main.rs crate:main deps:foo 218 //- /main.rs crate:main deps:foo
@@ -262,7 +262,7 @@ fn macro_rules_from_other_crates_are_visible_with_macro_use() {
262 262
263#[test] 263#[test]
264fn prelude_is_macro_use() { 264fn prelude_is_macro_use() {
265 covers!(prelude_is_macro_use); 265 mark::check!(prelude_is_macro_use);
266 let map = def_map( 266 let map = def_map(
267 " 267 "
268 //- /main.rs crate:main deps:foo 268 //- /main.rs crate:main deps:foo
@@ -544,8 +544,7 @@ fn path_qualified_macros() {
544 544
545#[test] 545#[test]
546fn macro_dollar_crate_is_correct_in_item() { 546fn macro_dollar_crate_is_correct_in_item() {
547 covers!(macro_dollar_crate_self); 547 mark::check!(macro_dollar_crate_self);
548 covers!(macro_dollar_crate_other);
549 let map = def_map( 548 let map = def_map(
550 " 549 "
551 //- /main.rs crate:main deps:foo 550 //- /main.rs crate:main deps:foo
@@ -603,7 +602,7 @@ fn macro_dollar_crate_is_correct_in_item() {
603 602
604#[test] 603#[test]
605fn macro_dollar_crate_is_correct_in_indirect_deps() { 604fn macro_dollar_crate_is_correct_in_indirect_deps() {
606 covers!(macro_dollar_crate_other); 605 mark::check!(macro_dollar_crate_other);
607 // From std 606 // From std
608 let map = def_map( 607 let map = def_map(
609 r#" 608 r#"
diff --git a/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs
index 37fcdfb8c..b43b294ca 100644
--- a/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs
+++ b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs
@@ -2,7 +2,7 @@ use super::*;
2 2
3#[test] 3#[test]
4fn name_res_works_for_broken_modules() { 4fn name_res_works_for_broken_modules() {
5 covers!(name_res_works_for_broken_modules); 5 mark::check!(name_res_works_for_broken_modules);
6 let map = def_map( 6 let map = def_map(
7 r" 7 r"
8 //- /lib.rs 8 //- /lib.rs
diff --git a/crates/ra_hir_def/src/path/lower/lower_use.rs b/crates/ra_hir_def/src/path/lower/lower_use.rs
index 5b6854b0f..7cc655487 100644
--- a/crates/ra_hir_def/src/path/lower/lower_use.rs
+++ b/crates/ra_hir_def/src/path/lower/lower_use.rs
@@ -6,7 +6,7 @@ use std::iter;
6use either::Either; 6use either::Either;
7use hir_expand::{hygiene::Hygiene, name::AsName}; 7use hir_expand::{hygiene::Hygiene, name::AsName};
8use ra_syntax::ast::{self, NameOwner}; 8use ra_syntax::ast::{self, NameOwner};
9use test_utils::tested_by; 9use test_utils::mark;
10 10
11use crate::path::{ImportAlias, ModPath, PathKind}; 11use crate::path::{ImportAlias, ModPath, PathKind};
12 12
@@ -54,7 +54,7 @@ pub(crate) fn lower_use_tree(
54 // FIXME: report errors somewhere 54 // FIXME: report errors somewhere
55 // We get here if we do 55 // We get here if we do
56 } else if is_glob { 56 } else if is_glob {
57 tested_by!(glob_enum_group); 57 mark::hit!(glob_enum_group);
58 if let Some(prefix) = prefix { 58 if let Some(prefix) = prefix {
59 cb(prefix, &tree, is_glob, None) 59 cb(prefix, &tree, is_glob, None)
60 } 60 }
diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs
index 149f65042..3e6e1e333 100644
--- a/crates/ra_hir_ty/src/_match.rs
+++ b/crates/ra_hir_ty/src/_match.rs
@@ -1946,6 +1946,23 @@ mod tests {
1946 1946
1947 check_no_diagnostic(content); 1947 check_no_diagnostic(content);
1948 } 1948 }
1949
1950 #[test]
1951 fn expr_diverges_missing_arm() {
1952 let content = r"
1953 enum Either {
1954 A,
1955 B,
1956 }
1957 fn test_fn() {
1958 match loop {} {
1959 Either::A => (),
1960 }
1961 }
1962 ";
1963
1964 check_no_diagnostic(content);
1965 }
1949} 1966}
1950 1967
1951#[cfg(test)] 1968#[cfg(test)]
@@ -1998,26 +2015,6 @@ mod false_negatives {
1998 } 2015 }
1999 2016
2000 #[test] 2017 #[test]
2001 fn expr_diverges_missing_arm() {
2002 let content = r"
2003 enum Either {
2004 A,
2005 B,
2006 }
2007 fn test_fn() {
2008 match loop {} {
2009 Either::A => (),
2010 }
2011 }
2012 ";
2013
2014 // This is a false negative.
2015 // Even though the match expression diverges, rustc fails
2016 // to compile here since `Either::B` is missing.
2017 check_no_diagnostic(content);
2018 }
2019
2020 #[test]
2021 fn expr_loop_missing_arm() { 2018 fn expr_loop_missing_arm() {
2022 let content = r" 2019 let content = r"
2023 enum Either { 2020 enum Either {
@@ -2035,7 +2032,7 @@ mod false_negatives {
2035 // We currently infer the type of `loop { break Foo::A }` to `!`, which 2032 // We currently infer the type of `loop { break Foo::A }` to `!`, which
2036 // causes us to skip the diagnostic since `Either::A` doesn't type check 2033 // causes us to skip the diagnostic since `Either::A` doesn't type check
2037 // with `!`. 2034 // with `!`.
2038 check_no_diagnostic(content); 2035 check_diagnostic(content);
2039 } 2036 }
2040 2037
2041 #[test] 2038 #[test]
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index 2876cb141..957d6e0b5 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -218,6 +218,7 @@ struct InferenceContext<'a> {
218#[derive(Clone, Debug)] 218#[derive(Clone, Debug)]
219struct BreakableContext { 219struct BreakableContext {
220 pub may_break: bool, 220 pub may_break: bool,
221 pub break_ty: Ty,
221} 222}
222 223
223impl<'a> InferenceContext<'a> { 224impl<'a> InferenceContext<'a> {
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs
index 173ec59ed..2ee9adb16 100644
--- a/crates/ra_hir_ty/src/infer/coerce.rs
+++ b/crates/ra_hir_ty/src/infer/coerce.rs
@@ -5,7 +5,7 @@
5//! See: https://doc.rust-lang.org/nomicon/coercions.html 5//! See: https://doc.rust-lang.org/nomicon/coercions.html
6 6
7use hir_def::{lang_item::LangItemTarget, type_ref::Mutability}; 7use hir_def::{lang_item::LangItemTarget, type_ref::Mutability};
8use test_utils::tested_by; 8use test_utils::mark;
9 9
10use crate::{autoderef, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor}; 10use crate::{autoderef, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor};
11 11
@@ -34,7 +34,7 @@ impl<'a> InferenceContext<'a> {
34 ty1.clone() 34 ty1.clone()
35 } else { 35 } else {
36 if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) { 36 if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
37 tested_by!(coerce_fn_reification); 37 mark::hit!(coerce_fn_reification);
38 // Special case: two function types. Try to coerce both to 38 // Special case: two function types. Try to coerce both to
39 // pointers to have a chance at getting a match. See 39 // pointers to have a chance at getting a match. See
40 // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 40 // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
@@ -44,7 +44,7 @@ impl<'a> InferenceContext<'a> {
44 let ptr_ty2 = Ty::fn_ptr(sig2); 44 let ptr_ty2 = Ty::fn_ptr(sig2);
45 self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) 45 self.coerce_merge_branch(&ptr_ty1, &ptr_ty2)
46 } else { 46 } else {
47 tested_by!(coerce_merge_fail_fallback); 47 mark::hit!(coerce_merge_fail_fallback);
48 // For incompatible types, we use the latter one as result 48 // For incompatible types, we use the latter one as result
49 // to be better recovery for `if` without `else`. 49 // to be better recovery for `if` without `else`.
50 ty2.clone() 50 ty2.clone()
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs
index 0b67d216a..b28724f0e 100644
--- a/crates/ra_hir_ty/src/infer/expr.rs
+++ b/crates/ra_hir_ty/src/infer/expr.rs
@@ -93,22 +93,25 @@ impl<'a> InferenceContext<'a> {
93 Ty::Unknown 93 Ty::Unknown
94 } 94 }
95 Expr::Loop { body } => { 95 Expr::Loop { body } => {
96 self.breakables.push(BreakableContext { may_break: false }); 96 self.breakables.push(BreakableContext {
97 may_break: false,
98 break_ty: self.table.new_type_var(),
99 });
97 self.infer_expr(*body, &Expectation::has_type(Ty::unit())); 100 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
98 101
99 let ctxt = self.breakables.pop().expect("breakable stack broken"); 102 let ctxt = self.breakables.pop().expect("breakable stack broken");
100 if ctxt.may_break { 103 if ctxt.may_break {
101 self.diverges = Diverges::Maybe; 104 self.diverges = Diverges::Maybe;
102 } 105 }
103 // FIXME handle break with value 106
104 if ctxt.may_break { 107 if ctxt.may_break {
105 Ty::unit() 108 ctxt.break_ty
106 } else { 109 } else {
107 Ty::simple(TypeCtor::Never) 110 Ty::simple(TypeCtor::Never)
108 } 111 }
109 } 112 }
110 Expr::While { condition, body } => { 113 Expr::While { condition, body } => {
111 self.breakables.push(BreakableContext { may_break: false }); 114 self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
112 // while let is desugared to a match loop, so this is always simple while 115 // while let is desugared to a match loop, so this is always simple while
113 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); 116 self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
114 self.infer_expr(*body, &Expectation::has_type(Ty::unit())); 117 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
@@ -120,7 +123,7 @@ impl<'a> InferenceContext<'a> {
120 Expr::For { iterable, body, pat } => { 123 Expr::For { iterable, body, pat } => {
121 let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); 124 let iterable_ty = self.infer_expr(*iterable, &Expectation::none());
122 125
123 self.breakables.push(BreakableContext { may_break: false }); 126 self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown });
124 let pat_ty = 127 let pat_ty =
125 self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); 128 self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
126 129
@@ -229,17 +232,29 @@ impl<'a> InferenceContext<'a> {
229 } 232 }
230 Expr::Continue => Ty::simple(TypeCtor::Never), 233 Expr::Continue => Ty::simple(TypeCtor::Never),
231 Expr::Break { expr } => { 234 Expr::Break { expr } => {
232 if let Some(expr) = expr { 235 let val_ty = if let Some(expr) = expr {
233 // FIXME handle break with value 236 self.infer_expr(*expr, &Expectation::none())
234 self.infer_expr(*expr, &Expectation::none()); 237 } else {
235 } 238 Ty::unit()
239 };
240
241 let last_ty = if let Some(ctxt) = self.breakables.last() {
242 ctxt.break_ty.clone()
243 } else {
244 Ty::Unknown
245 };
246
247 let merged_type = self.coerce_merge_branch(&last_ty, &val_ty);
248
236 if let Some(ctxt) = self.breakables.last_mut() { 249 if let Some(ctxt) = self.breakables.last_mut() {
250 ctxt.break_ty = merged_type;
237 ctxt.may_break = true; 251 ctxt.may_break = true;
238 } else { 252 } else {
239 self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { 253 self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
240 expr: tgt_expr, 254 expr: tgt_expr,
241 }); 255 });
242 } 256 }
257
243 Ty::simple(TypeCtor::Never) 258 Ty::simple(TypeCtor::Never)
244 } 259 }
245 Expr::Return { expr } => { 260 Expr::Return { expr } => {
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs
index 54ec870df..4006f595d 100644
--- a/crates/ra_hir_ty/src/infer/pat.rs
+++ b/crates/ra_hir_ty/src/infer/pat.rs
@@ -10,7 +10,7 @@ use hir_def::{
10 FieldId, 10 FieldId,
11}; 11};
12use hir_expand::name::Name; 12use hir_expand::name::Name;
13use test_utils::tested_by; 13use test_utils::mark;
14 14
15use super::{BindingMode, Expectation, InferenceContext}; 15use super::{BindingMode, Expectation, InferenceContext};
16use crate::{utils::variant_data, Substs, Ty, TypeCtor}; 16use crate::{utils::variant_data, Substs, Ty, TypeCtor};
@@ -111,7 +111,7 @@ impl<'a> InferenceContext<'a> {
111 } 111 }
112 } 112 }
113 } else if let Pat::Ref { .. } = &body[pat] { 113 } else if let Pat::Ref { .. } = &body[pat] {
114 tested_by!(match_ergonomics_ref); 114 mark::hit!(match_ergonomics_ref);
115 // When you encounter a `&pat` pattern, reset to Move. 115 // When you encounter a `&pat` pattern, reset to Move.
116 // This is so that `w` is by value: `let (_, &w) = &(1, &2);` 116 // This is so that `w` is by value: `let (_, &w) = &(1, &2);`
117 default_bm = BindingMode::Move; 117 default_bm = BindingMode::Move;
diff --git a/crates/ra_hir_ty/src/infer/unify.rs b/crates/ra_hir_ty/src/infer/unify.rs
index ab0bc8b70..269495ca0 100644
--- a/crates/ra_hir_ty/src/infer/unify.rs
+++ b/crates/ra_hir_ty/src/infer/unify.rs
@@ -4,7 +4,7 @@ use std::borrow::Cow;
4 4
5use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; 5use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue};
6 6
7use test_utils::tested_by; 7use test_utils::mark;
8 8
9use super::{InferenceContext, Obligation}; 9use super::{InferenceContext, Obligation};
10use crate::{ 10use crate::{
@@ -313,7 +313,7 @@ impl InferenceTable {
313 // more than once 313 // more than once
314 for i in 0..3 { 314 for i in 0..3 {
315 if i > 0 { 315 if i > 0 {
316 tested_by!(type_var_resolves_to_int_var); 316 mark::hit!(type_var_resolves_to_int_var);
317 } 317 }
318 match &*ty { 318 match &*ty {
319 Ty::Infer(tv) => { 319 Ty::Infer(tv) => {
@@ -342,7 +342,7 @@ impl InferenceTable {
342 Ty::Infer(tv) => { 342 Ty::Infer(tv) => {
343 let inner = tv.to_inner(); 343 let inner = tv.to_inner();
344 if tv_stack.contains(&inner) { 344 if tv_stack.contains(&inner) {
345 tested_by!(type_var_cycles_resolve_as_possible); 345 mark::hit!(type_var_cycles_resolve_as_possible);
346 // recursive type 346 // recursive type
347 return tv.fallback_value(); 347 return tv.fallback_value();
348 } 348 }
@@ -369,7 +369,7 @@ impl InferenceTable {
369 Ty::Infer(tv) => { 369 Ty::Infer(tv) => {
370 let inner = tv.to_inner(); 370 let inner = tv.to_inner();
371 if tv_stack.contains(&inner) { 371 if tv_stack.contains(&inner) {
372 tested_by!(type_var_cycles_resolve_completely); 372 mark::hit!(type_var_cycles_resolve_completely);
373 // recursive type 373 // recursive type
374 return tv.fallback_value(); 374 return tv.fallback_value();
375 } 375 }
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs
index daea02f88..c87ee06ce 100644
--- a/crates/ra_hir_ty/src/lib.rs
+++ b/crates/ra_hir_ty/src/lib.rs
@@ -42,7 +42,6 @@ pub mod expr;
42mod tests; 42mod tests;
43#[cfg(test)] 43#[cfg(test)]
44mod test_db; 44mod test_db;
45mod marks;
46mod _match; 45mod _match;
47 46
48use std::ops::Deref; 47use std::ops::Deref;
diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs
index 9ad6dbe07..35ac86a46 100644
--- a/crates/ra_hir_ty/src/lower.rs
+++ b/crates/ra_hir_ty/src/lower.rs
@@ -812,7 +812,7 @@ impl TraitEnvironment {
812 // add `Self: Trait<T1, T2, ...>` to the environment in trait 812 // add `Self: Trait<T1, T2, ...>` to the environment in trait
813 // function default implementations (and hypothetical code 813 // function default implementations (and hypothetical code
814 // inside consts or type aliases) 814 // inside consts or type aliases)
815 test_utils::tested_by!(trait_self_implements_self); 815 test_utils::mark::hit!(trait_self_implements_self);
816 let substs = Substs::type_params(db, trait_id); 816 let substs = Substs::type_params(db, trait_id);
817 let trait_ref = TraitRef { trait_: trait_id, substs }; 817 let trait_ref = TraitRef { trait_: trait_id, substs };
818 let pred = GenericPredicate::Implemented(trait_ref); 818 let pred = GenericPredicate::Implemented(trait_ref);
diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs
deleted file mode 100644
index a39740143..000000000
--- a/crates/ra_hir_ty/src/marks.rs
+++ /dev/null
@@ -1,12 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks!(
4 type_var_cycles_resolve_completely
5 type_var_cycles_resolve_as_possible
6 type_var_resolves_to_int_var
7 impl_self_type_match_without_receiver
8 match_ergonomics_ref
9 coerce_merge_fail_fallback
10 coerce_fn_reification
11 trait_self_implements_self
12);
diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs
index 0851e16a8..e19628fdf 100644
--- a/crates/ra_hir_ty/src/method_resolution.rs
+++ b/crates/ra_hir_ty/src/method_resolution.rs
@@ -469,7 +469,7 @@ fn iterate_inherent_methods<T>(
469 // already happens in `is_valid_candidate` above; if not, we 469 // already happens in `is_valid_candidate` above; if not, we
470 // check it here 470 // check it here
471 if receiver_ty.is_none() && inherent_impl_substs(db, impl_def, self_ty).is_none() { 471 if receiver_ty.is_none() && inherent_impl_substs(db, impl_def, self_ty).is_none() {
472 test_utils::tested_by!(impl_self_type_match_without_receiver); 472 test_utils::mark::hit!(impl_self_type_match_without_receiver);
473 continue; 473 continue;
474 } 474 }
475 if let Some(result) = callback(&self_ty.value, item) { 475 if let Some(result) = callback(&self_ty.value, item) {
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs
index 6dc4b2cd1..2cc4f4bf9 100644
--- a/crates/ra_hir_ty/src/tests/coercion.rs
+++ b/crates/ra_hir_ty/src/tests/coercion.rs
@@ -1,6 +1,6 @@
1use super::infer_with_mismatches; 1use super::infer_with_mismatches;
2use insta::assert_snapshot; 2use insta::assert_snapshot;
3use test_utils::covers; 3use test_utils::mark;
4 4
5// Infer with some common definitions and impls. 5// Infer with some common definitions and impls.
6fn infer(source: &str) -> String { 6fn infer(source: &str) -> String {
@@ -339,7 +339,7 @@ fn test(i: i32) {
339 339
340#[test] 340#[test]
341fn coerce_merge_one_by_one1() { 341fn coerce_merge_one_by_one1() {
342 covers!(coerce_merge_fail_fallback); 342 mark::check!(coerce_merge_fail_fallback);
343 343
344 assert_snapshot!( 344 assert_snapshot!(
345 infer(r#" 345 infer(r#"
@@ -547,7 +547,7 @@ fn test() {
547 547
548#[test] 548#[test]
549fn coerce_fn_items_in_match_arms() { 549fn coerce_fn_items_in_match_arms() {
550 covers!(coerce_fn_reification); 550 mark::check!(coerce_fn_reification);
551 assert_snapshot!( 551 assert_snapshot!(
552 infer_with_mismatches(r#" 552 infer_with_mismatches(r#"
553fn foo1(x: u32) -> isize { 1 } 553fn foo1(x: u32) -> isize { 1 }
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs
index 9c2c9e1d2..558a70022 100644
--- a/crates/ra_hir_ty/src/tests/method_resolution.rs
+++ b/crates/ra_hir_ty/src/tests/method_resolution.rs
@@ -984,7 +984,7 @@ fn test() { S2.into()<|>; }
984 984
985#[test] 985#[test]
986fn method_resolution_overloaded_method() { 986fn method_resolution_overloaded_method() {
987 test_utils::covers!(impl_self_type_match_without_receiver); 987 test_utils::mark::check!(impl_self_type_match_without_receiver);
988 let t = type_at( 988 let t = type_at(
989 r#" 989 r#"
990//- main.rs 990//- main.rs
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs
index d83ff5e0e..0c5f972a2 100644
--- a/crates/ra_hir_ty/src/tests/patterns.rs
+++ b/crates/ra_hir_ty/src/tests/patterns.rs
@@ -1,5 +1,5 @@
1use insta::assert_snapshot; 1use insta::assert_snapshot;
2use test_utils::covers; 2use test_utils::mark;
3 3
4use super::{infer, infer_with_mismatches}; 4use super::{infer, infer_with_mismatches};
5 5
@@ -197,7 +197,7 @@ fn test() {
197 197
198#[test] 198#[test]
199fn infer_pattern_match_ergonomics_ref() { 199fn infer_pattern_match_ergonomics_ref() {
200 covers!(match_ergonomics_ref); 200 mark::check!(match_ergonomics_ref);
201 assert_snapshot!( 201 assert_snapshot!(
202 infer(r#" 202 infer(r#"
203fn test() { 203fn test() {
diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs
index c2168222e..1f004bd63 100644
--- a/crates/ra_hir_ty/src/tests/regression.rs
+++ b/crates/ra_hir_ty/src/tests/regression.rs
@@ -1,9 +1,10 @@
1use insta::assert_snapshot; 1use insta::assert_snapshot;
2use test_utils::covers; 2use ra_db::fixture::WithFixture;
3use test_utils::mark;
3 4
4use super::infer;
5use crate::test_db::TestDB; 5use crate::test_db::TestDB;
6use ra_db::fixture::WithFixture; 6
7use super::infer;
7 8
8#[test] 9#[test]
9fn bug_484() { 10fn bug_484() {
@@ -89,8 +90,8 @@ fn quux() {
89 90
90#[test] 91#[test]
91fn recursive_vars() { 92fn recursive_vars() {
92 covers!(type_var_cycles_resolve_completely); 93 mark::check!(type_var_cycles_resolve_completely);
93 covers!(type_var_cycles_resolve_as_possible); 94 mark::check!(type_var_cycles_resolve_as_possible);
94 assert_snapshot!( 95 assert_snapshot!(
95 infer(r#" 96 infer(r#"
96fn test() { 97fn test() {
@@ -112,8 +113,6 @@ fn test() {
112 113
113#[test] 114#[test]
114fn recursive_vars_2() { 115fn recursive_vars_2() {
115 covers!(type_var_cycles_resolve_completely);
116 covers!(type_var_cycles_resolve_as_possible);
117 assert_snapshot!( 116 assert_snapshot!(
118 infer(r#" 117 infer(r#"
119fn test() { 118fn test() {
@@ -170,7 +169,7 @@ fn write() {
170 169
171#[test] 170#[test]
172fn infer_std_crash_2() { 171fn infer_std_crash_2() {
173 covers!(type_var_resolves_to_int_var); 172 mark::check!(type_var_resolves_to_int_var);
174 // caused "equating two type variables, ...", taken from std 173 // caused "equating two type variables, ...", taken from std
175 assert_snapshot!( 174 assert_snapshot!(
176 infer(r#" 175 infer(r#"
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index 72122c070..fd2208af2 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -1860,3 +1860,66 @@ fn test() {
1860 "### 1860 "###
1861 ); 1861 );
1862} 1862}
1863
1864#[test]
1865fn infer_loop_break_with_val() {
1866 assert_snapshot!(
1867 infer(r#"
1868enum Option<T> { Some(T), None }
1869use Option::*;
1870
1871fn test() {
1872 let x = loop {
1873 if false {
1874 break None;
1875 }
1876
1877 break Some(true);
1878 };
1879}
1880"#),
1881 @r###"
1882 60..169 '{ ... }; }': ()
1883 70..71 'x': Option<bool>
1884 74..166 'loop {... }': Option<bool>
1885 79..166 '{ ... }': ()
1886 89..133 'if fal... }': ()
1887 92..97 'false': bool
1888 98..133 '{ ... }': ()
1889 112..122 'break None': !
1890 118..122 'None': Option<bool>
1891 143..159 'break ...(true)': !
1892 149..153 'Some': Some<bool>(bool) -> Option<bool>
1893 149..159 'Some(true)': Option<bool>
1894 154..158 'true': bool
1895 "###
1896 );
1897}
1898
1899#[test]
1900fn infer_loop_break_without_val() {
1901 assert_snapshot!(
1902 infer(r#"
1903enum Option<T> { Some(T), None }
1904use Option::*;
1905
1906fn test() {
1907 let x = loop {
1908 if false {
1909 break;
1910 }
1911 };
1912}
1913"#),
1914 @r###"
1915 60..137 '{ ... }; }': ()
1916 70..71 'x': ()
1917 74..134 'loop {... }': ()
1918 79..134 '{ ... }': ()
1919 89..128 'if fal... }': ()
1920 92..97 'false': bool
1921 98..128 '{ ... }': ()
1922 112..117 'break': !
1923 "###
1924 );
1925}
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index c49aacf98..34f4b9039 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -1,10 +1,11 @@
1use insta::assert_snapshot; 1use insta::assert_snapshot;
2
3use ra_db::fixture::WithFixture; 2use ra_db::fixture::WithFixture;
3use test_utils::mark;
4 4
5use super::{infer, infer_with_mismatches, type_at, type_at_pos};
6use crate::test_db::TestDB; 5use crate::test_db::TestDB;
7 6
7use super::{infer, infer_with_mismatches, type_at, type_at_pos};
8
8#[test] 9#[test]
9fn infer_await() { 10fn infer_await() {
10 let (db, pos) = TestDB::with_position( 11 let (db, pos) = TestDB::with_position(
@@ -301,7 +302,7 @@ fn test() {
301 302
302#[test] 303#[test]
303fn trait_default_method_self_bound_implements_trait() { 304fn trait_default_method_self_bound_implements_trait() {
304 test_utils::covers!(trait_self_implements_self); 305 mark::check!(trait_self_implements_self);
305 assert_snapshot!( 306 assert_snapshot!(
306 infer(r#" 307 infer(r#"
307trait Trait { 308trait Trait {
@@ -324,7 +325,6 @@ trait Trait {
324 325
325#[test] 326#[test]
326fn trait_default_method_self_bound_implements_super_trait() { 327fn trait_default_method_self_bound_implements_super_trait() {
327 test_utils::covers!(trait_self_implements_self);
328 assert_snapshot!( 328 assert_snapshot!(
329 infer(r#" 329 infer(r#"
330trait SuperTrait { 330trait SuperTrait {
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs
index 780a03c13..aa039e6fc 100644
--- a/crates/ra_ide/src/call_info.rs
+++ b/crates/ra_ide/src/call_info.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 ast::{self, ArgListOwner}, 5 ast::{self, ArgListOwner},
6 match_ast, AstNode, SyntaxNode, SyntaxToken, 6 match_ast, AstNode, SyntaxNode, SyntaxToken,
7}; 7};
8use test_utils::tested_by; 8use test_utils::mark;
9 9
10use crate::{CallInfo, FilePosition, FunctionSignature}; 10use crate::{CallInfo, FilePosition, FunctionSignature};
11 11
@@ -84,7 +84,7 @@ fn call_info_for_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Op
84 84
85 let arg_list_range = arg_list.syntax().text_range(); 85 let arg_list_range = arg_list.syntax().text_range();
86 if !arg_list_range.contains_inclusive(token.text_range().start()) { 86 if !arg_list_range.contains_inclusive(token.text_range().start()) {
87 tested_by!(call_info_bad_offset); 87 mark::hit!(call_info_bad_offset);
88 return None; 88 return None;
89 } 89 }
90 90
@@ -213,7 +213,7 @@ impl CallInfo {
213 213
214#[cfg(test)] 214#[cfg(test)]
215mod tests { 215mod tests {
216 use test_utils::covers; 216 use test_utils::mark;
217 217
218 use crate::mock_analysis::single_file_with_position; 218 use crate::mock_analysis::single_file_with_position;
219 219
@@ -529,7 +529,7 @@ By default this method stops actor's `Context`."#
529 529
530 #[test] 530 #[test]
531 fn call_info_bad_offset() { 531 fn call_info_bad_offset() {
532 covers!(call_info_bad_offset); 532 mark::check!(call_info_bad_offset);
533 let (analysis, position) = single_file_with_position( 533 let (analysis, position) = single_file_with_position(
534 r#"fn foo(x: u32, y: u32) -> u32 {x + y} 534 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
535 fn bar() { foo <|> (3, ); }"#, 535 fn bar() { foo <|> (3, ); }"#,
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 8bdc43b1a..191300704 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -59,8 +59,8 @@ pub use crate::completion::{
59/// with ordering of completions (currently this is done by the client). 59/// with ordering of completions (currently this is done by the client).
60pub(crate) fn completions( 60pub(crate) fn completions(
61 db: &RootDatabase, 61 db: &RootDatabase,
62 position: FilePosition,
63 config: &CompletionConfig, 62 config: &CompletionConfig,
63 position: FilePosition,
64) -> Option<Completions> { 64) -> Option<Completions> {
65 let ctx = CompletionContext::new(db, position, config)?; 65 let ctx = CompletionContext::new(db, position, config)?;
66 66
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index db7430454..02ac0166b 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -3,7 +3,7 @@
3use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
5use rustc_hash::FxHashSet; 5use rustc_hash::FxHashSet;
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::completion::{CompletionContext, Completions}; 8use crate::completion::{CompletionContext, Completions};
9 9
@@ -40,7 +40,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
40 if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { 40 if let Some(name_ref) = ctx.name_ref_syntax.as_ref() {
41 if name_ref.syntax().text() == name.to_string().as_str() { 41 if name_ref.syntax().text() == name.to_string().as_str() {
42 // for `use self::foo<|>`, don't suggest `foo` as a completion 42 // for `use self::foo<|>`, don't suggest `foo` as a completion
43 tested_by!(dont_complete_current_use); 43 mark::hit!(dont_complete_current_use);
44 continue; 44 continue;
45 } 45 }
46 } 46 }
@@ -147,7 +147,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
147 147
148#[cfg(test)] 148#[cfg(test)]
149mod tests { 149mod tests {
150 use test_utils::covers; 150 use test_utils::mark;
151 151
152 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 152 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
153 use insta::assert_debug_snapshot; 153 use insta::assert_debug_snapshot;
@@ -158,7 +158,7 @@ mod tests {
158 158
159 #[test] 159 #[test]
160 fn dont_complete_current_use() { 160 fn dont_complete_current_use() {
161 covers!(dont_complete_current_use); 161 mark::check!(dont_complete_current_use);
162 let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); 162 let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference);
163 assert!(completions.is_empty()); 163 assert!(completions.is_empty());
164 } 164 }
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index bd40af1cb..db791660a 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -1,7 +1,7 @@
1//! Completion of names from the current scope, e.g. locals and imported items. 1//! Completion of names from the current scope, e.g. locals and imported items.
2 2
3use hir::ScopeDef; 3use hir::ScopeDef;
4use test_utils::tested_by; 4use test_utils::mark;
5 5
6use crate::completion::{CompletionContext, Completions}; 6use crate::completion::{CompletionContext, Completions};
7use hir::{Adt, ModuleDef, Type}; 7use hir::{Adt, ModuleDef, Type};
@@ -30,7 +30,7 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
30 if ctx.use_item_syntax.is_some() { 30 if ctx.use_item_syntax.is_some() {
31 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { 31 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
32 if name_ref.syntax().text() == name.to_string().as_str() { 32 if name_ref.syntax().text() == name.to_string().as_str() {
33 tested_by!(self_fulfilling_completion); 33 mark::hit!(self_fulfilling_completion);
34 return; 34 return;
35 } 35 }
36 } 36 }
@@ -66,7 +66,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
66#[cfg(test)] 66#[cfg(test)]
67mod tests { 67mod tests {
68 use insta::assert_debug_snapshot; 68 use insta::assert_debug_snapshot;
69 use test_utils::covers; 69 use test_utils::mark;
70 70
71 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 71 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
72 72
@@ -76,7 +76,7 @@ mod tests {
76 76
77 #[test] 77 #[test]
78 fn self_fulfilling_completion() { 78 fn self_fulfilling_completion() {
79 covers!(self_fulfilling_completion); 79 mark::check!(self_fulfilling_completion);
80 assert_debug_snapshot!( 80 assert_debug_snapshot!(
81 do_reference_completion( 81 do_reference_completion(
82 r#" 82 r#"
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 6021f7279..cfb7c1e38 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -63,8 +63,8 @@ impl fmt::Debug for CompletionItem {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let mut s = f.debug_struct("CompletionItem"); 64 let mut s = f.debug_struct("CompletionItem");
65 s.field("label", &self.label()).field("source_range", &self.source_range()); 65 s.field("label", &self.label()).field("source_range", &self.source_range());
66 if self.text_edit().as_indels().len() == 1 { 66 if self.text_edit().len() == 1 {
67 let atom = &self.text_edit().as_indels()[0]; 67 let atom = &self.text_edit().iter().next().unwrap();
68 s.field("delete", &atom.delete); 68 s.field("delete", &atom.delete);
69 s.field("insert", &atom.insert); 69 s.field("insert", &atom.insert);
70 } else { 70 } else {
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 077cf9647..440ffa31d 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -3,7 +3,7 @@
3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; 3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
4use ra_syntax::ast::NameOwner; 4use ra_syntax::ast::NameOwner;
5use stdx::SepBy; 5use stdx::SepBy;
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::{ 8use crate::{
9 completion::{ 9 completion::{
@@ -121,7 +121,7 @@ impl Completions {
121 _ => false, 121 _ => false,
122 }; 122 };
123 if has_non_default_type_params { 123 if has_non_default_type_params {
124 tested_by!(inserts_angle_brackets_for_generics); 124 mark::hit!(inserts_angle_brackets_for_generics);
125 completion_item = completion_item 125 completion_item = completion_item
126 .lookup_by(local_name.clone()) 126 .lookup_by(local_name.clone())
127 .label(format!("{}<…>", local_name)) 127 .label(format!("{}<…>", local_name))
@@ -176,7 +176,7 @@ impl Completions {
176 } 176 }
177 None if needs_bang => builder.insert_text(format!("{}!", name)), 177 None if needs_bang => builder.insert_text(format!("{}!", name)),
178 _ => { 178 _ => {
179 tested_by!(dont_insert_macro_call_parens_unncessary); 179 mark::hit!(dont_insert_macro_call_parens_unncessary);
180 builder.insert_text(name) 180 builder.insert_text(name)
181 } 181 }
182 }; 182 };
@@ -330,14 +330,14 @@ pub(crate) fn compute_score(
330 // FIXME: this should not fall back to string equality. 330 // FIXME: this should not fall back to string equality.
331 let ty = &ty.display(ctx.db).to_string(); 331 let ty = &ty.display(ctx.db).to_string();
332 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { 332 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
333 tested_by!(test_struct_field_completion_in_record_lit); 333 mark::hit!(test_struct_field_completion_in_record_lit);
334 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; 334 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
335 ( 335 (
336 struct_field.name(ctx.db).to_string(), 336 struct_field.name(ctx.db).to_string(),
337 struct_field.signature_ty(ctx.db).display(ctx.db).to_string(), 337 struct_field.signature_ty(ctx.db).display(ctx.db).to_string(),
338 ) 338 )
339 } else if let Some(active_parameter) = &ctx.active_parameter { 339 } else if let Some(active_parameter) = &ctx.active_parameter {
340 tested_by!(test_struct_field_completion_in_func_call); 340 mark::hit!(test_struct_field_completion_in_func_call);
341 (active_parameter.name.clone(), active_parameter.ty.clone()) 341 (active_parameter.name.clone(), active_parameter.ty.clone())
342 } else { 342 } else {
343 return None; 343 return None;
@@ -398,7 +398,7 @@ impl Builder {
398 None => return self, 398 None => return self,
399 }; 399 };
400 // If not an import, add parenthesis automatically. 400 // If not an import, add parenthesis automatically.
401 tested_by!(inserts_parens_for_function_calls); 401 mark::hit!(inserts_parens_for_function_calls);
402 402
403 let (snippet, label) = if params.is_empty() { 403 let (snippet, label) = if params.is_empty() {
404 (format!("{}()$0", name), format!("{}()", name)) 404 (format!("{}()$0", name), format!("{}()", name))
@@ -457,7 +457,7 @@ fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static s
457#[cfg(test)] 457#[cfg(test)]
458mod tests { 458mod tests {
459 use insta::assert_debug_snapshot; 459 use insta::assert_debug_snapshot;
460 use test_utils::covers; 460 use test_utils::mark;
461 461
462 use crate::completion::{ 462 use crate::completion::{
463 test_utils::{do_completion, do_completion_with_options}, 463 test_utils::{do_completion, do_completion_with_options},
@@ -607,7 +607,7 @@ mod tests {
607 607
608 #[test] 608 #[test]
609 fn inserts_parens_for_function_calls() { 609 fn inserts_parens_for_function_calls() {
610 covers!(inserts_parens_for_function_calls); 610 mark::check!(inserts_parens_for_function_calls);
611 assert_debug_snapshot!( 611 assert_debug_snapshot!(
612 do_reference_completion( 612 do_reference_completion(
613 r" 613 r"
@@ -992,7 +992,7 @@ mod tests {
992 992
993 #[test] 993 #[test]
994 fn inserts_angle_brackets_for_generics() { 994 fn inserts_angle_brackets_for_generics() {
995 covers!(inserts_angle_brackets_for_generics); 995 mark::check!(inserts_angle_brackets_for_generics);
996 assert_debug_snapshot!( 996 assert_debug_snapshot!(
997 do_reference_completion( 997 do_reference_completion(
998 r" 998 r"
@@ -1115,7 +1115,7 @@ mod tests {
1115 1115
1116 #[test] 1116 #[test]
1117 fn dont_insert_macro_call_parens_unncessary() { 1117 fn dont_insert_macro_call_parens_unncessary() {
1118 covers!(dont_insert_macro_call_parens_unncessary); 1118 mark::check!(dont_insert_macro_call_parens_unncessary);
1119 assert_debug_snapshot!( 1119 assert_debug_snapshot!(
1120 do_reference_completion( 1120 do_reference_completion(
1121 r" 1121 r"
@@ -1181,7 +1181,7 @@ mod tests {
1181 1181
1182 #[test] 1182 #[test]
1183 fn test_struct_field_completion_in_func_call() { 1183 fn test_struct_field_completion_in_func_call() {
1184 covers!(test_struct_field_completion_in_func_call); 1184 mark::check!(test_struct_field_completion_in_func_call);
1185 assert_debug_snapshot!( 1185 assert_debug_snapshot!(
1186 do_reference_completion( 1186 do_reference_completion(
1187 r" 1187 r"
@@ -1271,7 +1271,7 @@ mod tests {
1271 1271
1272 #[test] 1272 #[test]
1273 fn test_struct_field_completion_in_record_lit() { 1273 fn test_struct_field_completion_in_record_lit() {
1274 covers!(test_struct_field_completion_in_record_lit); 1274 mark::check!(test_struct_field_completion_in_record_lit);
1275 assert_debug_snapshot!( 1275 assert_debug_snapshot!(
1276 do_reference_completion( 1276 do_reference_completion(
1277 r" 1277 r"
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index eb90b5279..bf22452a2 100644
--- a/crates/ra_ide/src/completion/test_utils.rs
+++ b/crates/ra_ide/src/completion/test_utils.rs
@@ -20,7 +20,7 @@ pub(crate) fn do_completion_with_options(
20 } else { 20 } else {
21 single_file_with_position(code) 21 single_file_with_position(code)
22 }; 22 };
23 let completions = analysis.completions(position, options).unwrap().unwrap(); 23 let completions = analysis.completions(options, position).unwrap().unwrap();
24 let completion_items: Vec<CompletionItem> = completions.into(); 24 let completion_items: Vec<CompletionItem> = completions.into();
25 let mut kind_completions: Vec<CompletionItem> = 25 let mut kind_completions: Vec<CompletionItem> =
26 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); 26 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 87a0b80f1..c2819bbf7 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -628,7 +628,7 @@ mod tests {
628 path: "foo.rs", 628 path: "foo.rs",
629 }, 629 },
630 ], 630 ],
631 cursor_position: None, 631 is_snippet: false,
632 }, 632 },
633 ), 633 ),
634 severity: Error, 634 severity: Error,
@@ -684,7 +684,7 @@ mod tests {
684 }, 684 },
685 ], 685 ],
686 file_system_edits: [], 686 file_system_edits: [],
687 cursor_position: None, 687 is_snippet: false,
688 }, 688 },
689 ), 689 ),
690 severity: Error, 690 severity: Error,
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 150895abb..90e85d419 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -93,7 +93,7 @@ pub(crate) fn reference_definition(
93 93
94#[cfg(test)] 94#[cfg(test)]
95mod tests { 95mod tests {
96 use test_utils::{assert_eq_text, covers}; 96 use test_utils::assert_eq_text;
97 97
98 use crate::mock_analysis::analysis_and_position; 98 use crate::mock_analysis::analysis_and_position;
99 99
@@ -208,7 +208,6 @@ mod tests {
208 208
209 #[test] 209 #[test]
210 fn goto_def_for_macros() { 210 fn goto_def_for_macros() {
211 covers!(ra_ide_db::goto_def_for_macros);
212 check_goto( 211 check_goto(
213 " 212 "
214 //- /lib.rs 213 //- /lib.rs
@@ -225,7 +224,6 @@ mod tests {
225 224
226 #[test] 225 #[test]
227 fn goto_def_for_macros_from_other_crates() { 226 fn goto_def_for_macros_from_other_crates() {
228 covers!(ra_ide_db::goto_def_for_macros);
229 check_goto( 227 check_goto(
230 " 228 "
231 //- /lib.rs 229 //- /lib.rs
@@ -245,7 +243,6 @@ mod tests {
245 243
246 #[test] 244 #[test]
247 fn goto_def_for_use_alias() { 245 fn goto_def_for_use_alias() {
248 covers!(ra_ide_db::goto_def_for_use_alias);
249 check_goto( 246 check_goto(
250 " 247 "
251 //- /lib.rs 248 //- /lib.rs
@@ -370,7 +367,6 @@ mod tests {
370 367
371 #[test] 368 #[test]
372 fn goto_def_for_methods() { 369 fn goto_def_for_methods() {
373 covers!(ra_ide_db::goto_def_for_methods);
374 check_goto( 370 check_goto(
375 " 371 "
376 //- /lib.rs 372 //- /lib.rs
@@ -390,7 +386,6 @@ mod tests {
390 386
391 #[test] 387 #[test]
392 fn goto_def_for_fields() { 388 fn goto_def_for_fields() {
393 covers!(ra_ide_db::goto_def_for_fields);
394 check_goto( 389 check_goto(
395 r" 390 r"
396 //- /lib.rs 391 //- /lib.rs
@@ -409,7 +404,6 @@ mod tests {
409 404
410 #[test] 405 #[test]
411 fn goto_def_for_record_fields() { 406 fn goto_def_for_record_fields() {
412 covers!(ra_ide_db::goto_def_for_record_fields);
413 check_goto( 407 check_goto(
414 r" 408 r"
415 //- /lib.rs 409 //- /lib.rs
@@ -430,7 +424,6 @@ mod tests {
430 424
431 #[test] 425 #[test]
432 fn goto_def_for_record_pat_fields() { 426 fn goto_def_for_record_pat_fields() {
433 covers!(ra_ide_db::goto_def_for_record_field_pats);
434 check_goto( 427 check_goto(
435 r" 428 r"
436 //- /lib.rs 429 //- /lib.rs
@@ -873,7 +866,6 @@ mod tests {
873 866
874 #[test] 867 #[test]
875 fn goto_def_for_field_init_shorthand() { 868 fn goto_def_for_field_init_shorthand() {
876 covers!(ra_ide_db::goto_def_for_field_init_shorthand);
877 check_goto( 869 check_goto(
878 " 870 "
879 //- /lib.rs 871 //- /lib.rs
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index d3af780c4..af1ade8a1 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -166,16 +166,28 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
166 166
167#[cfg(test)] 167#[cfg(test)]
168mod tests { 168mod tests {
169 use crate::test_utils::{assert_eq_text, check_action, extract_range}; 169 use ra_syntax::SourceFile;
170 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
170 171
171 use super::*; 172 use super::*;
172 173
173 fn check_join_lines(before: &str, after: &str) { 174 fn check_join_lines(before: &str, after: &str) {
174 check_action(before, after, |file, offset| { 175 let (before_cursor_pos, before) = extract_offset(before);
175 let range = TextRange::empty(offset); 176 let file = SourceFile::parse(&before).ok().unwrap();
176 let res = join_lines(file, range); 177
177 Some(res) 178 let range = TextRange::empty(before_cursor_pos);
178 }) 179 let result = join_lines(&file, range);
180
181 let actual = {
182 let mut actual = before.to_string();
183 result.apply(&mut actual);
184 actual
185 };
186 let actual_cursor_pos = result
187 .apply_to_offset(before_cursor_pos)
188 .expect("cursor position is affected by the edit");
189 let actual = add_cursor(&actual, actual_cursor_pos);
190 assert_eq_text!(after, &actual);
179 } 191 }
180 192
181 #[test] 193 #[test]
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 78149ddfc..97ff67ee8 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -42,11 +42,6 @@ mod inlay_hints;
42mod expand_macro; 42mod expand_macro;
43mod ssr; 43mod ssr;
44 44
45#[cfg(test)]
46mod marks;
47#[cfg(test)]
48mod test_utils;
49
50use std::sync::Arc; 45use std::sync::Arc;
51 46
52use ra_cfg::CfgOptions; 47use ra_cfg::CfgOptions;
@@ -82,19 +77,19 @@ pub use crate::{
82}; 77};
83 78
84pub use hir::Documentation; 79pub use hir::Documentation;
85pub use ra_assists::AssistId; 80pub use ra_assists::{AssistConfig, AssistId};
86pub use ra_db::{ 81pub use ra_db::{
87 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
88}; 83};
89pub use ra_ide_db::{ 84pub use ra_ide_db::{
90 change::{AnalysisChange, LibraryData}, 85 change::{AnalysisChange, LibraryData},
91 line_index::{LineCol, LineIndex}, 86 line_index::{LineCol, LineIndex},
92 line_index_utils::translate_offset_with_edit,
93 search::SearchScope, 87 search::SearchScope,
94 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
95 symbol_index::Query, 89 symbol_index::Query,
96 RootDatabase, 90 RootDatabase,
97}; 91};
92pub use ra_text_edit::{Indel, TextEdit};
98 93
99pub type Cancelable<T> = Result<T, Canceled>; 94pub type Cancelable<T> = Result<T, Canceled>;
100 95
@@ -291,14 +286,10 @@ impl Analysis {
291 286
292 /// Returns an edit to remove all newlines in the range, cleaning up minor 287 /// Returns an edit to remove all newlines in the range, cleaning up minor
293 /// stuff like trailing commas. 288 /// stuff like trailing commas.
294 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { 289 pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> {
295 self.with_db(|db| { 290 self.with_db(|db| {
296 let parse = db.parse(frange.file_id); 291 let parse = db.parse(frange.file_id);
297 let file_edit = SourceFileEdit { 292 join_lines::join_lines(&parse.tree(), frange.range)
298 file_id: frange.file_id,
299 edit: join_lines::join_lines(&parse.tree(), frange.range),
300 };
301 SourceChange::source_file_edit("Join lines", file_edit)
302 }) 293 })
303 } 294 }
304 295
@@ -458,17 +449,17 @@ impl Analysis {
458 /// Computes completions at the given position. 449 /// Computes completions at the given position.
459 pub fn completions( 450 pub fn completions(
460 &self, 451 &self,
461 position: FilePosition,
462 config: &CompletionConfig, 452 config: &CompletionConfig,
453 position: FilePosition,
463 ) -> Cancelable<Option<Vec<CompletionItem>>> { 454 ) -> Cancelable<Option<Vec<CompletionItem>>> {
464 self.with_db(|db| completion::completions(db, position, config).map(Into::into)) 455 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
465 } 456 }
466 457
467 /// Computes assists (aka code actions aka intentions) for the given 458 /// Computes assists (aka code actions aka intentions) for the given
468 /// position. 459 /// position.
469 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { 460 pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> {
470 self.with_db(|db| { 461 self.with_db(|db| {
471 ra_assists::Assist::resolved(db, frange) 462 ra_assists::Assist::resolved(db, config, frange)
472 .into_iter() 463 .into_iter()
473 .map(|assist| Assist { 464 .map(|assist| Assist {
474 id: assist.assist.id, 465 id: assist.assist.id,
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
deleted file mode 100644
index 51ca4dde3..000000000
--- a/crates/ra_ide/src/marks.rs
+++ /dev/null
@@ -1,16 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks!(
4 inserts_angle_brackets_for_generics
5 inserts_parens_for_function_calls
6 call_info_bad_offset
7 dont_complete_current_use
8 test_resolve_parent_module_on_module_decl
9 search_filters_by_range
10 dont_insert_macro_call_parens_unncessary
11 self_fulfilling_completion
12 test_struct_field_completion_in_func_call
13 test_struct_field_completion_in_record_lit
14 test_rename_struct_field_for_shorthand
15 test_rename_local_for_field_shorthand
16);
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs
index aaf4460df..a083fb1eb 100644
--- a/crates/ra_ide/src/parent_module.rs
+++ b/crates/ra_ide/src/parent_module.rs
@@ -7,7 +7,7 @@ use ra_syntax::{
7 algo::find_node_at_offset, 7 algo::find_node_at_offset,
8 ast::{self, AstNode}, 8 ast::{self, AstNode},
9}; 9};
10use test_utils::tested_by; 10use test_utils::mark;
11 11
12use crate::NavigationTarget; 12use crate::NavigationTarget;
13 13
@@ -25,7 +25,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na
25 .item_list() 25 .item_list()
26 .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) 26 .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset))
27 { 27 {
28 tested_by!(test_resolve_parent_module_on_module_decl); 28 mark::hit!(test_resolve_parent_module_on_module_decl);
29 module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); 29 module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast);
30 } 30 }
31 } 31 }
@@ -57,7 +57,7 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
57mod tests { 57mod tests {
58 use ra_cfg::CfgOptions; 58 use ra_cfg::CfgOptions;
59 use ra_db::Env; 59 use ra_db::Env;
60 use test_utils::covers; 60 use test_utils::mark;
61 61
62 use crate::{ 62 use crate::{
63 mock_analysis::{analysis_and_position, MockAnalysis}, 63 mock_analysis::{analysis_and_position, MockAnalysis},
@@ -81,7 +81,7 @@ mod tests {
81 81
82 #[test] 82 #[test]
83 fn test_resolve_parent_module_on_module_decl() { 83 fn test_resolve_parent_module_on_module_decl() {
84 covers!(test_resolve_parent_module_on_module_decl); 84 mark::check!(test_resolve_parent_module_on_module_decl);
85 let (analysis, pos) = analysis_and_position( 85 let (analysis, pos) = analysis_and_position(
86 " 86 "
87 //- /lib.rs 87 //- /lib.rs
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index 074284b42..96444bf6a 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -190,8 +190,6 @@ fn get_struct_def_name_for_struct_literal_search(
190 190
191#[cfg(test)] 191#[cfg(test)]
192mod tests { 192mod tests {
193 use test_utils::covers;
194
195 use crate::{ 193 use crate::{
196 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, 194 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
197 Declaration, Reference, ReferenceSearchResult, SearchScope, 195 Declaration, Reference, ReferenceSearchResult, SearchScope,
@@ -301,7 +299,6 @@ mod tests {
301 299
302 #[test] 300 #[test]
303 fn search_filters_by_range() { 301 fn search_filters_by_range() {
304 covers!(ra_ide_db::search_filters_by_range);
305 let code = r#" 302 let code = r#"
306 fn foo() { 303 fn foo() {
307 let spam<|> = 92; 304 let spam<|> = 92;
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 410dae75c..fd2163dad 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9}; 9};
10use ra_text_edit::TextEdit; 10use ra_text_edit::TextEdit;
11use std::convert::TryInto; 11use std::convert::TryInto;
12use test_utils::tested_by; 12use test_utils::mark;
13 13
14use crate::{ 14use crate::{
15 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, 15 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
@@ -57,13 +57,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
57 let file_id = reference.file_range.file_id; 57 let file_id = reference.file_range.file_id;
58 let range = match reference.kind { 58 let range = match reference.kind {
59 ReferenceKind::FieldShorthandForField => { 59 ReferenceKind::FieldShorthandForField => {
60 tested_by!(test_rename_struct_field_for_shorthand); 60 mark::hit!(test_rename_struct_field_for_shorthand);
61 replacement_text.push_str(new_name); 61 replacement_text.push_str(new_name);
62 replacement_text.push_str(": "); 62 replacement_text.push_str(": ");
63 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 63 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
64 } 64 }
65 ReferenceKind::FieldShorthandForLocal => { 65 ReferenceKind::FieldShorthandForLocal => {
66 tested_by!(test_rename_local_for_field_shorthand); 66 mark::hit!(test_rename_local_for_field_shorthand);
67 replacement_text.push_str(": "); 67 replacement_text.push_str(": ");
68 replacement_text.push_str(new_name); 68 replacement_text.push_str(new_name);
69 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 69 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
@@ -260,7 +260,7 @@ fn rename_reference(
260mod tests { 260mod tests {
261 use insta::assert_debug_snapshot; 261 use insta::assert_debug_snapshot;
262 use ra_text_edit::TextEditBuilder; 262 use ra_text_edit::TextEditBuilder;
263 use test_utils::{assert_eq_text, covers}; 263 use test_utils::{assert_eq_text, mark};
264 264
265 use crate::{ 265 use crate::{
266 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 266 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
@@ -492,7 +492,7 @@ mod tests {
492 492
493 #[test] 493 #[test]
494 fn test_rename_struct_field_for_shorthand() { 494 fn test_rename_struct_field_for_shorthand() {
495 covers!(test_rename_struct_field_for_shorthand); 495 mark::check!(test_rename_struct_field_for_shorthand);
496 test_rename( 496 test_rename(
497 r#" 497 r#"
498 struct Foo { 498 struct Foo {
@@ -522,7 +522,7 @@ mod tests {
522 522
523 #[test] 523 #[test]
524 fn test_rename_local_for_field_shorthand() { 524 fn test_rename_local_for_field_shorthand() {
525 covers!(test_rename_local_for_field_shorthand); 525 mark::check!(test_rename_local_for_field_shorthand);
526 test_rename( 526 test_rename(
527 r#" 527 r#"
528 struct Foo { 528 struct Foo {
@@ -669,7 +669,7 @@ mod tests {
669 dst_path: "bar/foo2.rs", 669 dst_path: "bar/foo2.rs",
670 }, 670 },
671 ], 671 ],
672 cursor_position: None, 672 is_snippet: false,
673 }, 673 },
674 }, 674 },
675 ) 675 )
@@ -721,7 +721,7 @@ mod tests {
721 dst_path: "foo2/mod.rs", 721 dst_path: "foo2/mod.rs",
722 }, 722 },
723 ], 723 ],
724 cursor_position: None, 724 is_snippet: false,
725 }, 725 },
726 }, 726 },
727 ) 727 )
@@ -817,7 +817,7 @@ mod tests {
817 dst_path: "bar/foo2.rs", 817 dst_path: "bar/foo2.rs",
818 }, 818 },
819 ], 819 ],
820 cursor_position: None, 820 is_snippet: false,
821 }, 821 },
822 }, 822 },
823 ) 823 )
@@ -983,8 +983,8 @@ mod tests {
983 if let Some(change) = source_change { 983 if let Some(change) = source_change {
984 for edit in change.info.source_file_edits { 984 for edit in change.info.source_file_edits {
985 file_id = Some(edit.file_id); 985 file_id = Some(edit.file_id);
986 for indel in edit.edit.as_indels() { 986 for indel in edit.edit.into_iter() {
987 text_edit_builder.replace(indel.delete, indel.insert.clone()); 987 text_edit_builder.replace(indel.delete, indel.insert);
988 } 988 }
989 } 989 }
990 } 990 }
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index fa8a9d92c..131b8f307 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::Semantics; 3use hir::{AsAssocItem, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
@@ -65,14 +65,36 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
65 RunnableKind::Bin 65 RunnableKind::Bin
66 } else { 66 } else {
67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
68 let path = module 68 let def = sema.to_def(&fn_def)?;
69 let impl_trait_name =
70 def.as_assoc_item(sema.db).and_then(|assoc_item| {
71 match assoc_item.container(sema.db) {
72 hir::AssocItemContainer::Trait(trait_item) => {
73 Some(trait_item.name(sema.db).to_string())
74 }
75 hir::AssocItemContainer::ImplDef(impl_def) => impl_def
76 .target_ty(sema.db)
77 .as_adt()
78 .map(|adt| adt.name(sema.db).to_string()),
79 }
80 });
81
82 let path_iter = module
69 .path_to_root(sema.db) 83 .path_to_root(sema.db)
70 .into_iter() 84 .into_iter()
71 .rev() 85 .rev()
72 .filter_map(|it| it.name(sema.db)) 86 .filter_map(|it| it.name(sema.db))
73 .map(|name| name.to_string()) 87 .map(|name| name.to_string());
74 .chain(std::iter::once(name_string)) 88
75 .join("::"); 89 let path = if let Some(impl_trait_name) = impl_trait_name {
90 path_iter
91 .chain(std::iter::once(impl_trait_name))
92 .chain(std::iter::once(name_string))
93 .join("::")
94 } else {
95 path_iter.chain(std::iter::once(name_string)).join("::")
96 };
97
76 TestId::Path(path) 98 TestId::Path(path)
77 } else { 99 } else {
78 TestId::Name(name_string) 100 TestId::Name(name_string)
@@ -238,6 +260,44 @@ mod tests {
238 } 260 }
239 261
240 #[test] 262 #[test]
263 fn test_runnables_doc_test_in_impl() {
264 let (analysis, pos) = analysis_and_position(
265 r#"
266 //- /lib.rs
267 <|> //empty
268 fn main() {}
269
270 struct Data;
271 impl Data {
272 /// ```
273 /// let x = 5;
274 /// ```
275 fn foo() {}
276 }
277 "#,
278 );
279 let runnables = analysis.runnables(pos.file_id).unwrap();
280 assert_debug_snapshot!(&runnables,
281 @r###"
282 [
283 Runnable {
284 range: 1..21,
285 kind: Bin,
286 },
287 Runnable {
288 range: 51..105,
289 kind: DocTest {
290 test_id: Path(
291 "Data::foo",
292 ),
293 },
294 },
295 ]
296 "###
297 );
298 }
299
300 #[test]
241 fn test_runnables_module() { 301 fn test_runnables_module() {
242 let (analysis, pos) = analysis_and_position( 302 let (analysis, pos) = analysis_and_position(
243 r#" 303 r#"
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
deleted file mode 100644
index 48c8fd1f4..000000000
--- a/crates/ra_ide/src/test_utils.rs
+++ /dev/null
@@ -1,25 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{SourceFile, TextSize};
4use ra_text_edit::TextEdit;
5
6pub use test_utils::*;
7
8pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
9 before: &str,
10 after: &str,
11 f: F,
12) {
13 let (before_cursor_pos, before) = extract_offset(before);
14 let file = SourceFile::parse(&before).ok().unwrap();
15 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
16 let actual = {
17 let mut actual = before.to_string();
18 result.apply(&mut actual);
19 actual
20 };
21 let actual_cursor_pos =
22 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
23 let actual = add_cursor(&actual, actual_cursor_pos);
24 assert_eq_text!(after, &actual);
25}
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 6f04f0be4..cd48cad93 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -82,7 +82,6 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
82 Some(SingleFileChange { 82 Some(SingleFileChange {
83 label: "add semicolon".to_string(), 83 label: "add semicolon".to_string(),
84 edit: TextEdit::insert(offset, ";".to_string()), 84 edit: TextEdit::insert(offset, ";".to_string()),
85 cursor_position: None,
86 }) 85 })
87} 86}
88 87
@@ -111,7 +110,6 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
111 Some(SingleFileChange { 110 Some(SingleFileChange {
112 label: "reindent dot".to_string(), 111 label: "reindent dot".to_string(),
113 edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent), 112 edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
114 cursor_position: Some(offset + target_indent_len - current_indent_len + TextSize::of('.')),
115 }) 113 })
116} 114}
117 115
@@ -130,7 +128,6 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang
130 Some(SingleFileChange { 128 Some(SingleFileChange {
131 label: "add space after return type".to_string(), 129 label: "add space after return type".to_string(),
132 edit: TextEdit::insert(after_arrow, " ".to_string()), 130 edit: TextEdit::insert(after_arrow, " ".to_string()),
133 cursor_position: Some(after_arrow),
134 }) 131 })
135} 132}
136 133
@@ -140,7 +137,7 @@ mod tests {
140 137
141 use super::*; 138 use super::*;
142 139
143 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { 140 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
144 let (offset, before) = extract_offset(before); 141 let (offset, before) = extract_offset(before);
145 let edit = TextEdit::insert(offset, char_typed.to_string()); 142 let edit = TextEdit::insert(offset, char_typed.to_string());
146 let mut before = before.to_string(); 143 let mut before = before.to_string();
@@ -148,21 +145,15 @@ mod tests {
148 let parse = SourceFile::parse(&before); 145 let parse = SourceFile::parse(&before);
149 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 146 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
150 it.edit.apply(&mut before); 147 it.edit.apply(&mut before);
151 (before.to_string(), it) 148 before.to_string()
152 }) 149 })
153 } 150 }
154 151
155 fn type_char(char_typed: char, before: &str, after: &str) { 152 fn type_char(char_typed: char, before: &str, after: &str) {
156 let (actual, file_change) = do_type_char(char_typed, before) 153 let actual = do_type_char(char_typed, before)
157 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); 154 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
158 155
159 if after.contains("<|>") { 156 assert_eq_text!(after, &actual);
160 let (offset, after) = extract_offset(after);
161 assert_eq_text!(&after, &actual);
162 assert_eq!(file_change.cursor_position, Some(offset))
163 } else {
164 assert_eq_text!(after, &actual);
165 }
166 } 157 }
167 158
168 fn type_char_noop(char_typed: char, before: &str) { 159 fn type_char_noop(char_typed: char, before: &str) {
@@ -350,6 +341,6 @@ fn foo() {
350 341
351 #[test] 342 #[test]
352 fn adds_space_after_return_type() { 343 fn adds_space_after_return_type() {
353 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") 344 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }")
354 } 345 }
355} 346}
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 78a40cc94..85be14ad3 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -38,17 +38,15 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
38 } 38 }
39 39
40 let indent = node_indent(&file, comment.syntax())?; 40 let indent = node_indent(&file, comment.syntax())?;
41 let inserted = format!("\n{}{} ", indent, prefix); 41 let inserted = format!("\n{}{} $0", indent, prefix);
42 let cursor_position = position.offset + TextSize::of(&inserted);
43 let edit = TextEdit::insert(position.offset, inserted); 42 let edit = TextEdit::insert(position.offset, inserted);
44 43
45 Some( 44 let mut res = SourceChange::source_file_edit(
46 SourceChange::source_file_edit( 45 "On enter",
47 "On enter", 46 SourceFileEdit { edit, file_id: position.file_id },
48 SourceFileEdit { edit, file_id: position.file_id }, 47 );
49 ) 48 res.is_snippet = true;
50 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), 49 Some(res)
51 )
52} 50}
53 51
54fn followed_by_comment(comment: &ast::Comment) -> bool { 52fn followed_by_comment(comment: &ast::Comment) -> bool {
@@ -84,7 +82,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
84 82
85#[cfg(test)] 83#[cfg(test)]
86mod tests { 84mod tests {
87 use test_utils::{add_cursor, assert_eq_text, extract_offset}; 85 use test_utils::{assert_eq_text, extract_offset};
88 86
89 use crate::mock_analysis::single_file; 87 use crate::mock_analysis::single_file;
90 88
@@ -98,7 +96,6 @@ mod tests {
98 assert_eq!(result.source_file_edits.len(), 1); 96 assert_eq!(result.source_file_edits.len(), 1);
99 let mut actual = before.to_string(); 97 let mut actual = before.to_string();
100 result.source_file_edits[0].edit.apply(&mut actual); 98 result.source_file_edits[0].edit.apply(&mut actual);
101 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
102 Some(actual) 99 Some(actual)
103 } 100 }
104 101
@@ -121,7 +118,7 @@ fn foo() {
121", 118",
122 r" 119 r"
123/// Some docs 120/// Some docs
124/// <|> 121/// $0
125fn foo() { 122fn foo() {
126} 123}
127", 124",
@@ -137,7 +134,7 @@ impl S {
137 r" 134 r"
138impl S { 135impl S {
139 /// Some 136 /// Some
140 /// <|> docs. 137 /// $0 docs.
141 fn foo() {} 138 fn foo() {}
142} 139}
143", 140",
@@ -151,7 +148,7 @@ fn foo() {
151", 148",
152 r" 149 r"
153/// 150///
154/// <|> Some docs 151/// $0 Some docs
155fn foo() { 152fn foo() {
156} 153}
157", 154",
@@ -175,7 +172,7 @@ fn main() {
175 r" 172 r"
176fn main() { 173fn main() {
177 // Fix 174 // Fix
178 // <|> me 175 // $0 me
179 let x = 1 + 1; 176 let x = 1 + 1;
180} 177}
181", 178",
@@ -195,7 +192,7 @@ fn main() {
195 r" 192 r"
196fn main() { 193fn main() {
197 // Fix 194 // Fix
198 // <|> 195 // $0
199 // me 196 // me
200 let x = 1 + 1; 197 let x = 1 + 1;
201} 198}
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index 60c11178e..8b06cbfc5 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -14,7 +14,6 @@ use ra_syntax::{
14 ast::{self, AstNode}, 14 ast::{self, AstNode},
15 match_ast, 15 match_ast,
16}; 16};
17use test_utils::tested_by;
18 17
19use crate::RootDatabase; 18use crate::RootDatabase;
20 19
@@ -118,7 +117,6 @@ fn classify_name_inner(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Opti
118 match_ast! { 117 match_ast! {
119 match parent { 118 match parent {
120 ast::Alias(it) => { 119 ast::Alias(it) => {
121 tested_by!(goto_def_for_use_alias; force);
122 let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?; 120 let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?;
123 let path = use_tree.path()?; 121 let path = use_tree.path()?;
124 let path_segment = path.segment()?; 122 let path_segment = path.segment()?;
@@ -203,6 +201,8 @@ impl NameRefClass {
203 } 201 }
204} 202}
205 203
204// Note: we don't have unit-tests for this rather important function.
205// It is primarily exercised via goto definition tests in `ra_ide`.
206pub fn classify_name_ref( 206pub fn classify_name_ref(
207 sema: &Semantics<RootDatabase>, 207 sema: &Semantics<RootDatabase>,
208 name_ref: &ast::NameRef, 208 name_ref: &ast::NameRef,
@@ -212,22 +212,18 @@ pub fn classify_name_ref(
212 let parent = name_ref.syntax().parent()?; 212 let parent = name_ref.syntax().parent()?;
213 213
214 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { 214 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
215 tested_by!(goto_def_for_methods; force);
216 if let Some(func) = sema.resolve_method_call(&method_call) { 215 if let Some(func) = sema.resolve_method_call(&method_call) {
217 return Some(NameRefClass::Definition(Definition::ModuleDef(func.into()))); 216 return Some(NameRefClass::Definition(Definition::ModuleDef(func.into())));
218 } 217 }
219 } 218 }
220 219
221 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { 220 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
222 tested_by!(goto_def_for_fields; force);
223 if let Some(field) = sema.resolve_field(&field_expr) { 221 if let Some(field) = sema.resolve_field(&field_expr) {
224 return Some(NameRefClass::Definition(Definition::Field(field))); 222 return Some(NameRefClass::Definition(Definition::Field(field)));
225 } 223 }
226 } 224 }
227 225
228 if let Some(record_field) = ast::RecordField::for_field_name(name_ref) { 226 if let Some(record_field) = ast::RecordField::for_field_name(name_ref) {
229 tested_by!(goto_def_for_record_fields; force);
230 tested_by!(goto_def_for_field_init_shorthand; force);
231 if let Some((field, local)) = sema.resolve_record_field(&record_field) { 227 if let Some((field, local)) = sema.resolve_record_field(&record_field) {
232 let field = Definition::Field(field); 228 let field = Definition::Field(field);
233 let res = match local { 229 let res = match local {
@@ -239,7 +235,6 @@ pub fn classify_name_ref(
239 } 235 }
240 236
241 if let Some(record_field_pat) = ast::RecordFieldPat::cast(parent.clone()) { 237 if let Some(record_field_pat) = ast::RecordFieldPat::cast(parent.clone()) {
242 tested_by!(goto_def_for_record_field_pats; force);
243 if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) { 238 if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
244 let field = Definition::Field(field); 239 let field = Definition::Field(field);
245 return Some(NameRefClass::Definition(field)); 240 return Some(NameRefClass::Definition(field));
@@ -247,7 +242,6 @@ pub fn classify_name_ref(
247 } 242 }
248 243
249 if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { 244 if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
250 tested_by!(goto_def_for_macros; force);
251 if let Some(macro_def) = sema.resolve_macro_call(&macro_call) { 245 if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
252 return Some(NameRefClass::Definition(Definition::Macro(macro_def))); 246 return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
253 } 247 }
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index 52fcd7b6f..1b74e6558 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -2,9 +2,7 @@
2//! 2//!
3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. 3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
4 4
5pub mod marks;
6pub mod line_index; 5pub mod line_index;
7pub mod line_index_utils;
8pub mod symbol_index; 6pub mod symbol_index;
9pub mod change; 7pub mod change;
10pub mod defs; 8pub mod defs;
diff --git a/crates/ra_ide_db/src/line_index_utils.rs b/crates/ra_ide_db/src/line_index_utils.rs
deleted file mode 100644
index 7fa6fc448..000000000
--- a/crates/ra_ide_db/src/line_index_utils.rs
+++ /dev/null
@@ -1,302 +0,0 @@
1//! Code actions can specify desirable final position of the cursor.
2//!
3//! The position is specified as a `TextSize` in the final file. We need to send
4//! it in `(Line, Column)` coordinate though. However, we only have a LineIndex
5//! for a file pre-edit!
6//!
7//! Code in this module applies this "to (Line, Column) after edit"
8//! transformation.
9
10use std::convert::TryInto;
11
12use ra_syntax::{TextRange, TextSize};
13use ra_text_edit::{Indel, TextEdit};
14
15use crate::line_index::{LineCol, LineIndex, Utf16Char};
16
17pub fn translate_offset_with_edit(
18 line_index: &LineIndex,
19 offset: TextSize,
20 text_edit: &TextEdit,
21) -> LineCol {
22 let mut state = Edits::from_text_edit(&text_edit);
23
24 let mut res = RunningLineCol::new();
25
26 macro_rules! test_step {
27 ($x:ident) => {
28 match &$x {
29 Step::Newline(n) => {
30 if offset < *n {
31 return res.to_line_col(offset);
32 } else {
33 res.add_line(*n);
34 }
35 }
36 Step::Utf16Char(x) => {
37 if offset < x.end() {
38 // if the offset is inside a multibyte char it's invalid
39 // clamp it to the start of the char
40 let clamp = offset.min(x.start());
41 return res.to_line_col(clamp);
42 } else {
43 res.adjust_col(*x);
44 }
45 }
46 }
47 };
48 }
49
50 for orig_step in LineIndexStepIter::from(line_index) {
51 loop {
52 let translated_step = state.translate_step(&orig_step);
53 match state.next_steps(&translated_step) {
54 NextSteps::Use => {
55 test_step!(translated_step);
56 break;
57 }
58 NextSteps::ReplaceMany(ns) => {
59 for n in ns {
60 test_step!(n);
61 }
62 break;
63 }
64 NextSteps::AddMany(ns) => {
65 for n in ns {
66 test_step!(n);
67 }
68 }
69 }
70 }
71 }
72
73 loop {
74 match state.next_inserted_steps() {
75 None => break,
76 Some(ns) => {
77 for n in ns {
78 test_step!(n);
79 }
80 }
81 }
82 }
83
84 res.to_line_col(offset)
85}
86
87#[derive(Debug, Clone)]
88enum Step {
89 Newline(TextSize),
90 Utf16Char(TextRange),
91}
92
93#[derive(Debug)]
94struct LineIndexStepIter<'a> {
95 line_index: &'a LineIndex,
96 next_newline_idx: usize,
97 utf16_chars: Option<(TextSize, std::slice::Iter<'a, Utf16Char>)>,
98}
99
100impl LineIndexStepIter<'_> {
101 fn from(line_index: &LineIndex) -> LineIndexStepIter {
102 let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None };
103 // skip first newline since it's not real
104 x.next();
105 x
106 }
107}
108
109impl Iterator for LineIndexStepIter<'_> {
110 type Item = Step;
111 fn next(&mut self) -> Option<Step> {
112 self.utf16_chars
113 .as_mut()
114 .and_then(|(newline, x)| {
115 let x = x.next()?;
116 Some(Step::Utf16Char(TextRange::new(*newline + x.start, *newline + x.end)))
117 })
118 .or_else(|| {
119 let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?;
120 self.utf16_chars = self
121 .line_index
122 .utf16_lines
123 .get(&(self.next_newline_idx as u32))
124 .map(|x| (next_newline, x.iter()));
125 self.next_newline_idx += 1;
126 Some(Step::Newline(next_newline))
127 })
128 }
129}
130
131#[derive(Debug)]
132struct OffsetStepIter<'a> {
133 text: &'a str,
134 offset: TextSize,
135}
136
137impl Iterator for OffsetStepIter<'_> {
138 type Item = Step;
139 fn next(&mut self) -> Option<Step> {
140 let (next, next_offset) = self
141 .text
142 .char_indices()
143 .filter_map(|(i, c)| {
144 let i: TextSize = i.try_into().unwrap();
145 let char_len = TextSize::of(c);
146 if c == '\n' {
147 let next_offset = self.offset + i + char_len;
148 let next = Step::Newline(next_offset);
149 Some((next, next_offset))
150 } else {
151 if !c.is_ascii() {
152 let start = self.offset + i;
153 let end = start + char_len;
154 let next = Step::Utf16Char(TextRange::new(start, end));
155 let next_offset = end;
156 Some((next, next_offset))
157 } else {
158 None
159 }
160 }
161 })
162 .next()?;
163 let next_idx: usize = (next_offset - self.offset).into();
164 self.text = &self.text[next_idx..];
165 self.offset = next_offset;
166 Some(next)
167 }
168}
169
170#[derive(Debug)]
171enum NextSteps<'a> {
172 Use,
173 ReplaceMany(OffsetStepIter<'a>),
174 AddMany(OffsetStepIter<'a>),
175}
176
177#[derive(Debug)]
178struct TranslatedEdit<'a> {
179 delete: TextRange,
180 insert: &'a str,
181 diff: i64,
182}
183
184struct Edits<'a> {
185 edits: &'a [Indel],
186 current: Option<TranslatedEdit<'a>>,
187 acc_diff: i64,
188}
189
190impl<'a> Edits<'a> {
191 fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
192 let mut x = Edits { edits: text_edit.as_indels(), current: None, acc_diff: 0 };
193 x.advance_edit();
194 x
195 }
196 fn advance_edit(&mut self) {
197 self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff);
198 match self.edits.split_first() {
199 Some((next, rest)) => {
200 let delete = self.translate_range(next.delete);
201 let diff = next.insert.len() as i64 - usize::from(next.delete.len()) as i64;
202 self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff });
203 self.edits = rest;
204 }
205 None => {
206 self.current = None;
207 }
208 }
209 }
210
211 fn next_inserted_steps(&mut self) -> Option<OffsetStepIter<'a>> {
212 let cur = self.current.as_ref()?;
213 let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert });
214 self.advance_edit();
215 res
216 }
217
218 fn next_steps(&mut self, step: &Step) -> NextSteps {
219 let step_pos = match *step {
220 Step::Newline(n) => n,
221 Step::Utf16Char(r) => r.end(),
222 };
223 match &mut self.current {
224 Some(edit) => {
225 if step_pos <= edit.delete.start() {
226 NextSteps::Use
227 } else if step_pos <= edit.delete.end() {
228 let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
229 // empty slice to avoid returning steps again
230 edit.insert = &edit.insert[edit.insert.len()..];
231 NextSteps::ReplaceMany(iter)
232 } else {
233 let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
234 // empty slice to avoid returning steps again
235 edit.insert = &edit.insert[edit.insert.len()..];
236 self.advance_edit();
237 NextSteps::AddMany(iter)
238 }
239 }
240 None => NextSteps::Use,
241 }
242 }
243
244 fn translate_range(&self, range: TextRange) -> TextRange {
245 if self.acc_diff == 0 {
246 range
247 } else {
248 let start = self.translate(range.start());
249 let end = self.translate(range.end());
250 TextRange::new(start, end)
251 }
252 }
253
254 fn translate(&self, x: TextSize) -> TextSize {
255 if self.acc_diff == 0 {
256 x
257 } else {
258 TextSize::from((usize::from(x) as i64 + self.acc_diff) as u32)
259 }
260 }
261
262 fn translate_step(&self, x: &Step) -> Step {
263 if self.acc_diff == 0 {
264 x.clone()
265 } else {
266 match *x {
267 Step::Newline(n) => Step::Newline(self.translate(n)),
268 Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)),
269 }
270 }
271 }
272}
273
274#[derive(Debug)]
275struct RunningLineCol {
276 line: u32,
277 last_newline: TextSize,
278 col_adjust: TextSize,
279}
280
281impl RunningLineCol {
282 fn new() -> RunningLineCol {
283 RunningLineCol { line: 0, last_newline: TextSize::from(0), col_adjust: TextSize::from(0) }
284 }
285
286 fn to_line_col(&self, offset: TextSize) -> LineCol {
287 LineCol {
288 line: self.line,
289 col_utf16: ((offset - self.last_newline) - self.col_adjust).into(),
290 }
291 }
292
293 fn add_line(&mut self, newline: TextSize) {
294 self.line += 1;
295 self.last_newline = newline;
296 self.col_adjust = TextSize::from(0);
297 }
298
299 fn adjust_col(&mut self, range: TextRange) {
300 self.col_adjust += range.len() - TextSize::from(1);
301 }
302}
diff --git a/crates/ra_ide_db/src/marks.rs b/crates/ra_ide_db/src/marks.rs
deleted file mode 100644
index 386fe605c..000000000
--- a/crates/ra_ide_db/src/marks.rs
+++ /dev/null
@@ -1,12 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks![
4 goto_def_for_macros
5 goto_def_for_use_alias
6 goto_def_for_methods
7 goto_def_for_fields
8 goto_def_for_record_fields
9 goto_def_for_field_init_shorthand
10 goto_def_for_record_field_pats
11 search_filters_by_range
12];
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs
index b464959fc..589f44771 100644
--- a/crates/ra_ide_db/src/search.rs
+++ b/crates/ra_ide_db/src/search.rs
@@ -12,7 +12,6 @@ use ra_db::{FileId, FileRange, SourceDatabaseExt};
12use ra_prof::profile; 12use ra_prof::profile;
13use ra_syntax::{ast, match_ast, AstNode, TextRange, TextSize}; 13use ra_syntax::{ast, match_ast, AstNode, TextRange, TextSize};
14use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
15use test_utils::tested_by;
16 15
17use crate::{ 16use crate::{
18 defs::{classify_name_ref, Definition, NameRefClass}, 17 defs::{classify_name_ref, Definition, NameRefClass},
@@ -209,7 +208,6 @@ impl Definition {
209 for (idx, _) in text.match_indices(pat) { 208 for (idx, _) in text.match_indices(pat) {
210 let offset: TextSize = idx.try_into().unwrap(); 209 let offset: TextSize = idx.try_into().unwrap();
211 if !search_range.contains_inclusive(offset) { 210 if !search_range.contains_inclusive(offset) {
212 tested_by!(search_filters_by_range; force);
213 continue; 211 continue;
214 } 212 }
215 213
diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs
index af81a91a4..3484f5588 100644
--- a/crates/ra_ide_db/src/source_change.rs
+++ b/crates/ra_ide_db/src/source_change.rs
@@ -3,8 +3,8 @@
3//! 3//!
4//! It can be viewed as a dual for `AnalysisChange`. 4//! It can be viewed as a dual for `AnalysisChange`.
5 5
6use ra_db::{FileId, FilePosition, RelativePathBuf, SourceRootId}; 6use ra_db::{FileId, RelativePathBuf, SourceRootId};
7use ra_text_edit::{TextEdit, TextSize}; 7use ra_text_edit::TextEdit;
8 8
9#[derive(Debug, Clone)] 9#[derive(Debug, Clone)]
10pub struct SourceChange { 10pub struct SourceChange {
@@ -12,7 +12,7 @@ pub struct SourceChange {
12 pub label: String, 12 pub label: String,
13 pub source_file_edits: Vec<SourceFileEdit>, 13 pub source_file_edits: Vec<SourceFileEdit>,
14 pub file_system_edits: Vec<FileSystemEdit>, 14 pub file_system_edits: Vec<FileSystemEdit>,
15 pub cursor_position: Option<FilePosition>, 15 pub is_snippet: bool,
16} 16}
17 17
18impl SourceChange { 18impl SourceChange {
@@ -27,7 +27,7 @@ impl SourceChange {
27 label: label.into(), 27 label: label.into(),
28 source_file_edits, 28 source_file_edits,
29 file_system_edits, 29 file_system_edits,
30 cursor_position: None, 30 is_snippet: false,
31 } 31 }
32 } 32 }
33 33
@@ -40,7 +40,7 @@ impl SourceChange {
40 label: label, 40 label: label,
41 source_file_edits: edits, 41 source_file_edits: edits,
42 file_system_edits: vec![], 42 file_system_edits: vec![],
43 cursor_position: None, 43 is_snippet: false,
44 } 44 }
45 } 45 }
46 46
@@ -51,7 +51,7 @@ impl SourceChange {
51 label: label.into(), 51 label: label.into(),
52 source_file_edits: vec![], 52 source_file_edits: vec![],
53 file_system_edits: edits, 53 file_system_edits: edits,
54 cursor_position: None, 54 is_snippet: false,
55 } 55 }
56 } 56 }
57 57
@@ -76,18 +76,6 @@ impl SourceChange {
76 pub fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { 76 pub fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
77 SourceChange::file_system_edits(label, vec![edit]) 77 SourceChange::file_system_edits(label, vec![edit])
78 } 78 }
79
80 /// Sets the cursor position to the given `FilePosition`
81 pub fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
82 self.cursor_position = Some(cursor_position);
83 self
84 }
85
86 /// Sets the cursor position to the given `FilePosition`
87 pub fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self {
88 self.cursor_position = cursor_position;
89 self
90 }
91} 79}
92 80
93#[derive(Debug, Clone)] 81#[derive(Debug, Clone)]
@@ -105,7 +93,6 @@ pub enum FileSystemEdit {
105pub struct SingleFileChange { 93pub struct SingleFileChange {
106 pub label: String, 94 pub label: String,
107 pub edit: TextEdit, 95 pub edit: TextEdit,
108 pub cursor_position: Option<TextSize>,
109} 96}
110 97
111impl SingleFileChange { 98impl SingleFileChange {
@@ -114,7 +101,7 @@ impl SingleFileChange {
114 label: self.label, 101 label: self.label,
115 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], 102 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
116 file_system_edits: Vec::new(), 103 file_system_edits: Vec::new(),
117 cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), 104 is_snippet: false,
118 } 105 }
119 } 106 }
120} 107}
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index 2a8dac757..664894d1f 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -266,6 +266,15 @@ impl<'a> SyntaxRewriter<'a> {
266 let replacement = Replacement::Single(with.clone().into()); 266 let replacement = Replacement::Single(with.clone().into());
267 self.replacements.insert(what, replacement); 267 self.replacements.insert(what, replacement);
268 } 268 }
269 pub fn replace_with_many<T: Clone + Into<SyntaxElement>>(
270 &mut self,
271 what: &T,
272 with: Vec<SyntaxElement>,
273 ) {
274 let what = what.clone().into();
275 let replacement = Replacement::Many(with);
276 self.replacements.insert(what, replacement);
277 }
269 pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) { 278 pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) {
270 self.replace(what.syntax(), with.syntax()) 279 self.replace(what.syntax(), with.syntax())
271 } 280 }
@@ -302,31 +311,41 @@ impl<'a> SyntaxRewriter<'a> {
302 311
303 fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { 312 fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode {
304 // FIXME: this could be made much faster. 313 // FIXME: this could be made much faster.
305 let new_children = 314 let mut new_children = Vec::new();
306 node.children_with_tokens().flat_map(|it| self.rewrite_self(&it)).collect::<Vec<_>>(); 315 for child in node.children_with_tokens() {
316 self.rewrite_self(&mut new_children, &child);
317 }
307 with_children(node, new_children) 318 with_children(node, new_children)
308 } 319 }
309 320
310 fn rewrite_self( 321 fn rewrite_self(
311 &self, 322 &self,
323 acc: &mut Vec<NodeOrToken<rowan::GreenNode, rowan::GreenToken>>,
312 element: &SyntaxElement, 324 element: &SyntaxElement,
313 ) -> Option<NodeOrToken<rowan::GreenNode, rowan::GreenToken>> { 325 ) {
314 if let Some(replacement) = self.replacement(&element) { 326 if let Some(replacement) = self.replacement(&element) {
315 return match replacement { 327 match replacement {
316 Replacement::Single(NodeOrToken::Node(it)) => { 328 Replacement::Single(NodeOrToken::Node(it)) => {
317 Some(NodeOrToken::Node(it.green().clone())) 329 acc.push(NodeOrToken::Node(it.green().clone()))
318 } 330 }
319 Replacement::Single(NodeOrToken::Token(it)) => { 331 Replacement::Single(NodeOrToken::Token(it)) => {
320 Some(NodeOrToken::Token(it.green().clone())) 332 acc.push(NodeOrToken::Token(it.green().clone()))
321 } 333 }
322 Replacement::Delete => None, 334 Replacement::Many(replacements) => {
335 acc.extend(replacements.iter().map(|it| match it {
336 NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
337 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
338 }))
339 }
340 Replacement::Delete => (),
323 }; 341 };
342 return;
324 } 343 }
325 let res = match element { 344 let res = match element {
326 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), 345 NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
327 NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()), 346 NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()),
328 }; 347 };
329 Some(res) 348 acc.push(res)
330 } 349 }
331} 350}
332 351
@@ -341,6 +360,7 @@ impl ops::AddAssign for SyntaxRewriter<'_> {
341enum Replacement { 360enum Replacement {
342 Delete, 361 Delete,
343 Single(SyntaxElement), 362 Single(SyntaxElement),
363 Many(Vec<SyntaxElement>),
344} 364}
345 365
346fn with_children( 366fn with_children(
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 24a1e1d91..29eb3fcb9 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -1,7 +1,10 @@
1//! This module contains functions for editing syntax trees. As the trees are 1//! This module contains functions for editing syntax trees. As the trees are
2//! immutable, all function here return a fresh copy of the tree, instead of 2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification. 3//! doing an in-place modification.
4use std::{iter, ops::RangeInclusive}; 4use std::{
5 fmt, iter,
6 ops::{self, RangeInclusive},
7};
5 8
6use arrayvec::ArrayVec; 9use arrayvec::ArrayVec;
7 10
@@ -437,6 +440,28 @@ impl From<u8> for IndentLevel {
437 } 440 }
438} 441}
439 442
443impl fmt::Display for IndentLevel {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 let spaces = " ";
446 let buf;
447 let len = self.0 as usize * 4;
448 let indent = if len <= spaces.len() {
449 &spaces[..len]
450 } else {
451 buf = iter::repeat(' ').take(len).collect::<String>();
452 &buf
453 };
454 fmt::Display::fmt(indent, f)
455 }
456}
457
458impl ops::Add<u8> for IndentLevel {
459 type Output = IndentLevel;
460 fn add(self, rhs: u8) -> IndentLevel {
461 IndentLevel(self.0 + rhs)
462 }
463}
464
440impl IndentLevel { 465impl IndentLevel {
441 pub fn from_node(node: &SyntaxNode) -> IndentLevel { 466 pub fn from_node(node: &SyntaxNode) -> IndentLevel {
442 let first_token = match node.first_token() { 467 let first_token = match node.first_token() {
@@ -453,6 +478,14 @@ impl IndentLevel {
453 IndentLevel(0) 478 IndentLevel(0)
454 } 479 }
455 480
481 /// XXX: this intentionally doesn't change the indent of the very first token.
482 /// Ie, in something like
483 /// ```
484 /// fn foo() {
485 /// 92
486 /// }
487 /// ```
488 /// if you indent the block, the `{` token would stay put.
456 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { 489 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
457 let mut rewriter = SyntaxRewriter::default(); 490 let mut rewriter = SyntaxRewriter::default();
458 node.descendants_with_tokens() 491 node.descendants_with_tokens()
@@ -463,12 +496,7 @@ impl IndentLevel {
463 text.contains('\n') 496 text.contains('\n')
464 }) 497 })
465 .for_each(|ws| { 498 .for_each(|ws| {
466 let new_ws = make::tokens::whitespace(&format!( 499 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,));
467 "{}{:width$}",
468 ws.syntax().text(),
469 "",
470 width = self.0 as usize * 4
471 ));
472 rewriter.replace(ws.syntax(), &new_ws) 500 rewriter.replace(ws.syntax(), &new_ws)
473 }); 501 });
474 rewriter.rewrite(&node) 502 rewriter.rewrite(&node)
@@ -485,7 +513,7 @@ impl IndentLevel {
485 }) 513 })
486 .for_each(|ws| { 514 .for_each(|ws| {
487 let new_ws = make::tokens::whitespace( 515 let new_ws = make::tokens::whitespace(
488 &ws.syntax().text().replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"), 516 &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
489 ); 517 );
490 rewriter.replace(ws.syntax(), &new_ws) 518 rewriter.replace(ws.syntax(), &new_ws)
491 }); 519 });
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index d0e960fb4..da0eb0926 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -1,5 +1,9 @@
1//! This module contains free-standing functions for creating AST fragments out 1//! This module contains free-standing functions for creating AST fragments out
2//! of smaller pieces. 2//! of smaller pieces.
3//!
4//! Note that all functions here intended to be stupid constructors, which just
5//! assemble a finish node from immediate children. If you want to do something
6//! smarter than that, it probably doesn't belong in this module.
3use itertools::Itertools; 7use itertools::Itertools;
4use stdx::format_to; 8use stdx::format_to;
5 9
@@ -95,6 +99,9 @@ pub fn expr_empty_block() -> ast::Expr {
95pub fn expr_unimplemented() -> ast::Expr { 99pub fn expr_unimplemented() -> ast::Expr {
96 expr_from_text("unimplemented!()") 100 expr_from_text("unimplemented!()")
97} 101}
102pub fn expr_unreachable() -> ast::Expr {
103 expr_from_text("unreachable!()")
104}
98pub fn expr_todo() -> ast::Expr { 105pub fn expr_todo() -> ast::Expr {
99 expr_from_text("todo!()") 106 expr_from_text("todo!()")
100} 107}
@@ -264,10 +271,6 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken {
264 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) 271 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
265} 272}
266 273
267pub fn unreachable_macro_call() -> ast::MacroCall {
268 ast_from_text(&format!("unreachable!()"))
269}
270
271pub fn param(name: String, ty: String) -> ast::Param { 274pub fn param(name: String, ty: String) -> ast::Param {
272 ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) 275 ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty))
273} 276}
@@ -277,7 +280,12 @@ pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList
277 ast_from_text(&format!("fn f({}) {{ }}", args)) 280 ast_from_text(&format!("fn f({}) {{ }}", args))
278} 281}
279 282
283pub fn visibility_pub_crate() -> ast::Visibility {
284 ast_from_text("pub(crate) struct S")
285}
286
280pub fn fn_def( 287pub fn fn_def(
288 visibility: Option<ast::Visibility>,
281 fn_name: ast::Name, 289 fn_name: ast::Name,
282 type_params: Option<ast::TypeParamList>, 290 type_params: Option<ast::TypeParamList>,
283 params: ast::ParamList, 291 params: ast::ParamList,
@@ -285,21 +293,11 @@ pub fn fn_def(
285) -> ast::FnDef { 293) -> ast::FnDef {
286 let type_params = 294 let type_params =
287 if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() }; 295 if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
288 ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) 296 let visibility = match visibility {
289} 297 None => String::new(),
290 298 Some(it) => format!("{} ", it),
291pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { 299 };
292 let newlines = "\n".repeat(amount_of_newlines); 300 ast_from_text(&format!("{}fn {}{}{} {}", visibility, fn_name, type_params, params, body))
293 ast_from_text(&format!("{}{}", newlines, t.syntax()))
294}
295
296pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
297 let newlines = "\n".repeat(amount_of_newlines);
298 ast_from_text(&format!("{}{}", t.syntax(), newlines))
299}
300
301pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef {
302 ast_from_text(&format!("pub(crate) {}", fn_def))
303} 301}
304 302
305fn ast_from_text<N: AstNode>(text: &str) -> N { 303fn ast_from_text<N: AstNode>(text: &str) -> N {
diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs
index c4f945101..25554f583 100644
--- a/crates/ra_text_edit/src/lib.rs
+++ b/crates/ra_text_edit/src/lib.rs
@@ -3,6 +3,7 @@
3//! `rust-analyzer` never mutates text itself and only sends diffs to clients, 3//! `rust-analyzer` never mutates text itself and only sends diffs to clients,
4//! so `TextEdit` is the ultimate representation of the work done by 4//! so `TextEdit` is the ultimate representation of the work done by
5//! rust-analyzer. 5//! rust-analyzer.
6use std::{slice, vec};
6 7
7pub use text_size::{TextRange, TextSize}; 8pub use text_size::{TextRange, TextSize};
8 9
@@ -16,7 +17,7 @@ pub struct Indel {
16 pub delete: TextRange, 17 pub delete: TextRange,
17} 18}
18 19
19#[derive(Debug, Clone)] 20#[derive(Default, Debug, Clone)]
20pub struct TextEdit { 21pub struct TextEdit {
21 indels: Vec<Indel>, 22 indels: Vec<Indel>,
22} 23}
@@ -63,25 +64,24 @@ impl TextEdit {
63 builder.finish() 64 builder.finish()
64 } 65 }
65 66
66 pub(crate) fn from_indels(mut indels: Vec<Indel>) -> TextEdit { 67 pub fn len(&self) -> usize {
67 indels.sort_by_key(|a| (a.delete.start(), a.delete.end())); 68 self.indels.len()
68 for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) {
69 assert!(a1.delete.end() <= a2.delete.start())
70 }
71 TextEdit { indels }
72 } 69 }
73 70
74 pub fn is_empty(&self) -> bool { 71 pub fn is_empty(&self) -> bool {
75 self.indels.is_empty() 72 self.indels.is_empty()
76 } 73 }
77 74
78 // FXME: impl IntoIter instead 75 pub fn iter(&self) -> slice::Iter<'_, Indel> {
79 pub fn as_indels(&self) -> &[Indel] { 76 self.indels.iter()
80 &self.indels 77 }
78
79 pub fn into_iter(self) -> vec::IntoIter<Indel> {
80 self.indels.into_iter()
81 } 81 }
82 82
83 pub fn apply(&self, text: &mut String) { 83 pub fn apply(&self, text: &mut String) {
84 match self.indels.len() { 84 match self.len() {
85 0 => return, 85 0 => return,
86 1 => { 86 1 => {
87 self.indels[0].apply(text); 87 self.indels[0].apply(text);
@@ -114,6 +114,17 @@ impl TextEdit {
114 *text = buf 114 *text = buf
115 } 115 }
116 116
117 pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> {
118 // FIXME: can be done without allocating intermediate vector
119 let mut all = self.iter().chain(other.iter()).collect::<Vec<_>>();
120 if !check_disjoint(&mut all) {
121 return Err(other);
122 }
123 self.indels.extend(other.indels);
124 assert!(check_disjoint(&mut self.indels));
125 Ok(())
126 }
127
117 pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> { 128 pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
118 let mut res = offset; 129 let mut res = offset;
119 for indel in self.indels.iter() { 130 for indel in self.indels.iter() {
@@ -141,9 +152,19 @@ impl TextEditBuilder {
141 self.indels.push(Indel::insert(offset, text)) 152 self.indels.push(Indel::insert(offset, text))
142 } 153 }
143 pub fn finish(self) -> TextEdit { 154 pub fn finish(self) -> TextEdit {
144 TextEdit::from_indels(self.indels) 155 let mut indels = self.indels;
156 assert!(check_disjoint(&mut indels));
157 TextEdit { indels }
145 } 158 }
146 pub fn invalidates_offset(&self, offset: TextSize) -> bool { 159 pub fn invalidates_offset(&self, offset: TextSize) -> bool {
147 self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset)) 160 self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset))
148 } 161 }
149} 162}
163
164fn check_disjoint(indels: &mut [impl std::borrow::Borrow<Indel>]) -> bool {
165 indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end()));
166 indels
167 .iter()
168 .zip(indels.iter().skip(1))
169 .all(|(l, r)| l.borrow().delete.end() <= r.borrow().delete.start())
170}
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 4effc82ca..780fc9317 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,8 +1,6 @@
1//! Advertizes the capabilities of the LSP Server. 1//! Advertizes the capabilities of the LSP Server.
2use std::env; 2use std::env;
3 3
4use crate::semantic_tokens;
5
6use lsp_types::{ 4use lsp_types::{
7 CallHierarchyServerCapability, ClientCapabilities, CodeActionOptions, 5 CallHierarchyServerCapability, ClientCapabilities, CodeActionOptions,
8 CodeActionProviderCapability, CodeLensOptions, CompletionOptions, 6 CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
@@ -13,6 +11,9 @@ use lsp_types::{
13 TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, 11 TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
14 WorkDoneProgressOptions, 12 WorkDoneProgressOptions,
15}; 13};
14use serde_json::json;
15
16use crate::semantic_tokens;
16 17
17pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities { 18pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities {
18 let code_action_provider = code_action_capabilities(client_caps); 19 let code_action_provider = code_action_capabilities(client_caps);
@@ -81,7 +82,10 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
81 } 82 }
82 .into(), 83 .into(),
83 ), 84 ),
84 experimental: Default::default(), 85 experimental: Some(json!({
86 "joinLines": true,
87 "ssr": true,
88 })),
85 } 89 }
86} 90}
87 91
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 6147ae207..b20efe98d 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -105,7 +105,7 @@ pub fn analysis_bench(
105 if is_completion { 105 if is_completion {
106 let options = CompletionConfig::default(); 106 let options = CompletionConfig::default();
107 let res = do_work(&mut host, file_id, |analysis| { 107 let res = do_work(&mut host, file_id, |analysis| {
108 analysis.completions(file_position, &options) 108 analysis.completions(&options, file_position)
109 }); 109 });
110 if verbosity.is_verbose() { 110 if verbosity.is_verbose() {
111 println!("\n{:#?}", res); 111 println!("\n{:#?}", res);
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index b5dc6f0fa..d75c48597 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf};
11 11
12use lsp_types::ClientCapabilities; 12use lsp_types::ClientCapabilities;
13use ra_flycheck::FlycheckConfig; 13use ra_flycheck::FlycheckConfig;
14use ra_ide::{CompletionConfig, InlayHintsConfig}; 14use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
15use ra_project_model::CargoConfig; 15use ra_project_model::CargoConfig;
16use serde::Deserialize; 16use serde::Deserialize;
17 17
@@ -32,6 +32,7 @@ pub struct Config {
32 32
33 pub inlay_hints: InlayHintsConfig, 33 pub inlay_hints: InlayHintsConfig,
34 pub completion: CompletionConfig, 34 pub completion: CompletionConfig,
35 pub assist: AssistConfig,
35 pub call_info_full: bool, 36 pub call_info_full: bool,
36 pub lens: LensConfig, 37 pub lens: LensConfig,
37} 38}
@@ -136,6 +137,7 @@ impl Default for Config {
136 add_call_argument_snippets: true, 137 add_call_argument_snippets: true,
137 ..CompletionConfig::default() 138 ..CompletionConfig::default()
138 }, 139 },
140 assist: AssistConfig::default(),
139 call_info_full: true, 141 call_info_full: true,
140 lens: LensConfig::default(), 142 lens: LensConfig::default(),
141 } 143 }
@@ -273,6 +275,7 @@ impl Config {
273 { 275 {
274 self.client_caps.code_action_literals = value; 276 self.client_caps.code_action_literals = value;
275 } 277 }
278
276 self.completion.allow_snippets(false); 279 self.completion.allow_snippets(false);
277 if let Some(completion) = &doc_caps.completion { 280 if let Some(completion) = &doc_caps.completion {
278 if let Some(completion_item) = &completion.completion_item { 281 if let Some(completion_item) = &completion.completion_item {
@@ -288,5 +291,12 @@ impl Config {
288 self.client_caps.work_done_progress = value; 291 self.client_caps.work_done_progress = value;
289 } 292 }
290 } 293 }
294
295 self.assist.allow_snippets(false);
296 if let Some(experimental) = &caps.experimental {
297 let enable =
298 experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true);
299 self.assist.allow_snippets(enable);
300 }
291 } 301 }
292} 302}
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 4bdd45a7d..25856c543 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -3,9 +3,11 @@ pub(crate) mod to_proto;
3 3
4use std::{collections::HashMap, sync::Arc}; 4use std::{collections::HashMap, sync::Arc};
5 5
6use lsp_types::{CodeActionOrCommand, Diagnostic, Range}; 6use lsp_types::{Diagnostic, Range};
7use ra_ide::FileId; 7use ra_ide::FileId;
8 8
9use crate::lsp_ext;
10
9pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; 11pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>;
10 12
11#[derive(Debug, Default, Clone)] 13#[derive(Debug, Default, Clone)]
@@ -18,13 +20,13 @@ pub struct DiagnosticCollection {
18#[derive(Debug, Clone)] 20#[derive(Debug, Clone)]
19pub struct Fix { 21pub struct Fix {
20 pub range: Range, 22 pub range: Range,
21 pub action: CodeActionOrCommand, 23 pub action: lsp_ext::CodeAction,
22} 24}
23 25
24#[derive(Debug)] 26#[derive(Debug)]
25pub enum DiagnosticTask { 27pub enum DiagnosticTask {
26 ClearCheck, 28 ClearCheck,
27 AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>), 29 AddCheck(FileId, Diagnostic, Vec<lsp_ext::CodeAction>),
28 SetNative(FileId, Vec<Diagnostic>), 30 SetNative(FileId, Vec<Diagnostic>),
29} 31}
30 32
@@ -38,7 +40,7 @@ impl DiagnosticCollection {
38 &mut self, 40 &mut self,
39 file_id: FileId, 41 file_id: FileId,
40 diagnostic: Diagnostic, 42 diagnostic: Diagnostic,
41 fixes: Vec<CodeActionOrCommand>, 43 fixes: Vec<lsp_ext::CodeAction>,
42 ) { 44 ) {
43 let diagnostics = self.check.entry(file_id).or_default(); 45 let diagnostics = self.check.entry(file_id).or_default();
44 for existing_diagnostic in diagnostics.iter() { 46 for existing_diagnostic in diagnostics.iter() {
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap
index 076b3cf27..96466b5c9 100644
--- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap
@@ -68,9 +68,9 @@ expression: diag
68 kind: Some( 68 kind: Some(
69 "quickfix", 69 "quickfix",
70 ), 70 ),
71 diagnostics: None, 71 command: None,
72 edit: Some( 72 edit: Some(
73 WorkspaceEdit { 73 SnippetWorkspaceEdit {
74 changes: Some( 74 changes: Some(
75 { 75 {
76 "file:///test/src/main.rs": [ 76 "file:///test/src/main.rs": [
@@ -106,8 +106,6 @@ expression: diag
106 document_changes: None, 106 document_changes: None,
107 }, 107 },
108 ), 108 ),
109 command: None,
110 is_preferred: None,
111 }, 109 },
112 ], 110 ],
113 }, 111 },
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
index 69138c15b..8f962277f 100644
--- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
@@ -53,9 +53,9 @@ expression: diag
53 kind: Some( 53 kind: Some(
54 "quickfix", 54 "quickfix",
55 ), 55 ),
56 diagnostics: None, 56 command: None,
57 edit: Some( 57 edit: Some(
58 WorkspaceEdit { 58 SnippetWorkspaceEdit {
59 changes: Some( 59 changes: Some(
60 { 60 {
61 "file:///test/driver/subcommand/repl.rs": [ 61 "file:///test/driver/subcommand/repl.rs": [
@@ -78,8 +78,6 @@ expression: diag
78 document_changes: None, 78 document_changes: None,
79 }, 79 },
80 ), 80 ),
81 command: None,
82 is_preferred: None,
83 }, 81 },
84 ], 82 ],
85 }, 83 },
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index eabf4908f..afea59525 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -7,13 +7,13 @@ use std::{
7}; 7};
8 8
9use lsp_types::{ 9use lsp_types::{
10 CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, 10 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
11 Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, 11 NumberOrString, Position, Range, TextEdit, Url,
12}; 12};
13use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; 13use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion};
14use stdx::format_to; 14use stdx::format_to;
15 15
16use crate::Result; 16use crate::{lsp_ext, Result};
17 17
18/// Converts a Rust level string to a LSP severity 18/// Converts a Rust level string to a LSP severity
19fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { 19fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
@@ -110,7 +110,7 @@ fn is_deprecated(rd: &ra_flycheck::Diagnostic) -> bool {
110 110
111enum MappedRustChildDiagnostic { 111enum MappedRustChildDiagnostic {
112 Related(DiagnosticRelatedInformation), 112 Related(DiagnosticRelatedInformation),
113 SuggestedFix(CodeAction), 113 SuggestedFix(lsp_ext::CodeAction),
114 MessageLine(String), 114 MessageLine(String),
115} 115}
116 116
@@ -143,13 +143,15 @@ fn map_rust_child_diagnostic(
143 message: rd.message.clone(), 143 message: rd.message.clone(),
144 }) 144 })
145 } else { 145 } else {
146 MappedRustChildDiagnostic::SuggestedFix(CodeAction { 146 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction {
147 title: rd.message.clone(), 147 title: rd.message.clone(),
148 kind: Some("quickfix".to_string()), 148 kind: Some("quickfix".to_string()),
149 diagnostics: None, 149 edit: Some(lsp_ext::SnippetWorkspaceEdit {
150 edit: Some(WorkspaceEdit::new(edit_map)), 150 // FIXME: there's no good reason to use edit_map here....
151 changes: Some(edit_map),
152 document_changes: None,
153 }),
151 command: None, 154 command: None,
152 is_preferred: None,
153 }) 155 })
154 } 156 }
155} 157}
@@ -158,7 +160,7 @@ fn map_rust_child_diagnostic(
158pub(crate) struct MappedRustDiagnostic { 160pub(crate) struct MappedRustDiagnostic {
159 pub location: Location, 161 pub location: Location,
160 pub diagnostic: Diagnostic, 162 pub diagnostic: Diagnostic,
161 pub fixes: Vec<CodeAction>, 163 pub fixes: Vec<lsp_ext::CodeAction>,
162} 164}
163 165
164/// Converts a Rust root diagnostic to LSP form 166/// Converts a Rust root diagnostic to LSP form
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 313a8c769..0fd60caf4 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -1,6 +1,6 @@
1//! rust-analyzer extensions to the LSP. 1//! rust-analyzer extensions to the LSP.
2 2
3use std::path::PathBuf; 3use std::{collections::HashMap, path::PathBuf};
4 4
5use lsp_types::request::Request; 5use lsp_types::request::Request;
6use lsp_types::{Location, Position, Range, TextDocumentIdentifier}; 6use lsp_types::{Location, Position, Range, TextDocumentIdentifier};
@@ -87,22 +87,22 @@ pub enum JoinLines {}
87 87
88impl Request for JoinLines { 88impl Request for JoinLines {
89 type Params = JoinLinesParams; 89 type Params = JoinLinesParams;
90 type Result = SourceChange; 90 type Result = Vec<lsp_types::TextEdit>;
91 const METHOD: &'static str = "rust-analyzer/joinLines"; 91 const METHOD: &'static str = "experimental/joinLines";
92} 92}
93 93
94#[derive(Deserialize, Serialize, Debug)] 94#[derive(Deserialize, Serialize, Debug)]
95#[serde(rename_all = "camelCase")] 95#[serde(rename_all = "camelCase")]
96pub struct JoinLinesParams { 96pub struct JoinLinesParams {
97 pub text_document: TextDocumentIdentifier, 97 pub text_document: TextDocumentIdentifier,
98 pub range: Range, 98 pub ranges: Vec<Range>,
99} 99}
100 100
101pub enum OnEnter {} 101pub enum OnEnter {}
102 102
103impl Request for OnEnter { 103impl Request for OnEnter {
104 type Params = lsp_types::TextDocumentPositionParams; 104 type Params = lsp_types::TextDocumentPositionParams;
105 type Result = Option<SourceChange>; 105 type Result = Option<SnippetWorkspaceEdit>;
106 const METHOD: &'static str = "rust-analyzer/onEnter"; 106 const METHOD: &'static str = "rust-analyzer/onEnter";
107} 107}
108 108
@@ -137,7 +137,7 @@ pub struct Runnable {
137#[serde(rename_all = "camelCase")] 137#[serde(rename_all = "camelCase")]
138pub struct SourceChange { 138pub struct SourceChange {
139 pub label: String, 139 pub label: String,
140 pub workspace_edit: lsp_types::WorkspaceEdit, 140 pub workspace_edit: SnippetWorkspaceEdit,
141 pub cursor_position: Option<lsp_types::TextDocumentPositionParams>, 141 pub cursor_position: Option<lsp_types::TextDocumentPositionParams>,
142} 142}
143 143
@@ -173,8 +173,8 @@ pub enum Ssr {}
173 173
174impl Request for Ssr { 174impl Request for Ssr {
175 type Params = SsrParams; 175 type Params = SsrParams;
176 type Result = SourceChange; 176 type Result = lsp_types::WorkspaceEdit;
177 const METHOD: &'static str = "rust-analyzer/ssr"; 177 const METHOD: &'static str = "experimental/ssr";
178} 178}
179 179
180#[derive(Debug, Deserialize, Serialize)] 180#[derive(Debug, Deserialize, Serialize)]
@@ -183,3 +183,54 @@ pub struct SsrParams {
183 pub query: String, 183 pub query: String,
184 pub parse_only: bool, 184 pub parse_only: bool,
185} 185}
186
187pub enum CodeActionRequest {}
188
189impl Request for CodeActionRequest {
190 type Params = lsp_types::CodeActionParams;
191 type Result = Option<Vec<CodeAction>>;
192 const METHOD: &'static str = "textDocument/codeAction";
193}
194
195#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
196pub struct CodeAction {
197 pub title: String,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub kind: Option<String>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub command: Option<lsp_types::Command>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub edit: Option<SnippetWorkspaceEdit>,
204}
205
206#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
207#[serde(rename_all = "camelCase")]
208pub struct SnippetWorkspaceEdit {
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>,
213}
214
215#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
216#[serde(untagged, rename_all = "lowercase")]
217pub enum SnippetDocumentChangeOperation {
218 Op(lsp_types::ResourceOp),
219 Edit(SnippetTextDocumentEdit),
220}
221
222#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
223#[serde(rename_all = "camelCase")]
224pub struct SnippetTextDocumentEdit {
225 pub text_document: lsp_types::VersionedTextDocumentIdentifier,
226 pub edits: Vec<SnippetTextEdit>,
227}
228
229#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
230#[serde(rename_all = "camelCase")]
231pub struct SnippetTextEdit {
232 pub range: Range,
233 pub new_text: String,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub insert_text_format: Option<lsp_types::InsertTextFormat>,
236}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 15e5bb354..87795fffb 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -518,6 +518,7 @@ fn on_request(
518 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)? 518 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)?
519 .on::<lsp_ext::Runnables>(handlers::handle_runnables)? 519 .on::<lsp_ext::Runnables>(handlers::handle_runnables)?
520 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)? 520 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
521 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
521 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? 522 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
522 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? 523 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
523 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 524 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
@@ -525,7 +526,6 @@ fn on_request(
525 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)? 526 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)?
526 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)? 527 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)?
527 .on::<lsp_types::request::Completion>(handlers::handle_completion)? 528 .on::<lsp_types::request::Completion>(handlers::handle_completion)?
528 .on::<lsp_types::request::CodeActionRequest>(handlers::handle_code_action)?
529 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)? 529 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)?
530 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)? 530 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
531 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)? 531 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index e67556752..25e660bd5 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -11,15 +11,15 @@ use lsp_server::ErrorCode;
11use lsp_types::{ 11use lsp_types::{
12 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, 12 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
13 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, 13 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
14 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, 14 CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
15 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, 15 DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location,
16 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, 16 MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams,
17 Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams, 17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
18 SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, 18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit,
19 TextEdit, Url, WorkspaceEdit,
20}; 19};
21use ra_ide::{ 20use ra_ide::{
22 Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, 21 Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
22 TextEdit,
23}; 23};
24use ra_prof::profile; 24use ra_prof::profile;
25use ra_project_model::TargetKind; 25use ra_project_model::TargetKind;
@@ -150,22 +150,35 @@ pub fn handle_find_matching_brace(
150pub fn handle_join_lines( 150pub fn handle_join_lines(
151 world: WorldSnapshot, 151 world: WorldSnapshot,
152 params: lsp_ext::JoinLinesParams, 152 params: lsp_ext::JoinLinesParams,
153) -> Result<lsp_ext::SourceChange> { 153) -> Result<Vec<lsp_types::TextEdit>> {
154 let _p = profile("handle_join_lines"); 154 let _p = profile("handle_join_lines");
155 let frange = from_proto::file_range(&world, params.text_document, params.range)?; 155 let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
156 let source_change = world.analysis().join_lines(frange)?; 156 let line_index = world.analysis().file_line_index(file_id)?;
157 to_proto::source_change(&world, source_change) 157 let line_endings = world.file_line_endings(file_id);
158 let mut res = TextEdit::default();
159 for range in params.ranges {
160 let range = from_proto::text_range(&line_index, range);
161 let edit = world.analysis().join_lines(FileRange { file_id, range })?;
162 match res.union(edit) {
163 Ok(()) => (),
164 Err(_edit) => {
165 // just ignore overlapping edits
166 }
167 }
168 }
169 let res = to_proto::text_edit_vec(&line_index, line_endings, res);
170 Ok(res)
158} 171}
159 172
160pub fn handle_on_enter( 173pub fn handle_on_enter(
161 world: WorldSnapshot, 174 world: WorldSnapshot,
162 params: lsp_types::TextDocumentPositionParams, 175 params: lsp_types::TextDocumentPositionParams,
163) -> Result<Option<lsp_ext::SourceChange>> { 176) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
164 let _p = profile("handle_on_enter"); 177 let _p = profile("handle_on_enter");
165 let position = from_proto::file_position(&world, params)?; 178 let position = from_proto::file_position(&world, params)?;
166 match world.analysis().on_enter(position)? { 179 match world.analysis().on_enter(position)? {
167 None => Ok(None), 180 None => Ok(None),
168 Some(source_change) => to_proto::source_change(&world, source_change).map(Some), 181 Some(source_change) => to_proto::snippet_workspace_edit(&world, source_change).map(Some),
169 } 182 }
170} 183}
171 184
@@ -173,7 +186,7 @@ pub fn handle_on_enter(
173pub fn handle_on_type_formatting( 186pub fn handle_on_type_formatting(
174 world: WorldSnapshot, 187 world: WorldSnapshot,
175 params: lsp_types::DocumentOnTypeFormattingParams, 188 params: lsp_types::DocumentOnTypeFormattingParams,
176) -> Result<Option<Vec<TextEdit>>> { 189) -> Result<Option<Vec<lsp_types::TextEdit>>> {
177 let _p = profile("handle_on_type_formatting"); 190 let _p = profile("handle_on_type_formatting");
178 let mut position = from_proto::file_position(&world, params.text_document_position)?; 191 let mut position = from_proto::file_position(&world, params.text_document_position)?;
179 let line_index = world.analysis().file_line_index(position.file_id)?; 192 let line_index = world.analysis().file_line_index(position.file_id)?;
@@ -476,7 +489,7 @@ pub fn handle_completion(
476 return Ok(None); 489 return Ok(None);
477 } 490 }
478 491
479 let items = match world.analysis().completions(position, &world.config.completion)? { 492 let items = match world.analysis().completions(&world.config.completion, position)? {
480 None => return Ok(None), 493 None => return Ok(None),
481 Some(items) => items, 494 Some(items) => items,
482 }; 495 };
@@ -585,9 +598,8 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Optio
585 None => return Ok(None), 598 None => return Ok(None),
586 Some(it) => it.info, 599 Some(it) => it.info,
587 }; 600 };
588 601 let workspace_edit = to_proto::workspace_edit(&world, source_change)?;
589 let source_change = to_proto::source_change(&world, source_change)?; 602 Ok(Some(workspace_edit))
590 Ok(Some(source_change.workspace_edit))
591} 603}
592 604
593pub fn handle_references( 605pub fn handle_references(
@@ -620,7 +632,7 @@ pub fn handle_references(
620pub fn handle_formatting( 632pub fn handle_formatting(
621 world: WorldSnapshot, 633 world: WorldSnapshot,
622 params: DocumentFormattingParams, 634 params: DocumentFormattingParams,
623) -> Result<Option<Vec<TextEdit>>> { 635) -> Result<Option<Vec<lsp_types::TextEdit>>> {
624 let _p = profile("handle_formatting"); 636 let _p = profile("handle_formatting");
625 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 637 let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
626 let file = world.analysis().file_text(file_id)?; 638 let file = world.analysis().file_text(file_id)?;
@@ -687,7 +699,7 @@ pub fn handle_formatting(
687 } 699 }
688 } 700 }
689 701
690 Ok(Some(vec![TextEdit { 702 Ok(Some(vec![lsp_types::TextEdit {
691 range: Range::new(Position::new(0, 0), end_position), 703 range: Range::new(Position::new(0, 0), end_position),
692 new_text: captured_stdout, 704 new_text: captured_stdout,
693 }])) 705 }]))
@@ -696,14 +708,21 @@ pub fn handle_formatting(
696pub fn handle_code_action( 708pub fn handle_code_action(
697 world: WorldSnapshot, 709 world: WorldSnapshot,
698 params: lsp_types::CodeActionParams, 710 params: lsp_types::CodeActionParams,
699) -> Result<Option<CodeActionResponse>> { 711) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
700 let _p = profile("handle_code_action"); 712 let _p = profile("handle_code_action");
713 // We intentionally don't support command-based actions, as those either
714 // requires custom client-code anyway, or requires server-initiated edits.
715 // Server initiated edits break causality, so we avoid those as well.
716 if !world.config.client_caps.code_action_literals {
717 return Ok(None);
718 }
719
701 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 720 let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
702 let line_index = world.analysis().file_line_index(file_id)?; 721 let line_index = world.analysis().file_line_index(file_id)?;
703 let range = from_proto::text_range(&line_index, params.range); 722 let range = from_proto::text_range(&line_index, params.range);
704 723
705 let diagnostics = world.analysis().diagnostics(file_id)?; 724 let diagnostics = world.analysis().diagnostics(file_id)?;
706 let mut res = CodeActionResponse::default(); 725 let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
707 726
708 let fixes_from_diagnostics = diagnostics 727 let fixes_from_diagnostics = diagnostics
709 .into_iter() 728 .into_iter()
@@ -713,22 +732,9 @@ pub fn handle_code_action(
713 732
714 for source_edit in fixes_from_diagnostics { 733 for source_edit in fixes_from_diagnostics {
715 let title = source_edit.label.clone(); 734 let title = source_edit.label.clone();
716 let edit = to_proto::source_change(&world, source_edit)?; 735 let edit = to_proto::snippet_workspace_edit(&world, source_edit)?;
717 736 let action = lsp_ext::CodeAction { title, kind: None, edit: Some(edit), command: None };
718 let command = Command { 737 res.push(action);
719 title,
720 command: "rust-analyzer.applySourceChange".to_string(),
721 arguments: Some(vec![to_value(edit).unwrap()]),
722 };
723 let action = CodeAction {
724 title: command.title.clone(),
725 kind: None,
726 diagnostics: None,
727 edit: None,
728 command: Some(command),
729 is_preferred: None,
730 };
731 res.push(action.into());
732 } 738 }
733 739
734 for fix in world.check_fixes.get(&file_id).into_iter().flatten() { 740 for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
@@ -740,14 +746,21 @@ pub fn handle_code_action(
740 } 746 }
741 747
742 let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default(); 748 let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default();
743 for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { 749 for assist in
750 world.analysis().assists(&world.config.assist, FileRange { file_id, range })?.into_iter()
751 {
744 match &assist.group_label { 752 match &assist.group_label {
745 Some(label) => grouped_assists 753 Some(label) => grouped_assists
746 .entry(label.to_owned()) 754 .entry(label.to_owned())
747 .or_insert_with(|| { 755 .or_insert_with(|| {
748 let idx = res.len(); 756 let idx = res.len();
749 let dummy = Command::new(String::new(), String::new(), None); 757 let dummy = lsp_ext::CodeAction {
750 res.push(dummy.into()); 758 title: String::new(),
759 kind: None,
760 command: None,
761 edit: None,
762 };
763 res.push(dummy);
751 (idx, Vec::new()) 764 (idx, Vec::new())
752 }) 765 })
753 .1 766 .1
@@ -775,35 +788,10 @@ pub fn handle_code_action(
775 command: "rust-analyzer.selectAndApplySourceChange".to_string(), 788 command: "rust-analyzer.selectAndApplySourceChange".to_string(),
776 arguments: Some(vec![serde_json::Value::Array(arguments)]), 789 arguments: Some(vec![serde_json::Value::Array(arguments)]),
777 }); 790 });
778 res[idx] = CodeAction { 791 res[idx] = lsp_ext::CodeAction { title, kind: None, edit: None, command };
779 title,
780 kind: None,
781 diagnostics: None,
782 edit: None,
783 command,
784 is_preferred: None,
785 }
786 .into();
787 } 792 }
788 } 793 }
789 794
790 // If the client only supports commands then filter the list
791 // and remove and actions that depend on edits.
792 if !world.config.client_caps.code_action_literals {
793 // FIXME: use drain_filter once it hits stable.
794 res = res
795 .into_iter()
796 .filter_map(|it| match it {
797 cmd @ lsp_types::CodeActionOrCommand::Command(_) => Some(cmd),
798 lsp_types::CodeActionOrCommand::CodeAction(action) => match action.command {
799 Some(cmd) if action.edit.is_none() => {
800 Some(lsp_types::CodeActionOrCommand::Command(cmd))
801 }
802 _ => None,
803 },
804 })
805 .collect();
806 }
807 Ok(Some(res)) 795 Ok(Some(res))
808} 796}
809 797
@@ -828,11 +816,11 @@ pub fn handle_code_lens(
828 for runnable in world.analysis().runnables(file_id)? { 816 for runnable in world.analysis().runnables(file_id)? {
829 let (run_title, debugee) = match &runnable.kind { 817 let (run_title, debugee) = match &runnable.kind {
830 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { 818 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
831 ("▶\u{fe0e}Run Test", true) 819 ("▶\u{fe0e} Run Test", true)
832 } 820 }
833 RunnableKind::DocTest { .. } => { 821 RunnableKind::DocTest { .. } => {
834 // cargo does not support -no-run for doctests 822 // cargo does not support -no-run for doctests
835 ("▶\u{fe0e}Run Doctest", false) 823 ("▶\u{fe0e} Run Doctest", false)
836 } 824 }
837 RunnableKind::Bench { .. } => { 825 RunnableKind::Bench { .. } => {
838 // Nothing wrong with bench debugging 826 // Nothing wrong with bench debugging
@@ -998,11 +986,11 @@ pub fn handle_document_highlight(
998pub fn handle_ssr( 986pub fn handle_ssr(
999 world: WorldSnapshot, 987 world: WorldSnapshot,
1000 params: lsp_ext::SsrParams, 988 params: lsp_ext::SsrParams,
1001) -> Result<lsp_ext::SourceChange> { 989) -> Result<lsp_types::WorkspaceEdit> {
1002 let _p = profile("handle_ssr"); 990 let _p = profile("handle_ssr");
1003 let source_change = 991 let source_change =
1004 world.analysis().structural_search_replace(&params.query, params.parse_only)??; 992 world.analysis().structural_search_replace(&params.query, params.parse_only)??;
1005 to_proto::source_change(&world, source_change) 993 to_proto::workspace_edit(&world, source_change)
1006} 994}
1007 995
1008pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { 996pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index a8e2e535f..f6f4bb134 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1,13 +1,12 @@
1//! Conversion of rust-analyzer specific types to lsp_types equivalents. 1//! Conversion of rust-analyzer specific types to lsp_types equivalents.
2use ra_db::{FileId, FileRange}; 2use ra_db::{FileId, FileRange};
3use ra_ide::{ 3use ra_ide::{
4 translate_offset_with_edit, Assist, CompletionItem, CompletionItemKind, Documentation, 4 Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
5 FileSystemEdit, Fold, FoldKind, FunctionSignature, Highlight, HighlightModifier, HighlightTag, 5 FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel,
6 HighlightedRange, InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, 6 InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Severity,
7 ReferenceAccess, Severity, SourceChange, SourceFileEdit, 7 SourceChange, SourceFileEdit, TextEdit,
8}; 8};
9use ra_syntax::{SyntaxKind, TextRange, TextSize}; 9use ra_syntax::{SyntaxKind, TextRange, TextSize};
10use ra_text_edit::{Indel, TextEdit};
11use ra_vfs::LineEndings; 10use ra_vfs::LineEndings;
12 11
13use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result}; 12use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result};
@@ -112,16 +111,28 @@ pub(crate) fn text_edit(
112 lsp_types::TextEdit { range, new_text } 111 lsp_types::TextEdit { range, new_text }
113} 112}
114 113
114pub(crate) fn snippet_text_edit(
115 line_index: &LineIndex,
116 line_endings: LineEndings,
117 is_snippet: bool,
118 indel: Indel,
119) -> lsp_ext::SnippetTextEdit {
120 let text_edit = text_edit(line_index, line_endings, indel);
121 let insert_text_format =
122 if is_snippet { Some(lsp_types::InsertTextFormat::Snippet) } else { None };
123 lsp_ext::SnippetTextEdit {
124 range: text_edit.range,
125 new_text: text_edit.new_text,
126 insert_text_format,
127 }
128}
129
115pub(crate) fn text_edit_vec( 130pub(crate) fn text_edit_vec(
116 line_index: &LineIndex, 131 line_index: &LineIndex,
117 line_endings: LineEndings, 132 line_endings: LineEndings,
118 text_edit: TextEdit, 133 text_edit: TextEdit,
119) -> Vec<lsp_types::TextEdit> { 134) -> Vec<lsp_types::TextEdit> {
120 text_edit 135 text_edit.into_iter().map(|indel| self::text_edit(line_index, line_endings, indel)).collect()
121 .as_indels()
122 .iter()
123 .map(|it| self::text_edit(line_index, line_endings, it.clone()))
124 .collect()
125} 136}
126 137
127pub(crate) fn completion_item( 138pub(crate) fn completion_item(
@@ -134,7 +145,7 @@ pub(crate) fn completion_item(
134 // LSP does not allow arbitrary edits in completion, so we have to do a 145 // LSP does not allow arbitrary edits in completion, so we have to do a
135 // non-trivial mapping here. 146 // non-trivial mapping here.
136 let source_range = completion_item.source_range(); 147 let source_range = completion_item.source_range();
137 for indel in completion_item.text_edit().as_indels() { 148 for indel in completion_item.text_edit().iter() {
138 if indel.delete.contains_range(source_range) { 149 if indel.delete.contains_range(source_range) {
139 text_edit = Some(if indel.delete == source_range { 150 text_edit = Some(if indel.delete == source_range {
140 self::text_edit(line_index, line_endings, indel.clone()) 151 self::text_edit(line_index, line_endings, indel.clone())
@@ -359,14 +370,6 @@ pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result<lsp_types::U
359 world.file_id_to_uri(file_id) 370 world.file_id_to_uri(file_id)
360} 371}
361 372
362pub(crate) fn text_document_identifier(
363 world: &WorldSnapshot,
364 file_id: FileId,
365) -> Result<lsp_types::TextDocumentIdentifier> {
366 let res = lsp_types::TextDocumentIdentifier { uri: url(world, file_id)? };
367 Ok(res)
368}
369
370pub(crate) fn versioned_text_document_identifier( 373pub(crate) fn versioned_text_document_identifier(
371 world: &WorldSnapshot, 374 world: &WorldSnapshot,
372 file_id: FileId, 375 file_id: FileId,
@@ -441,20 +444,20 @@ pub(crate) fn goto_definition_response(
441 } 444 }
442} 445}
443 446
444pub(crate) fn text_document_edit( 447pub(crate) fn snippet_text_document_edit(
445 world: &WorldSnapshot, 448 world: &WorldSnapshot,
449 is_snippet: bool,
446 source_file_edit: SourceFileEdit, 450 source_file_edit: SourceFileEdit,
447) -> Result<lsp_types::TextDocumentEdit> { 451) -> Result<lsp_ext::SnippetTextDocumentEdit> {
448 let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; 452 let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?;
449 let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; 453 let line_index = world.analysis().file_line_index(source_file_edit.file_id)?;
450 let line_endings = world.file_line_endings(source_file_edit.file_id); 454 let line_endings = world.file_line_endings(source_file_edit.file_id);
451 let edits = source_file_edit 455 let edits = source_file_edit
452 .edit 456 .edit
453 .as_indels() 457 .into_iter()
454 .iter() 458 .map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it.clone()))
455 .map(|it| text_edit(&line_index, line_endings, it.clone()))
456 .collect(); 459 .collect();
457 Ok(lsp_types::TextDocumentEdit { text_document, edits }) 460 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
458} 461}
459 462
460pub(crate) fn resource_op( 463pub(crate) fn resource_op(
@@ -479,41 +482,70 @@ pub(crate) fn source_change(
479 world: &WorldSnapshot, 482 world: &WorldSnapshot,
480 source_change: SourceChange, 483 source_change: SourceChange,
481) -> Result<lsp_ext::SourceChange> { 484) -> Result<lsp_ext::SourceChange> {
482 let cursor_position = match source_change.cursor_position { 485 let label = source_change.label.clone();
483 None => None, 486 let workspace_edit = self::snippet_workspace_edit(world, source_change)?;
484 Some(pos) => { 487 Ok(lsp_ext::SourceChange { label, workspace_edit, cursor_position: None })
485 let line_index = world.analysis().file_line_index(pos.file_id)?; 488}
486 let edit = source_change 489
487 .source_file_edits 490pub(crate) fn snippet_workspace_edit(
488 .iter() 491 world: &WorldSnapshot,
489 .find(|it| it.file_id == pos.file_id) 492 source_change: SourceChange,
490 .map(|it| &it.edit); 493) -> Result<lsp_ext::SnippetWorkspaceEdit> {
491 let line_col = match edit { 494 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
492 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
493 None => line_index.line_col(pos.offset),
494 };
495 let position =
496 lsp_types::Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
497 Some(lsp_types::TextDocumentPositionParams {
498 text_document: text_document_identifier(world, pos.file_id)?,
499 position,
500 })
501 }
502 };
503 let mut document_changes: Vec<lsp_types::DocumentChangeOperation> = Vec::new();
504 for op in source_change.file_system_edits { 495 for op in source_change.file_system_edits {
505 let op = resource_op(&world, op)?; 496 let op = resource_op(&world, op)?;
506 document_changes.push(lsp_types::DocumentChangeOperation::Op(op)); 497 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op));
507 } 498 }
508 for edit in source_change.source_file_edits { 499 for edit in source_change.source_file_edits {
509 let edit = text_document_edit(&world, edit)?; 500 let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?;
510 document_changes.push(lsp_types::DocumentChangeOperation::Edit(edit)); 501 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
502 }
503 let workspace_edit =
504 lsp_ext::SnippetWorkspaceEdit { changes: None, document_changes: Some(document_changes) };
505 Ok(workspace_edit)
506}
507
508pub(crate) fn workspace_edit(
509 world: &WorldSnapshot,
510 source_change: SourceChange,
511) -> Result<lsp_types::WorkspaceEdit> {
512 assert!(!source_change.is_snippet);
513 snippet_workspace_edit(world, source_change).map(|it| it.into())
514}
515
516impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
517 fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
518 lsp_types::WorkspaceEdit {
519 changes: None,
520 document_changes: snippet_workspace_edit.document_changes.map(|changes| {
521 lsp_types::DocumentChanges::Operations(
522 changes
523 .into_iter()
524 .map(|change| match change {
525 lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
526 lsp_types::DocumentChangeOperation::Op(op)
527 }
528 lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
529 lsp_types::DocumentChangeOperation::Edit(
530 lsp_types::TextDocumentEdit {
531 text_document: edit.text_document,
532 edits: edit
533 .edits
534 .into_iter()
535 .map(|edit| lsp_types::TextEdit {
536 range: edit.range,
537 new_text: edit.new_text,
538 })
539 .collect(),
540 },
541 )
542 }
543 })
544 .collect(),
545 )
546 }),
547 }
511 } 548 }
512 let workspace_edit = lsp_types::WorkspaceEdit {
513 changes: None,
514 document_changes: Some(lsp_types::DocumentChanges::Operations(document_changes)),
515 };
516 Ok(lsp_ext::SourceChange { label: source_change.label, workspace_edit, cursor_position })
517} 549}
518 550
519pub fn call_hierarchy_item( 551pub fn call_hierarchy_item(
@@ -571,22 +603,12 @@ fn main() <fold>{
571 } 603 }
572} 604}
573 605
574pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_types::CodeAction> { 606pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> {
575 let source_change = source_change(&world, assist.source_change)?; 607 let res = lsp_ext::CodeAction {
576 let arg = serde_json::to_value(source_change)?; 608 title: assist.label,
577 let title = assist.label;
578 let command = lsp_types::Command {
579 title: title.clone(),
580 command: "rust-analyzer.applySourceChange".to_string(),
581 arguments: Some(vec![arg]),
582 };
583
584 Ok(lsp_types::CodeAction {
585 title,
586 kind: Some(String::new()), 609 kind: Some(String::new()),
587 diagnostics: None, 610 edit: Some(snippet_workspace_edit(world, assist.source_change)?),
588 edit: None, 611 command: None,
589 command: Some(command), 612 };
590 is_preferred: None, 613 Ok(res)
591 })
592} 614}
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 5011cc273..4e94c37e1 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -333,29 +333,17 @@ fn main() {}
333 partial_result_params: PartialResultParams::default(), 333 partial_result_params: PartialResultParams::default(),
334 work_done_progress_params: WorkDoneProgressParams::default(), 334 work_done_progress_params: WorkDoneProgressParams::default(),
335 }, 335 },
336 json!([ 336 json!([{
337 { 337 "edit": {
338 "command": { 338 "documentChanges": [
339 "arguments": [
340 { 339 {
341 "cursorPosition": null, 340 "kind": "create",
342 "label": "Create module", 341 "uri": "file:///[..]/src/bar.rs"
343 "workspaceEdit": {
344 "documentChanges": [
345 {
346 "kind": "create",
347 "uri": "file:///[..]/src/bar.rs"
348 }
349 ]
350 }
351 } 342 }
352 ], 343 ]
353 "command": "rust-analyzer.applySourceChange",
354 "title": "Create module"
355 }, 344 },
356 "title": "Create module" 345 "title": "Create module"
357 } 346 }]),
358 ]),
359 ); 347 );
360 348
361 server.request::<CodeActionRequest>( 349 server.request::<CodeActionRequest>(
@@ -416,29 +404,17 @@ fn main() {{}}
416 partial_result_params: PartialResultParams::default(), 404 partial_result_params: PartialResultParams::default(),
417 work_done_progress_params: WorkDoneProgressParams::default(), 405 work_done_progress_params: WorkDoneProgressParams::default(),
418 }, 406 },
419 json!([ 407 json!([{
420 { 408 "edit": {
421 "command": { 409 "documentChanges": [
422 "arguments": [
423 { 410 {
424 "cursorPosition": null, 411 "kind": "create",
425 "label": "Create module", 412 "uri": "file://[..]/src/bar.rs"
426 "workspaceEdit": {
427 "documentChanges": [
428 {
429 "kind": "create",
430 "uri": "file:///[..]/src/bar.rs"
431 }
432 ]
433 }
434 } 413 }
435 ], 414 ]
436 "command": "rust-analyzer.applySourceChange",
437 "title": "Create module"
438 }, 415 },
439 "title": "Create module" 416 "title": "Create module"
440 } 417 }]),
441 ]),
442 ); 418 );
443 419
444 server.request::<CodeActionRequest>( 420 server.request::<CodeActionRequest>(
@@ -498,27 +474,21 @@ fn main() {{}}
498 position: Position { line: 0, character: 5 }, 474 position: Position { line: 0, character: 5 },
499 }, 475 },
500 json!({ 476 json!({
501 "cursorPosition": { 477 "documentChanges": [
502 "position": { "character": 4, "line": 1 }, 478 {
503 "textDocument": { "uri": "file:///[..]src/m0.rs" } 479 "edits": [
504 }, 480 {
505 "label": "On enter", 481 "insertTextFormat": 2,
506 "workspaceEdit": { 482 "newText": "\n/// $0",
507 "documentChanges": [ 483 "range": {
508 { 484 "end": { "character": 5, "line": 0 },
509 "edits": [ 485 "start": { "character": 5, "line": 0 }
510 {
511 "newText": "\n/// ",
512 "range": {
513 "end": { "character": 5, "line": 0 },
514 "start": { "character": 5, "line": 0 }
515 }
516 } 486 }
517 ], 487 }
518 "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null } 488 ],
519 } 489 "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
520 ] 490 }
521 } 491 ]
522 }), 492 }),
523 ); 493 );
524 let elapsed = start.elapsed(); 494 let elapsed = start.elapsed();
@@ -550,27 +520,21 @@ version = \"0.0.0\"
550 position: Position { line: 0, character: 8 }, 520 position: Position { line: 0, character: 8 },
551 }, 521 },
552 json!({ 522 json!({
553 "cursorPosition": { 523 "documentChanges": [
554 "position": { "line": 1, "character": 4 }, 524 {
555 "textDocument": { "uri": "file:///[..]src/main.rs" } 525 "edits": [
556 }, 526 {
557 "label": "On enter", 527 "insertTextFormat": 2,
558 "workspaceEdit": { 528 "newText": "\r\n/// $0",
559 "documentChanges": [ 529 "range": {
560 { 530 "end": { "line": 0, "character": 8 },
561 "edits": [ 531 "start": { "line": 0, "character": 8 }
562 {
563 "newText": "\r\n/// ",
564 "range": {
565 "end": { "line": 0, "character": 8 },
566 "start": { "line": 0, "character": 8 }
567 }
568 } 532 }
569 ], 533 }
570 "textDocument": { "uri": "file:///[..]src/main.rs", "version": null } 534 ],
571 } 535 "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
572 ] 536 }
573 } 537 ]
574 }), 538 }),
575 ); 539 );
576} 540}
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 0f34ce70e..71a57fba2 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -116,3 +116,11 @@ pub fn to_lower_snake_case(s: &str) -> String {
116 } 116 }
117 buf 117 buf
118} 118}
119
120pub fn replace(buf: &mut String, from: char, to: &str) {
121 if !buf.contains(from) {
122 return;
123 }
124 // FIXME: do this in place.
125 *buf = buf.replace(from, to)
126}
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index b1e3c328f..be2cfbaa2 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -7,7 +7,7 @@
7//! * marks (see the eponymous module). 7//! * marks (see the eponymous module).
8 8
9#[macro_use] 9#[macro_use]
10pub mod marks; 10pub mod mark;
11 11
12use std::{ 12use std::{
13 fs, 13 fs,
diff --git a/crates/test_utils/src/marks.rs b/crates/test_utils/src/mark.rs
index c3185e860..7c309a894 100644
--- a/crates/test_utils/src/marks.rs
+++ b/crates/test_utils/src/mark.rs
@@ -7,18 +7,18 @@
7//! ``` 7//! ```
8//! #[test] 8//! #[test]
9//! fn test_foo() { 9//! fn test_foo() {
10//! covers!(test_foo); 10//! mark::check!(test_foo);
11//! } 11//! }
12//! ``` 12//! ```
13//! 13//!
14//! and in the code under test you write 14//! and in the code under test you write
15//! 15//!
16//! ``` 16//! ```
17//! # use test_utils::tested_by; 17//! # use test_utils::mark;
18//! # fn some_condition() -> bool { true } 18//! # fn some_condition() -> bool { true }
19//! fn foo() { 19//! fn foo() {
20//! if some_condition() { 20//! if some_condition() {
21//! tested_by!(test_foo); 21//! mark::hit!(test_foo);
22//! } 22//! }
23//! } 23//! }
24//! ``` 24//! ```
@@ -29,43 +29,31 @@
29use std::sync::atomic::{AtomicUsize, Ordering}; 29use std::sync::atomic::{AtomicUsize, Ordering};
30 30
31#[macro_export] 31#[macro_export]
32macro_rules! tested_by { 32macro_rules! _hit {
33 ($ident:ident; force) => {{
34 {
35 // sic! use call-site crate
36 crate::marks::$ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
37 }
38 }};
39 ($ident:ident) => {{ 33 ($ident:ident) => {{
40 #[cfg(test)] 34 #[cfg(test)]
41 { 35 {
42 // sic! use call-site crate 36 extern "C" {
43 crate::marks::$ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst); 37 #[no_mangle]
38 static $ident: std::sync::atomic::AtomicUsize;
39 }
40 unsafe {
41 $ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
42 }
44 } 43 }
45 }}; 44 }};
46} 45}
46pub use _hit as hit;
47 47
48#[macro_export] 48#[macro_export]
49macro_rules! covers { 49macro_rules! _check {
50 // sic! use call-site crate
51 ($ident:ident) => { 50 ($ident:ident) => {
52 $crate::covers!(crate::$ident) 51 #[no_mangle]
53 }; 52 static $ident: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
54 ($krate:ident :: $ident:ident) => { 53 let _checker = $crate::mark::MarkChecker::new(&$ident);
55 let _checker = $crate::marks::MarkChecker::new(&$krate::marks::$ident);
56 };
57}
58
59#[macro_export]
60macro_rules! marks {
61 ($($ident:ident)*) => {
62 $(
63 #[allow(bad_style)]
64 pub static $ident: std::sync::atomic::AtomicUsize =
65 std::sync::atomic::AtomicUsize::new(0);
66 )*
67 }; 54 };
68} 55}
56pub use _check as check;
69 57
70pub struct MarkChecker { 58pub struct MarkChecker {
71 mark: &'static AtomicUsize, 59 mark: &'static AtomicUsize,
diff --git a/docs/dev/README.md b/docs/dev/README.md
index a20ead0b6..65cc9fc12 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -74,7 +74,7 @@ relevant test and execute it (VS Code includes an action for running a single
74test). 74test).
75 75
76However, launching a VS Code instance with locally build language server is 76However, launching a VS Code instance with locally build language server is
77possible. There's **"Run Extension (Dev Server)"** launch configuration for this. 77possible. There's **"Run Extension (Debug Build)"** launch configuration for this.
78 78
79In general, I use one of the following workflows for fixing bugs and 79In general, I use one of the following workflows for fixing bugs and
80implementing features. 80implementing features.
@@ -86,7 +86,7 @@ then just do printf-driven development/debugging. As a sanity check after I'm
86done, I use `cargo xtask install --server` and **Reload Window** action in VS 86done, I use `cargo xtask install --server` and **Reload Window** action in VS
87Code to sanity check that the thing works as I expect. 87Code to sanity check that the thing works as I expect.
88 88
89If the problem concerns only the VS Code extension, I use **Run Extension** 89If the problem concerns only the VS Code extension, I use **Run Installed Extension**
90launch configuration from `launch.json`. Notably, this uses the usual 90launch configuration from `launch.json`. Notably, this uses the usual
91`rust-analyzer` binary from `PATH`. For this it is important to have the following 91`rust-analyzer` binary from `PATH`. For this it is important to have the following
92in `setting.json` file: 92in `setting.json` file:
diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md
index 1aa392935..59a83f7d7 100644
--- a/docs/dev/debugging.md
+++ b/docs/dev/debugging.md
@@ -22,8 +22,8 @@ where **only** the `rust-analyzer` extension being debugged is enabled.
22 22
23## Debug TypeScript VSCode extension 23## Debug TypeScript VSCode extension
24 24
25- `Run Extension` - runs the extension with the globally installed `rust-analyzer` binary. 25- `Run Installed Extension` - runs the extension with the globally installed `rust-analyzer` binary.
26- `Run Extension (Dev Server)` - runs extension with the locally built LSP server (`target/debug/rust-analyzer`). 26- `Run Extension (Debug Build)` - runs extension with the locally built LSP server (`target/debug/rust-analyzer`).
27 27
28TypeScript debugging is configured to watch your source edits and recompile. 28TypeScript debugging is configured to watch your source edits and recompile.
29To apply changes to an already running debug process, press <kbd>Ctrl+Shift+P</kbd> and run the following command in your `[Extension Development Host]` 29To apply changes to an already running debug process, press <kbd>Ctrl+Shift+P</kbd> and run the following command in your `[Extension Development Host]`
@@ -47,7 +47,7 @@ To apply changes to an already running debug process, press <kbd>Ctrl+Shift+P</k
47 debug = 2 47 debug = 2
48 ``` 48 ```
49 49
50- Select `Run Extension (Dev Server)` to run your locally built `target/debug/rust-analyzer`. 50- Select `Run Extension (Debug Build)` to run your locally built `target/debug/rust-analyzer`.
51 51
52- In the original VSCode window once again select the `Attach To Server` debug configuration. 52- In the original VSCode window once again select the `Attach To Server` debug configuration.
53 53
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
new file mode 100644
index 000000000..7c45aef4c
--- /dev/null
+++ b/docs/dev/lsp-extensions.md
@@ -0,0 +1,121 @@
1# LSP Extensions
2
3This document describes LSP extensions used by rust-analyzer.
4It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ).
5We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority.
6All capabilities are enabled via `experimental` field of `ClientCapabilities`.
7
8## `SnippetTextEdit`
9
10**Client Capability:** `{ "snippetTextEdit": boolean }`
11
12If this capability is set, `WorkspaceEdit`s returned from `codeAction` requests might contain `SnippetTextEdit`s instead of usual `TextEdit`s:
13
14```typescript
15interface SnippetTextEdit extends TextEdit {
16 insertTextFormat?: InsertTextFormat;
17}
18```
19
20```typescript
21export interface TextDocumentEdit {
22 textDocument: VersionedTextDocumentIdentifier;
23 edits: (TextEdit | SnippetTextEdit)[];
24}
25```
26
27When applying such code action, the editor should insert snippet, with tab stops and placeholder.
28At the moment, rust-analyzer guarantees that only a single edit will have `InsertTextFormat.Snippet`.
29
30### Example
31
32"Add `derive`" code action transforms `struct S;` into `#[derive($0)] struct S;`
33
34### Unresolved Questions
35
36* Where exactly are `SnippetTextEdit`s allowed (only in code actions at the moment)?
37* Can snippets span multiple files (so far, no)?
38
39## `joinLines`
40
41**Server Capability:** `{ "joinLines": boolean }`
42
43This request is send from client to server to handle "Join Lines" editor action.
44
45**Method:** `experimental/JoinLines`
46
47**Request:**
48
49```typescript
50interface JoinLinesParams {
51 textDocument: TextDocumentIdentifier,
52 /// Currently active selections/cursor offsets.
53 /// This is an array to support multiple cursors.
54 ranges: Range[],
55}
56```
57
58**Response:**
59
60```typescript
61TextEdit[]
62```
63
64### Example
65
66```rust
67fn main() {
68 /*cursor here*/let x = {
69 92
70 };
71}
72```
73
74`experimental/joinLines` yields (curly braces are automagiacally removed)
75
76```rust
77fn main() {
78 let x = 92;
79}
80```
81
82### Unresolved Question
83
84* What is the position of the cursor after `joinLines`?
85 Currently this is left to editor's discretion, but it might be useful to specify on the server via snippets.
86 However, it then becomes unclear how it works with multi cursor.
87
88## Structural Search Replace (SSR)
89
90**Server Capability:** `{ "ssr": boolean }`
91
92This request is send from client to server to handle structural search replace -- automated syntax tree based transformation of the source.
93
94**Method:** `experimental/ssr`
95
96**Request:**
97
98```typescript
99interface SsrParams {
100 /// Search query.
101 /// The specific syntax is specified outside of the protocol.
102 query: string,
103 /// If true, only check the syntax of the query and don't compute the actual edit.
104 parseOnly: bool,
105}
106```
107
108**Response:**
109
110```typescript
111WorkspaceEdit
112```
113
114### Example
115
116SSR with query `foo($a:expr, $b:expr) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
117
118### Unresolved Question
119
120* Probably needs search without replace mode
121* Needs a way to limit the scope to certain files.
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 692fd4f52..4ad7ea59d 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -17,7 +17,7 @@ struct S;
17struct S; 17struct S;
18 18
19impl Debug for S { 19impl Debug for S {
20 20 $0
21} 21}
22``` 22```
23 23
@@ -33,7 +33,7 @@ struct Point {
33} 33}
34 34
35// AFTER 35// AFTER
36#[derive()] 36#[derive($0)]
37struct Point { 37struct Point {
38 x: u32, 38 x: u32,
39 y: u32, 39 y: u32,
@@ -77,7 +77,7 @@ fn foo() {
77} 77}
78 78
79fn bar(arg: &str, baz: Baz) { 79fn bar(arg: &str, baz: Baz) {
80 todo!() 80 ${0:todo!()}
81} 81}
82 82
83``` 83```
@@ -105,16 +105,16 @@ Adds a new inherent impl for a type.
105```rust 105```rust
106// BEFORE 106// BEFORE
107struct Ctx<T: Clone> { 107struct Ctx<T: Clone> {
108 data: T,┃ 108 data: T,┃
109} 109}
110 110
111// AFTER 111// AFTER
112struct Ctx<T: Clone> { 112struct Ctx<T: Clone> {
113 data: T, 113 data: T,
114} 114}
115 115
116impl<T: Clone> Ctx<T> { 116impl<T: Clone> Ctx<T> {
117 117 $0
118} 118}
119``` 119```
120 120
@@ -146,7 +146,7 @@ trait Trait {
146impl Trait for () { 146impl Trait for () {
147 Type X = (); 147 Type X = ();
148 fn foo(&self) {} 148 fn foo(&self) {}
149 fn bar(&self) {} 149 $0fn bar(&self) {}
150 150
151} 151}
152``` 152```
@@ -176,7 +176,7 @@ trait Trait<T> {
176 176
177impl Trait<u32> for () { 177impl Trait<u32> for () {
178 fn foo(&self) -> u32 { 178 fn foo(&self) -> u32 {
179 todo!() 179 ${0:todo!()}
180 } 180 }
181 181
182} 182}
@@ -198,11 +198,29 @@ struct Ctx<T: Clone> {
198} 198}
199 199
200impl<T: Clone> Ctx<T> { 200impl<T: Clone> Ctx<T> {
201 fn new(data: T) -> Self { Self { data } } 201 fn $0new(data: T) -> Self { Self { data } }
202} 202}
203 203
204``` 204```
205 205
206## `add_turbo_fish`
207
208Adds `::<_>` to a call of a generic method or function.
209
210```rust
211// BEFORE
212fn make<T>() -> T { todo!() }
213fn main() {
214 let x = make┃();
215}
216
217// AFTER
218fn make<T>() -> T { todo!() }
219fn main() {
220 let x = make::<${0:_}>();
221}
222```
223
206## `apply_demorgan` 224## `apply_demorgan`
207 225
208Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). 226Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
@@ -250,7 +268,7 @@ Change the function's return type to Result.
250fn foo() -> i32┃ { 42i32 } 268fn foo() -> i32┃ { 42i32 }
251 269
252// AFTER 270// AFTER
253fn foo() -> Result<i32, > { Ok(42i32) } 271fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
254``` 272```
255 273
256## `change_visibility` 274## `change_visibility`
@@ -307,12 +325,34 @@ enum Action { Move { distance: u32 }, Stop }
307 325
308fn handle(action: Action) { 326fn handle(action: Action) {
309 match action { 327 match action {
310 Action::Move { distance } => {} 328 $0Action::Move { distance } => {}
311 Action::Stop => {} 329 Action::Stop => {}
312 } 330 }
313} 331}
314``` 332```
315 333
334## `fix_visibility`
335
336Makes inaccessible item public.
337
338```rust
339// BEFORE
340mod m {
341 fn frobnicate() {}
342}
343fn main() {
344 m::frobnicate┃() {}
345}
346
347// AFTER
348mod m {
349 $0pub(crate) fn frobnicate() {}
350}
351fn main() {
352 m::frobnicate() {}
353}
354```
355
316## `flip_binexpr` 356## `flip_binexpr`
317 357
318Flips operands of a binary expression. 358Flips operands of a binary expression.
@@ -386,7 +426,7 @@ fn main() {
386 426
387// AFTER 427// AFTER
388fn main() { 428fn main() {
389 let var_name = (1 + 2); 429 let $0var_name = (1 + 2);
390 var_name * 4; 430 var_name * 4;
391} 431}
392``` 432```
@@ -693,7 +733,7 @@ fn main() {
693 let x: Result<i32, i32> = Result::Ok(92); 733 let x: Result<i32, i32> = Result::Ok(92);
694 let y = match x { 734 let y = match x {
695 Ok(a) => a, 735 Ok(a) => a,
696 _ => unreachable!(), 736 $0_ => unreachable!(),
697 }; 737 };
698} 738}
699``` 739```
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index 03836e6e2..40ed54809 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -249,7 +249,7 @@ If it worked, you should see "rust-analyzer, Line X, Column Y" on the left side
249 249
250If you get an error saying `No such file or directory: 'rust-analyzer'`, see the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>> section on installing the language server binary. 250If you get an error saying `No such file or directory: 'rust-analyzer'`, see the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>> section on installing the language server binary.
251 251
252=== Gnome Builder 252=== GNOME Builder
253 253
254Prerequisites: You have installed the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>. 254Prerequisites: You have installed the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>.
255 255
diff --git a/editors/code/package.json b/editors/code/package.json
index d899f60e3..78f647baa 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -313,22 +313,22 @@
313 "rust-analyzer.inlayHints.enable": { 313 "rust-analyzer.inlayHints.enable": {
314 "type": "boolean", 314 "type": "boolean",
315 "default": true, 315 "default": true,
316 "description": "Disable all inlay hints" 316 "description": "Whether to show inlay hints"
317 }, 317 },
318 "rust-analyzer.inlayHints.typeHints": { 318 "rust-analyzer.inlayHints.typeHints": {
319 "type": "boolean", 319 "type": "boolean",
320 "default": true, 320 "default": true,
321 "description": "Whether to show inlay type hints" 321 "description": "Whether to show inlay type hints for variables."
322 }, 322 },
323 "rust-analyzer.inlayHints.chainingHints": { 323 "rust-analyzer.inlayHints.chainingHints": {
324 "type": "boolean", 324 "type": "boolean",
325 "default": true, 325 "default": true,
326 "description": "Whether to show inlay type hints for method chains" 326 "description": "Whether to show inlay type hints for method chains."
327 }, 327 },
328 "rust-analyzer.inlayHints.parameterHints": { 328 "rust-analyzer.inlayHints.parameterHints": {
329 "type": "boolean", 329 "type": "boolean",
330 "default": true, 330 "default": true,
331 "description": "Whether to show function parameter name inlay hints at the call site" 331 "description": "Whether to show function parameter name inlay hints at the call site."
332 }, 332 },
333 "rust-analyzer.inlayHints.maxLength": { 333 "rust-analyzer.inlayHints.maxLength": {
334 "type": [ 334 "type": [
diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts
index 28c7de992..6a41873d0 100644
--- a/editors/code/src/cargo.ts
+++ b/editors/code/src/cargo.ts
@@ -51,10 +51,14 @@ export class Cargo {
51 51
52 // arguments for a runnable from the quick pick should be updated. 52 // arguments for a runnable from the quick pick should be updated.
53 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens 53 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens
54 if (cargoArgs[0] === "run") { 54 switch (cargoArgs[0]) {
55 cargoArgs[0] = "build"; 55 case "run": cargoArgs[0] = "build"; break;
56 } else if (cargoArgs.indexOf("--no-run") === -1) { 56 case "test": {
57 cargoArgs.push("--no-run"); 57 if (cargoArgs.indexOf("--no-run") === -1) {
58 cargoArgs.push("--no-run");
59 }
60 break;
61 }
58 } 62 }
59 63
60 let artifacts = await this.artifactsFromArgs(cargoArgs); 64 let artifacts = await this.artifactsFromArgs(cargoArgs);
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index cffdcf11a..fac1a0be3 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -31,24 +31,79 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
31 const res = await next(document, token); 31 const res = await next(document, token);
32 if (res === undefined) throw new Error('busy'); 32 if (res === undefined) throw new Error('busy');
33 return res; 33 return res;
34 },
35 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
36 const params: lc.CodeActionParams = {
37 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
38 range: client.code2ProtocolConverter.asRange(range),
39 context: client.code2ProtocolConverter.asCodeActionContext(context)
40 };
41 return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => {
42 if (values === null) return undefined;
43 const result: (vscode.CodeAction | vscode.Command)[] = [];
44 for (const item of values) {
45 if (lc.CodeAction.is(item)) {
46 const action = client.protocol2CodeConverter.asCodeAction(item);
47 if (isSnippetEdit(item)) {
48 action.command = {
49 command: "rust-analyzer.applySnippetWorkspaceEdit",
50 title: "",
51 arguments: [action.edit],
52 };
53 action.edit = undefined;
54 }
55 result.push(action);
56 } else {
57 const command = client.protocol2CodeConverter.asCommand(item);
58 result.push(command);
59 }
60 }
61 return result;
62 },
63 (_error) => undefined
64 );
34 } 65 }
66
35 } as any 67 } as any
36 }; 68 };
37 69
38 const res = new lc.LanguageClient( 70 const client = new lc.LanguageClient(
39 'rust-analyzer', 71 'rust-analyzer',
40 'Rust Analyzer Language Server', 72 'Rust Analyzer Language Server',
41 serverOptions, 73 serverOptions,
42 clientOptions, 74 clientOptions,
43 ); 75 );
44 76
45 // To turn on all proposed features use: res.registerProposedFeatures(); 77 // To turn on all proposed features use: client.registerProposedFeatures();
46 // Here we want to enable CallHierarchyFeature and SemanticTokensFeature 78 // Here we want to enable CallHierarchyFeature and SemanticTokensFeature
47 // since they are available on stable. 79 // since they are available on stable.
48 // Note that while these features are stable in vscode their LSP protocol 80 // Note that while these features are stable in vscode their LSP protocol
49 // implementations are still in the "proposed" category for 3.16. 81 // implementations are still in the "proposed" category for 3.16.
50 res.registerFeature(new CallHierarchyFeature(res)); 82 client.registerFeature(new CallHierarchyFeature(client));
51 res.registerFeature(new SemanticTokensFeature(res)); 83 client.registerFeature(new SemanticTokensFeature(client));
84 client.registerFeature(new SnippetTextEditFeature());
85
86 return client;
87}
52 88
53 return res; 89class SnippetTextEditFeature implements lc.StaticFeature {
90 fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
91 const caps: any = capabilities.experimental ?? {};
92 caps.snippetTextEdit = true;
93 capabilities.experimental = caps;
94 }
95 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
96 }
97}
98
99function isSnippetEdit(action: lc.CodeAction): boolean {
100 const documentChanges = action.edit?.documentChanges ?? [];
101 for (const edit of documentChanges) {
102 if (lc.TextDocumentEdit.is(edit)) {
103 if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) {
104 return true;
105 }
106 }
107 }
108 return false;
54} 109}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index bdb7fc3b0..e5ed77e32 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -4,6 +4,7 @@ import * as ra from '../rust-analyzer-api';
4 4
5import { Ctx, Cmd } from '../ctx'; 5import { Ctx, Cmd } from '../ctx';
6import * as sourceChange from '../source_change'; 6import * as sourceChange from '../source_change';
7import { assert } from '../util';
7 8
8export * from './analyzer_status'; 9export * from './analyzer_status';
9export * from './matching_brace'; 10export * from './matching_brace';
@@ -51,3 +52,58 @@ export function selectAndApplySourceChange(ctx: Ctx): Cmd {
51 } 52 }
52 }; 53 };
53} 54}
55
56export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
57 return async (edit: vscode.WorkspaceEdit) => {
58 await applySnippetWorkspaceEdit(edit);
59 };
60}
61
62export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
63 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
64 const [uri, edits] = edit.entries()[0];
65
66 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
67 if (!editor) return;
68
69 let selection: vscode.Selection | undefined = undefined;
70 let lineDelta = 0;
71 await editor.edit((builder) => {
72 for (const indel of edits) {
73 const parsed = parseSnippet(indel.newText);
74 if (parsed) {
75 const [newText, [placeholderStart, placeholderLength]] = parsed;
76 const prefix = newText.substr(0, placeholderStart);
77 const lastNewline = prefix.lastIndexOf('\n');
78
79 const startLine = indel.range.start.line + lineDelta + countLines(prefix);
80 const startColumn = lastNewline === -1 ?
81 indel.range.start.character + placeholderStart
82 : prefix.length - lastNewline - 1;
83 const endColumn = startColumn + placeholderLength;
84 selection = new vscode.Selection(
85 new vscode.Position(startLine, startColumn),
86 new vscode.Position(startLine, endColumn),
87 );
88 builder.replace(indel.range, newText);
89 } else {
90 lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
91 builder.replace(indel.range, indel.newText);
92 }
93 }
94 });
95 if (selection) editor.selection = selection;
96}
97
98function parseSnippet(snip: string): [string, [number, number]] | undefined {
99 const m = snip.match(/\$(0|\{0:([^}]*)\})/);
100 if (!m) return undefined;
101 const placeholder = m[2] ?? "";
102 const range: [number, number] = [m.index!!, placeholder.length];
103 const insert = snip.replace(m[0], placeholder);
104 return [insert, range];
105}
106
107function countLines(text: string): number {
108 return (text.match(/\n/g) || []).length;
109}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
index de0614653..0bf1ee6e6 100644
--- a/editors/code/src/commands/join_lines.ts
+++ b/editors/code/src/commands/join_lines.ts
@@ -1,7 +1,7 @@
1import * as ra from '../rust-analyzer-api'; 1import * as ra from '../rust-analyzer-api';
2import * as lc from 'vscode-languageclient';
2 3
3import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd } from '../ctx';
4import { applySourceChange } from '../source_change';
5 5
6export function joinLines(ctx: Ctx): Cmd { 6export function joinLines(ctx: Ctx): Cmd {
7 return async () => { 7 return async () => {
@@ -9,10 +9,14 @@ export function joinLines(ctx: Ctx): Cmd {
9 const client = ctx.client; 9 const client = ctx.client;
10 if (!editor || !client) return; 10 if (!editor || !client) return;
11 11
12 const change = await client.sendRequest(ra.joinLines, { 12 const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
13 range: client.code2ProtocolConverter.asRange(editor.selection), 13 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
14 textDocument: { uri: editor.document.uri.toString() }, 14 textDocument: { uri: editor.document.uri.toString() },
15 }); 15 });
16 await applySourceChange(ctx, change); 16 editor.edit((builder) => {
17 client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
18 builder.replace(edit.range, edit.newText);
19 });
20 });
17 }; 21 };
18} 22}
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
index 285849db7..a7871c31e 100644
--- a/editors/code/src/commands/on_enter.ts
+++ b/editors/code/src/commands/on_enter.ts
@@ -1,8 +1,8 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api'; 2import * as ra from '../rust-analyzer-api';
3 3
4import { applySourceChange } from '../source_change';
5import { Cmd, Ctx } from '../ctx'; 4import { Cmd, Ctx } from '../ctx';
5import { applySnippetWorkspaceEdit } from '.';
6 6
7async function handleKeypress(ctx: Ctx) { 7async function handleKeypress(ctx: Ctx) {
8 const editor = ctx.activeRustEditor; 8 const editor = ctx.activeRustEditor;
@@ -21,7 +21,8 @@ async function handleKeypress(ctx: Ctx) {
21 }); 21 });
22 if (!change) return false; 22 if (!change) return false;
23 23
24 await applySourceChange(ctx, change); 24 const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change);
25 await applySnippetWorkspaceEdit(workspaceEdit);
25 return true; 26 return true;
26} 27}
27 28
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
index 4ef8cdf04..5d40a64d2 100644
--- a/editors/code/src/commands/ssr.ts
+++ b/editors/code/src/commands/ssr.ts
@@ -2,7 +2,6 @@ import * as vscode from 'vscode';
2import * as ra from "../rust-analyzer-api"; 2import * as ra from "../rust-analyzer-api";
3 3
4import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd } from '../ctx';
5import { applySourceChange } from '../source_change';
6 5
7export function ssr(ctx: Ctx): Cmd { 6export function ssr(ctx: Ctx): Cmd {
8 return async () => { 7 return async () => {
@@ -22,11 +21,10 @@ export function ssr(ctx: Ctx): Cmd {
22 } 21 }
23 }; 22 };
24 const request = await vscode.window.showInputBox(options); 23 const request = await vscode.window.showInputBox(options);
25
26 if (!request) return; 24 if (!request) return;
27 25
28 const change = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); 26 const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
29 27
30 await applySourceChange(ctx, change); 28 await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
31 }; 29 };
32} 30}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index c015460b8..8b0a9d870 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -91,6 +91,7 @@ export async function activate(context: vscode.ExtensionContext) {
91 ctx.registerCommand('debugSingle', commands.debugSingle); 91 ctx.registerCommand('debugSingle', commands.debugSingle);
92 ctx.registerCommand('showReferences', commands.showReferences); 92 ctx.registerCommand('showReferences', commands.showReferences);
93 ctx.registerCommand('applySourceChange', commands.applySourceChange); 93 ctx.registerCommand('applySourceChange', commands.applySourceChange);
94 ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand);
94 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); 95 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange);
95 96
96 ctx.pushCleanup(activateTaskProvider(workspaceFolder)); 97 ctx.pushCleanup(activateTaskProvider(workspaceFolder));
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
index 400ac3714..73f36432f 100644
--- a/editors/code/src/rust-analyzer-api.ts
+++ b/editors/code/src/rust-analyzer-api.ts
@@ -64,12 +64,12 @@ export const parentModule = request<lc.TextDocumentPositionParams, Vec<lc.Locati
64 64
65export interface JoinLinesParams { 65export interface JoinLinesParams {
66 textDocument: lc.TextDocumentIdentifier; 66 textDocument: lc.TextDocumentIdentifier;
67 range: lc.Range; 67 ranges: lc.Range[];
68} 68}
69export const joinLines = request<JoinLinesParams, SourceChange>("joinLines"); 69export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], unknown>('experimental/joinLines');
70 70
71 71
72export const onEnter = request<lc.TextDocumentPositionParams, Option<SourceChange>>("onEnter"); 72export const onEnter = request<lc.TextDocumentPositionParams, Option<lc.WorkspaceEdit>>("onEnter");
73 73
74export interface RunnablesParams { 74export interface RunnablesParams {
75 textDocument: lc.TextDocumentIdentifier; 75 textDocument: lc.TextDocumentIdentifier;
@@ -112,7 +112,7 @@ export interface SsrParams {
112 query: string; 112 query: string;
113 parseOnly: boolean; 113 parseOnly: boolean;
114} 114}
115export const ssr = request<SsrParams, SourceChange>("ssr"); 115export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, unknown>('experimental/ssr');
116 116
117 117
118export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations"); 118export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations");
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index b8e8860ba..2e9fcf07c 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -57,6 +57,7 @@ fn check_todo(path: &Path, text: &str) {
57 "tests/generated.rs", 57 "tests/generated.rs",
58 "handlers/add_missing_impl_members.rs", 58 "handlers/add_missing_impl_members.rs",
59 "handlers/add_function.rs", 59 "handlers/add_function.rs",
60 "handlers/add_turbo_fish.rs",
60 // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make. 61 // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make.
61 "ast/make.rs", 62 "ast/make.rs",
62 ]; 63 ];