aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/Cargo.toml2
-rw-r--r--crates/ra_assists/src/assist_ctx.rs140
-rw-r--r--crates/ra_assists/src/doc_tests.rs12
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs53
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs (renamed from crates/ra_assists/src/assists/add_custom_impl.rs)4
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs (renamed from crates/ra_assists/src/assists/add_derive.rs)3
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs (renamed from crates/ra_assists/src/assists/add_explicit_type.rs)58
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs (renamed from crates/ra_assists/src/assists/add_impl.rs)4
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs (renamed from crates/ra_assists/src/assists/add_missing_impl_members.rs)7
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs (renamed from crates/ra_assists/src/assists/add_new.rs)21
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs (renamed from crates/ra_assists/src/assists/apply_demorgan.rs)20
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs293
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs (renamed from crates/ra_assists/src/assists/change_visibility.rs)7
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs (renamed from crates/ra_assists/src/assists/early_return.rs)14
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs (renamed from crates/ra_assists/src/assists/fill_match_arms.rs)2
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs (renamed from crates/ra_assists/src/assists/flip_binexpr.rs)3
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs (renamed from crates/ra_assists/src/assists/flip_comma.rs)3
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs (renamed from crates/ra_assists/src/assists/flip_trait_bound.rs)3
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs (renamed from crates/ra_assists/src/assists/inline_local_variable.rs)18
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs (renamed from crates/ra_assists/src/assists/introduce_variable.rs)3
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs (renamed from crates/ra_assists/src/assists/invert_if.rs)33
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs (renamed from crates/ra_assists/src/assists/merge_match_arms.rs)130
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs (renamed from crates/ra_assists/src/assists/move_bounds.rs)3
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs (renamed from crates/ra_assists/src/assists/move_guard.rs)5
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs (renamed from crates/ra_assists/src/assists/raw_string.rs)9
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs (renamed from crates/ra_assists/src/assists/remove_dbg.rs)3
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs (renamed from crates/ra_assists/src/assists/replace_if_let_with_match.rs)52
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs (renamed from crates/ra_assists/src/assists/add_import.rs)124
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs (renamed from crates/ra_assists/src/assists/split_import.rs)3
-rw-r--r--crates/ra_assists/src/lib.rs209
-rw-r--r--crates/ra_assists/src/test_db.rs45
-rw-r--r--crates/ra_assists/src/utils.rs27
32 files changed, 841 insertions, 472 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index 0d2109e4e..6973038d4 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -16,6 +16,8 @@ either = "1.5"
16ra_syntax = { path = "../ra_syntax" } 16ra_syntax = { path = "../ra_syntax" }
17ra_text_edit = { path = "../ra_text_edit" } 17ra_text_edit = { path = "../ra_text_edit" }
18ra_fmt = { path = "../ra_fmt" } 18ra_fmt = { path = "../ra_fmt" }
19ra_prof = { path = "../ra_prof" }
19ra_db = { path = "../ra_db" } 20ra_db = { path = "../ra_db" }
21ra_ide_db = { path = "../ra_ide_db" }
20hir = { path = "../ra_hir", package = "ra_hir" } 22hir = { path = "../ra_hir", package = "ra_hir" }
21test_utils = { path = "../test_utils" } 23test_utils = { path = "../test_utils" }
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 43f0d664b..5aab5fb8b 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -1,8 +1,8 @@
1//! This module defines `AssistCtx` -- the API surface that is exposed to assists. 1//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
2use either::Either; 2use hir::{InFile, SourceAnalyzer, SourceBinder};
3use hir::{db::HirDatabase, InFile, SourceAnalyzer, SourceBinder}; 3use ra_db::{FileRange, SourceDatabase};
4use ra_db::FileRange;
5use ra_fmt::{leading_indent, reindent}; 4use ra_fmt::{leading_indent, reindent};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::{self, find_covering_element, find_node_at_offset}, 7 algo::{self, find_covering_element, find_node_at_offset},
8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit, 8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit,
@@ -10,14 +10,40 @@ use ra_syntax::{
10}; 10};
11use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
12 12
13use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; 13use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14 14
15#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
16pub(crate) enum Assist { 16pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
17 Unresolved { label: AssistLabel }, 17
18 Resolved { assist: ResolvedAssist }, 18#[derive(Clone, Debug)]
19pub(crate) struct AssistInfo {
20 pub(crate) label: AssistLabel,
21 pub(crate) group_label: Option<GroupLabel>,
22 pub(crate) action: Option<AssistAction>,
23}
24
25impl AssistInfo {
26 fn new(label: AssistLabel) -> AssistInfo {
27 AssistInfo { label, group_label: None, action: None }
28 }
29
30 fn resolved(self, action: AssistAction) -> AssistInfo {
31 AssistInfo { action: Some(action), ..self }
32 }
33
34 fn with_group(self, group_label: GroupLabel) -> AssistInfo {
35 AssistInfo { group_label: Some(group_label), ..self }
36 }
37
38 pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
39 let label = self.label;
40 let group_label = self.group_label;
41 self.action.map(|action| ResolvedAssist { label, group_label, action })
42 }
19} 43}
20 44
45pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;
46
21/// `AssistCtx` allows to apply an assist or check if it could be applied. 47/// `AssistCtx` allows to apply an assist or check if it could be applied.
22/// 48///
23/// Assists use a somewhat over-engineered approach, given the current needs. The 49/// Assists use a somewhat over-engineered approach, given the current needs. The
@@ -49,14 +75,14 @@ pub(crate) enum Assist {
49/// moment, because the LSP API is pretty awkward in this place, and it's much 75/// moment, because the LSP API is pretty awkward in this place, and it's much
50/// easier to just compute the edit eagerly :-) 76/// easier to just compute the edit eagerly :-)
51#[derive(Debug)] 77#[derive(Debug)]
52pub(crate) struct AssistCtx<'a, DB> { 78pub(crate) struct AssistCtx<'a> {
53 pub(crate) db: &'a DB, 79 pub(crate) db: &'a RootDatabase,
54 pub(crate) frange: FileRange, 80 pub(crate) frange: FileRange,
55 source_file: SourceFile, 81 source_file: SourceFile,
56 should_compute_edit: bool, 82 should_compute_edit: bool,
57} 83}
58 84
59impl<'a, DB> Clone for AssistCtx<'a, DB> { 85impl Clone for AssistCtx<'_> {
60 fn clone(&self) -> Self { 86 fn clone(&self) -> Self {
61 AssistCtx { 87 AssistCtx {
62 db: self.db, 88 db: self.db,
@@ -67,15 +93,10 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> {
67 } 93 }
68} 94}
69 95
70impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { 96impl<'a> AssistCtx<'a> {
71 pub(crate) fn with_ctx<F, T>(db: &DB, frange: FileRange, should_compute_edit: bool, f: F) -> T 97 pub fn new(db: &RootDatabase, frange: FileRange, should_compute_edit: bool) -> AssistCtx {
72 where
73 F: FnOnce(AssistCtx<DB>) -> T,
74 {
75 let parse = db.parse(frange.file_id); 98 let parse = db.parse(frange.file_id);
76 99 AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit }
77 let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit };
78 f(ctx)
79 } 100 }
80 101
81 pub(crate) fn add_assist( 102 pub(crate) fn add_assist(
@@ -84,48 +105,23 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
84 label: impl Into<String>, 105 label: impl Into<String>,
85 f: impl FnOnce(&mut ActionBuilder), 106 f: impl FnOnce(&mut ActionBuilder),
86 ) -> Option<Assist> { 107 ) -> Option<Assist> {
87 let label = AssistLabel { label: label.into(), id }; 108 let label = AssistLabel::new(label.into(), id);
88 assert!(label.label.chars().nth(0).unwrap().is_uppercase());
89 109
90 let assist = if self.should_compute_edit { 110 let mut info = AssistInfo::new(label);
111 if self.should_compute_edit {
91 let action = { 112 let action = {
92 let mut edit = ActionBuilder::default(); 113 let mut edit = ActionBuilder::default();
93 f(&mut edit); 114 f(&mut edit);
94 edit.build() 115 edit.build()
95 }; 116 };
96 Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } } 117 info = info.resolved(action)
97 } else {
98 Assist::Unresolved { label }
99 }; 118 };
100 119
101 Some(assist) 120 Some(Assist(vec![info]))
102 } 121 }
103 122
104 #[allow(dead_code)] // will be used for auto import assist with multiple actions 123 pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> {
105 pub(crate) fn add_assist_group( 124 AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() }
106 self,
107 id: AssistId,
108 label: impl Into<String>,
109 f: impl FnOnce() -> Vec<ActionBuilder>,
110 ) -> Option<Assist> {
111 let label = AssistLabel { label: label.into(), id };
112 let assist = if self.should_compute_edit {
113 let actions = f();
114 assert!(!actions.is_empty(), "Assist cannot have no");
115
116 Assist::Resolved {
117 assist: ResolvedAssist {
118 label,
119 action_data: Either::Right(
120 actions.into_iter().map(ActionBuilder::build).collect(),
121 ),
122 },
123 }
124 } else {
125 Assist::Unresolved { label }
126 };
127
128 Some(assist)
129 } 125 }
130 126
131 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { 127 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
@@ -142,7 +138,7 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
142 pub(crate) fn covering_element(&self) -> SyntaxElement { 138 pub(crate) fn covering_element(&self) -> SyntaxElement {
143 find_covering_element(self.source_file.syntax(), self.frange.range) 139 find_covering_element(self.source_file.syntax(), self.frange.range)
144 } 140 }
145 pub(crate) fn source_binder(&self) -> SourceBinder<'a, DB> { 141 pub(crate) fn source_binder(&self) -> SourceBinder<'a, RootDatabase> {
146 SourceBinder::new(self.db) 142 SourceBinder::new(self.db)
147 } 143 }
148 pub(crate) fn source_analyzer( 144 pub(crate) fn source_analyzer(
@@ -159,21 +155,48 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
159 } 155 }
160} 156}
161 157
158pub(crate) struct AssistGroup<'a> {
159 ctx: AssistCtx<'a>,
160 group_name: String,
161 assists: Vec<AssistInfo>,
162}
163
164impl<'a> AssistGroup<'a> {
165 pub(crate) fn add_assist(
166 &mut self,
167 id: AssistId,
168 label: impl Into<String>,
169 f: impl FnOnce(&mut ActionBuilder),
170 ) {
171 let label = AssistLabel::new(label.into(), id);
172
173 let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone()));
174 if self.ctx.should_compute_edit {
175 let action = {
176 let mut edit = ActionBuilder::default();
177 f(&mut edit);
178 edit.build()
179 };
180 info = info.resolved(action)
181 };
182
183 self.assists.push(info)
184 }
185
186 pub(crate) fn finish(self) -> Option<Assist> {
187 assert!(!self.assists.is_empty());
188 Some(Assist(self.assists))
189 }
190}
191
162#[derive(Default)] 192#[derive(Default)]
163pub(crate) struct ActionBuilder { 193pub(crate) struct ActionBuilder {
164 edit: TextEditBuilder, 194 edit: TextEditBuilder,
165 cursor_position: Option<TextUnit>, 195 cursor_position: Option<TextUnit>,
166 target: Option<TextRange>, 196 target: Option<TextRange>,
167 label: Option<String>,
168} 197}
169 198
170impl ActionBuilder { 199impl ActionBuilder {
171 #[allow(dead_code)]
172 /// Adds a custom label to the action, if it needs to be different from the assist label
173 pub(crate) fn label(&mut self, label: impl Into<String>) {
174 self.label = Some(label.into())
175 }
176
177 /// Replaces specified `range` of text with a given string. 200 /// Replaces specified `range` of text with a given string.
178 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { 201 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
179 self.edit.replace(range, replace_with.into()) 202 self.edit.replace(range, replace_with.into())
@@ -232,7 +255,6 @@ impl ActionBuilder {
232 edit: self.edit.finish(), 255 edit: self.edit.finish(),
233 cursor_position: self.cursor_position, 256 cursor_position: self.cursor_position,
234 target: self.target, 257 target: self.target,
235 label: self.label,
236 } 258 }
237 } 259 }
238} 260}
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs
index 5dc1ee233..c0f9bc1fb 100644
--- a/crates/ra_assists/src/doc_tests.rs
+++ b/crates/ra_assists/src/doc_tests.rs
@@ -5,24 +5,24 @@
5 5
6mod generated; 6mod generated;
7 7
8use ra_db::{fixture::WithFixture, FileRange}; 8use ra_db::FileRange;
9use test_utils::{assert_eq_text, extract_range_or_offset}; 9use test_utils::{assert_eq_text, extract_range_or_offset};
10 10
11use crate::test_db::TestDB; 11use crate::resolved_assists;
12 12
13fn check(assist_id: &str, before: &str, after: &str) { 13fn check(assist_id: &str, before: &str, after: &str) {
14 let (selection, before) = extract_range_or_offset(before); 14 let (selection, before) = extract_range_or_offset(before);
15 let (db, file_id) = TestDB::with_single_file(&before); 15 let (db, file_id) = crate::helpers::with_single_file(&before);
16 let frange = FileRange { file_id, range: selection.into() }; 16 let frange = FileRange { file_id, range: selection.into() };
17 17
18 let assist = crate::assists(&db, frange) 18 let assist = resolved_assists(&db, frange)
19 .into_iter() 19 .into_iter()
20 .find(|assist| assist.label.id.0 == assist_id) 20 .find(|assist| assist.label.id.0 == assist_id)
21 .unwrap_or_else(|| { 21 .unwrap_or_else(|| {
22 panic!( 22 panic!(
23 "\n\nAssist is not applicable: {}\nAvailable assists: {}", 23 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
24 assist_id, 24 assist_id,
25 crate::assists(&db, frange) 25 resolved_assists(&db, frange)
26 .into_iter() 26 .into_iter()
27 .map(|assist| assist.label.id.0) 27 .map(|assist| assist.label.id.0)
28 .collect::<Vec<_>>() 28 .collect::<Vec<_>>()
@@ -30,6 +30,6 @@ fn check(assist_id: &str, before: &str, after: &str) {
30 ) 30 )
31 }); 31 });
32 32
33 let actual = assist.get_first_action().edit.apply(&before); 33 let actual = assist.action.edit.apply(&before);
34 assert_eq_text!(after, &actual); 34 assert_eq_text!(after, &actual);
35} 35}
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 7d84dc8fb..4ab09b167 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -1,4 +1,4 @@
1//! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` 1//! Generated file, do not edit by hand, see `xtask/src/codegen`
2 2
3use super::check; 3use super::check;
4 4
@@ -161,21 +161,6 @@ impl Trait<u32> for () {
161} 161}
162 162
163#[test] 163#[test]
164fn doctest_add_import() {
165 check(
166 "add_import",
167 r#####"
168fn process(map: std::collections::<|>HashMap<String, String>) {}
169"#####,
170 r#####"
171use std::collections::HashMap;
172
173fn process(map: HashMap<String, String>) {}
174"#####,
175 )
176}
177
178#[test]
179fn doctest_add_new() { 164fn doctest_add_new() {
180 check( 165 check(
181 "add_new", 166 "add_new",
@@ -215,6 +200,27 @@ fn main() {
215} 200}
216 201
217#[test] 202#[test]
203fn doctest_auto_import() {
204 check(
205 "auto_import",
206 r#####"
207fn main() {
208 let map = HashMap<|>::new();
209}
210pub mod std { pub mod collections { pub struct HashMap { } } }
211"#####,
212 r#####"
213use std::collections::HashMap;
214
215fn main() {
216 let map = HashMap::new();
217}
218pub mod std { pub mod collections { pub struct HashMap { } } }
219"#####,
220 )
221}
222
223#[test]
218fn doctest_change_visibility() { 224fn doctest_change_visibility() {
219 check( 225 check(
220 "change_visibility", 226 "change_visibility",
@@ -571,6 +577,21 @@ fn handle(action: Action) {
571} 577}
572 578
573#[test] 579#[test]
580fn doctest_replace_qualified_name_with_use() {
581 check(
582 "replace_qualified_name_with_use",
583 r#####"
584fn process(map: std::collections::<|>HashMap<String, String>) {}
585"#####,
586 r#####"
587use std::collections::HashMap;
588
589fn process(map: HashMap<String, String>) {}
590"#####,
591 )
592}
593
594#[test]
574fn doctest_split_import() { 595fn doctest_split_import() {
575 check( 596 check(
576 "split_import", 597 "split_import",
diff --git a/crates/ra_assists/src/assists/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index f91034967..7fdd816bf 100644
--- a/crates/ra_assists/src/assists/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -1,7 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{Assist, AssistCtx, AssistId};
4use hir::db::HirDatabase; 4
5use join_to_string::join; 5use join_to_string::join;
6use ra_syntax::{ 6use ra_syntax::{
7 ast::{self, AstNode}, 7 ast::{self, AstNode},
@@ -29,7 +29,7 @@ const DERIVE_TRAIT: &str = "derive";
29// 29//
30// } 30// }
31// ``` 31// ```
32pub(crate) fn add_custom_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 32pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
33 let input = ctx.find_node_at_offset::<ast::AttrInput>()?; 33 let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
34 let attr = input.syntax().parent().and_then(ast::Attr::cast)?; 34 let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
35 35
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index 6d9af3905..b0d1a0a80 100644
--- a/crates/ra_assists/src/assists/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner}, 2 ast::{self, AstNode, AttrsOwner},
4 SyntaxKind::{COMMENT, WHITESPACE}, 3 SyntaxKind::{COMMENT, WHITESPACE},
@@ -25,7 +24,7 @@ use crate::{Assist, AssistCtx, AssistId};
25// y: u32, 24// y: u32,
26// } 25// }
27// ``` 26// ```
28pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
30 let node_start = derive_insertion_offset(&nominal)?; 29 let node_start = derive_insertion_offset(&nominal)?;
31 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| { 30 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| {
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index 38a351a54..2cb9d2f48 100644
--- a/crates/ra_assists/src/assists/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -1,7 +1,7 @@
1use hir::{db::HirDatabase, HirDisplay}; 1use hir::HirDisplay;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner}, 3 ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner},
4 TextRange, T, 4 TextRange,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{Assist, AssistCtx, AssistId};
@@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
21// let x: i32 = 92; 21// let x: i32 = 92;
22// } 22// }
23// ``` 23// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let expr = stmt.initializer()?; 26 let expr = stmt.initializer()?;
27 let pat = stmt.pat()?; 27 let pat = stmt.pat()?;
@@ -34,17 +34,21 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
34 // The binding must have a name 34 // The binding must have a name
35 let name = pat.name()?; 35 let name = pat.name()?;
36 let name_range = name.syntax().text_range(); 36 let name_range = name.syntax().text_range();
37 // Assist should only be applicable if cursor is between 'let' and '='
38 let stmt_range = stmt.syntax().text_range(); 37 let stmt_range = stmt.syntax().text_range();
39 let eq_range = stmt.eq_token()?.text_range(); 38 let eq_range = stmt.eq_token()?.text_range();
39 // Assist should only be applicable if cursor is between 'let' and '='
40 let let_range = TextRange::from_to(stmt_range.start(), eq_range.start()); 40 let let_range = TextRange::from_to(stmt_range.start(), eq_range.start());
41 let cursor_in_range = ctx.frange.range.is_subrange(&let_range); 41 let cursor_in_range = ctx.frange.range.is_subrange(&let_range);
42 if !cursor_in_range { 42 if !cursor_in_range {
43 return None; 43 return None;
44 } 44 }
45 // Assist not applicable if the type has already been specified 45 // Assist not applicable if the type has already been specified
46 if stmt.syntax().children_with_tokens().any(|child| child.kind() == T![:]) { 46 // and it has no placeholders
47 return None; 47 let ascribed_ty = stmt.ascribed_type();
48 if let Some(ref ty) = ascribed_ty {
49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
50 return None;
51 }
48 } 52 }
49 // Infer type 53 // Infer type
50 let db = ctx.db; 54 let db = ctx.db;
@@ -60,7 +64,11 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
60 format!("Insert explicit type '{}'", ty.display(db)), 64 format!("Insert explicit type '{}'", ty.display(db)),
61 |edit| { 65 |edit| {
62 edit.target(pat_range); 66 edit.target(pat_range);
63 edit.insert(name_range.end(), format!(": {}", ty.display(db))); 67 if let Some(ascribed_ty) = ascribed_ty {
68 edit.replace(ascribed_ty.syntax().text_range(), format!("{}", ty.display(db)));
69 } else {
70 edit.insert(name_range.end(), format!(": {}", ty.display(db)));
71 }
64 }, 72 },
65 ) 73 )
66} 74}
@@ -86,6 +94,40 @@ mod tests {
86 } 94 }
87 95
88 #[test] 96 #[test]
97 fn add_explicit_type_works_for_underscore() {
98 check_assist(
99 add_explicit_type,
100 "fn f() { let a<|>: _ = 1; }",
101 "fn f() { let a<|>: i32 = 1; }",
102 );
103 }
104
105 #[test]
106 fn add_explicit_type_works_for_nested_underscore() {
107 check_assist(
108 add_explicit_type,
109 r#"
110 enum Option<T> {
111 Some(T),
112 None
113 }
114
115 fn f() {
116 let a<|>: Option<_> = Option::Some(1);
117 }"#,
118 r#"
119 enum Option<T> {
120 Some(T),
121 None
122 }
123
124 fn f() {
125 let a<|>: Option<i32> = Option::Some(1);
126 }"#,
127 );
128 }
129
130 #[test]
89 fn add_explicit_type_works_for_macro_call() { 131 fn add_explicit_type_works_for_macro_call() {
90 check_assist( 132 check_assist(
91 add_explicit_type, 133 add_explicit_type,
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index 4b326c837..241b085fd 100644
--- a/crates/ra_assists/src/assists/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -1,5 +1,5 @@
1use format_buf::format; 1use format_buf::format;
2use hir::db::HirDatabase; 2
3use join_to_string::join; 3use join_to_string::join;
4use ra_syntax::{ 4use ra_syntax::{
5 ast::{self, AstNode, NameOwner, TypeParamsOwner}, 5 ast::{self, AstNode, NameOwner, TypeParamsOwner},
@@ -27,7 +27,7 @@ use crate::{Assist, AssistCtx, AssistId};
27// 27//
28// } 28// }
29// ``` 29// ```
30pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 30pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
31 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 31 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
32 let name = nominal.name()?; 32 let name = nominal.name()?;
33 ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| { 33 ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| {
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index 5bb937bde..448697d31 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -43,7 +43,7 @@ enum AddMissingImplMembersMode {
43// 43//
44// } 44// }
45// ``` 45// ```
46pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 46pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> {
47 add_missing_impl_members_inner( 47 add_missing_impl_members_inner(
48 ctx, 48 ctx,
49 AddMissingImplMembersMode::NoDefaultMethods, 49 AddMissingImplMembersMode::NoDefaultMethods,
@@ -84,7 +84,7 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti
84// 84//
85// } 85// }
86// ``` 86// ```
87pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 87pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> {
88 add_missing_impl_members_inner( 88 add_missing_impl_members_inner(
89 ctx, 89 ctx,
90 AddMissingImplMembersMode::DefaultMethodsOnly, 90 AddMissingImplMembersMode::DefaultMethodsOnly,
@@ -94,11 +94,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> O
94} 94}
95 95
96fn add_missing_impl_members_inner( 96fn add_missing_impl_members_inner(
97 ctx: AssistCtx<impl HirDatabase>, 97 ctx: AssistCtx,
98 mode: AddMissingImplMembersMode, 98 mode: AddMissingImplMembersMode,
99 assist_id: &'static str, 99 assist_id: &'static str,
100 label: &'static str, 100 label: &'static str,
101) -> Option<Assist> { 101) -> Option<Assist> {
102 let _p = ra_prof::profile("add_missing_impl_members_inner");
102 let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?; 103 let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?;
103 let impl_item_list = impl_node.item_list()?; 104 let impl_item_list = impl_node.item_list()?;
104 105
diff --git a/crates/ra_assists/src/assists/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index 8db63f762..2701eddb8 100644
--- a/crates/ra_assists/src/assists/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -1,5 +1,5 @@
1use format_buf::format; 1use format_buf::format;
2use hir::{db::HirDatabase, InFile}; 2use hir::{Adt, InFile};
3use join_to_string::join; 3use join_to_string::join;
4use ra_syntax::{ 4use ra_syntax::{
5 ast::{ 5 ast::{
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 31// }
32// 32//
33// ``` 33// ```
34pub(crate) fn add_new(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 34pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
35 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; 35 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
36 36
37 // We want to only apply this to non-union structs with named fields 37 // We want to only apply this to non-union structs with named fields
@@ -128,26 +128,29 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
128// 128//
129// FIXME: change the new fn checking to a more semantic approach when that's more 129// FIXME: change the new fn checking to a more semantic approach when that's more
130// viable (e.g. we process proc macros, etc) 130// viable (e.g. we process proc macros, etc)
131fn find_struct_impl( 131fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplBlock>> {
132 ctx: &AssistCtx<impl HirDatabase>,
133 strukt: &ast::StructDef,
134) -> Option<Option<ast::ImplBlock>> {
135 let db = ctx.db; 132 let db = ctx.db;
136 let module = strukt.syntax().ancestors().find(|node| { 133 let module = strukt.syntax().ancestors().find(|node| {
137 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) 134 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
138 })?; 135 })?;
139 let mut sb = ctx.source_binder(); 136 let mut sb = ctx.source_binder();
140 137
141 let struct_ty = { 138 let struct_def = {
142 let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() }; 139 let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() };
143 sb.to_def(src)?.ty(db) 140 sb.to_def(src)?
144 }; 141 };
145 142
146 let block = module.descendants().filter_map(ast::ImplBlock::cast).find_map(|impl_blk| { 143 let block = module.descendants().filter_map(ast::ImplBlock::cast).find_map(|impl_blk| {
147 let src = InFile { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() }; 144 let src = InFile { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() };
148 let blk = sb.to_def(src)?; 145 let blk = sb.to_def(src)?;
149 146
150 let same_ty = blk.target_ty(db) == struct_ty; 147 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
148 // (we currently use the wrong type parameter)
149 // also we wouldn't want to use e.g. `impl S<u32>`
150 let same_ty = match blk.target_ty(db).as_adt() {
151 Some(def) => def == Adt::Struct(struct_def),
152 None => false,
153 };
151 let not_trait_impl = blk.target_trait(db).is_none(); 154 let not_trait_impl = blk.target_trait(db).is_none();
152 155
153 if !(same_ty && not_trait_impl) { 156 if !(same_ty && not_trait_impl) {
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index 666dce4e6..239807e24 100644
--- a/crates/ra_assists/src/assists/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -1,8 +1,6 @@
1use super::invert_if::invert_boolean_expression;
2use hir::db::HirDatabase;
3use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
4 2
5use crate::{Assist, AssistCtx, AssistId}; 3use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
6 4
7// Assist: apply_demorgan 5// Assist: apply_demorgan
8// 6//
@@ -23,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
23// if !(x == 4 && y) {} 21// if !(x == 4 && y) {}
24// } 22// }
25// ``` 23// ```
26pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
27 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; 25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
28 let op = expr.op_kind()?; 26 let op = expr.op_kind()?;
29 let op_range = expr.op_token()?.text_range(); 27 let op_range = expr.op_token()?.text_range();
@@ -32,12 +30,14 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
32 if !cursor_in_range { 30 if !cursor_in_range {
33 return None; 31 return None;
34 } 32 }
33
35 let lhs = expr.lhs()?; 34 let lhs = expr.lhs()?;
36 let lhs_range = lhs.syntax().text_range(); 35 let lhs_range = lhs.syntax().text_range();
36 let not_lhs = invert_boolean_expression(lhs);
37
37 let rhs = expr.rhs()?; 38 let rhs = expr.rhs()?;
38 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
39 let not_lhs = invert_boolean_expression(&lhs)?; 40 let not_rhs = invert_boolean_expression(rhs);
40 let not_rhs = invert_boolean_expression(&rhs)?;
41 41
42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| { 42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| {
43 edit.target(op_range); 43 edit.target(op_range);
@@ -78,12 +78,12 @@ mod tests {
78 } 78 }
79 79
80 #[test] 80 #[test]
81 fn demorgan_doesnt_apply_with_cursor_not_on_op() { 81 fn demorgan_general_case() {
82 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }") 82 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }")
83 } 83 }
84 84
85 #[test] 85 #[test]
86 fn demorgan_doesnt_apply_when_operands_arent_negated_already() { 86 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
87 check_assist_not_applicable(apply_demorgan, "fn f() { x ||<|> x }") 87 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
88 } 88 }
89} 89}
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
new file mode 100644
index 000000000..1fb701da5
--- /dev/null
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -0,0 +1,293 @@
1use ra_ide_db::imports_locator::ImportsLocator;
2use ra_syntax::ast::{self, AstNode};
3
4use crate::{
5 assist_ctx::{Assist, AssistCtx},
6 insert_use_statement, AssistId,
7};
8use std::collections::BTreeSet;
9
10// Assist: auto_import
11//
12// If the name is unresolved, provides all possible imports for it.
13//
14// ```
15// fn main() {
16// let map = HashMap<|>::new();
17// }
18// # pub mod std { pub mod collections { pub struct HashMap { } } }
19// ```
20// ->
21// ```
22// use std::collections::HashMap;
23//
24// fn main() {
25// let map = HashMap::new();
26// }
27// # pub mod std { pub mod collections { pub struct HashMap { } } }
28// ```
29pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
30 let path_under_caret: ast::Path = ctx.find_node_at_offset()?;
31 if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
32 return None;
33 }
34
35 let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast);
36 let position = match module.and_then(|it| it.item_list()) {
37 Some(item_list) => item_list.syntax().clone(),
38 None => {
39 let current_file =
40 path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?;
41 current_file.syntax().clone()
42 }
43 };
44 let source_analyzer = ctx.source_analyzer(&position, None);
45 let module_with_name_to_import = source_analyzer.module()?;
46
47 let name_ref_to_import =
48 path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?;
49 if source_analyzer
50 .resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?)
51 .is_some()
52 {
53 return None;
54 }
55
56 let name_to_import = name_ref_to_import.syntax().to_string();
57 let proposed_imports = ImportsLocator::new(ctx.db)
58 .find_imports(&name_to_import)
59 .into_iter()
60 .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def))
61 .filter(|use_path| !use_path.segments.is_empty())
62 .take(20)
63 .collect::<BTreeSet<_>>();
64
65 if proposed_imports.is_empty() {
66 return None;
67 }
68
69 let mut group = ctx.add_assist_group(format!("Import {}", name_to_import));
70 for import in proposed_imports {
71 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
72 edit.target(path_under_caret.syntax().text_range());
73 insert_use_statement(
74 &position,
75 path_under_caret.syntax(),
76 &import,
77 edit.text_edit_builder(),
78 );
79 });
80 }
81 group.finish()
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
87
88 use super::*;
89
90 #[test]
91 fn applicable_when_found_an_import() {
92 check_assist(
93 auto_import,
94 r"
95 <|>PubStruct
96
97 pub mod PubMod {
98 pub struct PubStruct;
99 }
100 ",
101 r"
102 <|>use PubMod::PubStruct;
103
104 PubStruct
105
106 pub mod PubMod {
107 pub struct PubStruct;
108 }
109 ",
110 );
111 }
112
113 #[test]
114 fn auto_imports_are_merged() {
115 check_assist(
116 auto_import,
117 r"
118 use PubMod::PubStruct1;
119
120 struct Test {
121 test: Pub<|>Struct2<u8>,
122 }
123
124 pub mod PubMod {
125 pub struct PubStruct1;
126 pub struct PubStruct2<T> {
127 _t: T,
128 }
129 }
130 ",
131 r"
132 use PubMod::{PubStruct2, PubStruct1};
133
134 struct Test {
135 test: Pub<|>Struct2<u8>,
136 }
137
138 pub mod PubMod {
139 pub struct PubStruct1;
140 pub struct PubStruct2<T> {
141 _t: T,
142 }
143 }
144 ",
145 );
146 }
147
148 #[test]
149 fn applicable_when_found_multiple_imports() {
150 check_assist(
151 auto_import,
152 r"
153 PubSt<|>ruct
154
155 pub mod PubMod1 {
156 pub struct PubStruct;
157 }
158 pub mod PubMod2 {
159 pub struct PubStruct;
160 }
161 pub mod PubMod3 {
162 pub struct PubStruct;
163 }
164 ",
165 r"
166 use PubMod1::PubStruct;
167
168 PubSt<|>ruct
169
170 pub mod PubMod1 {
171 pub struct PubStruct;
172 }
173 pub mod PubMod2 {
174 pub struct PubStruct;
175 }
176 pub mod PubMod3 {
177 pub struct PubStruct;
178 }
179 ",
180 );
181 }
182
183 #[test]
184 fn not_applicable_for_already_imported_types() {
185 check_assist_not_applicable(
186 auto_import,
187 r"
188 use PubMod::PubStruct;
189
190 PubStruct<|>
191
192 pub mod PubMod {
193 pub struct PubStruct;
194 }
195 ",
196 );
197 }
198
199 #[test]
200 fn not_applicable_for_types_with_private_paths() {
201 check_assist_not_applicable(
202 auto_import,
203 r"
204 PrivateStruct<|>
205
206 pub mod PubMod {
207 struct PrivateStruct;
208 }
209 ",
210 );
211 }
212
213 #[test]
214 fn not_applicable_when_no_imports_found() {
215 check_assist_not_applicable(
216 auto_import,
217 "
218 PubStruct<|>",
219 );
220 }
221
222 #[test]
223 fn not_applicable_in_import_statements() {
224 check_assist_not_applicable(
225 auto_import,
226 r"
227 use PubStruct<|>;
228
229 pub mod PubMod {
230 pub struct PubStruct;
231 }",
232 );
233 }
234
235 #[test]
236 fn function_import() {
237 check_assist(
238 auto_import,
239 r"
240 test_function<|>
241
242 pub mod PubMod {
243 pub fn test_function() {};
244 }
245 ",
246 r"
247 use PubMod::test_function;
248
249 test_function<|>
250
251 pub mod PubMod {
252 pub fn test_function() {};
253 }
254 ",
255 );
256 }
257
258 #[test]
259 fn auto_import_target() {
260 check_assist_target(
261 auto_import,
262 r"
263 struct AssistInfo {
264 group_label: Option<<|>GroupLabel>,
265 }
266
267 mod m { pub struct GroupLabel; }
268 ",
269 "GroupLabel",
270 )
271 }
272
273 #[test]
274 fn not_applicable_when_path_start_is_imported() {
275 check_assist_not_applicable(
276 auto_import,
277 r"
278 pub mod mod1 {
279 pub mod mod2 {
280 pub mod mod3 {
281 pub struct TestStruct;
282 }
283 }
284 }
285
286 use mod1::mod2;
287 fn main() {
288 mod2::mod3::TestStruct<|>
289 }
290 ",
291 );
292 }
293}
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index fd766bb46..f325b6f92 100644
--- a/crates/ra_assists/src/assists/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast::{self, NameOwner, VisibilityOwner}, 2 ast::{self, NameOwner, VisibilityOwner},
4 AstNode, 3 AstNode,
@@ -22,14 +21,14 @@ use crate::{Assist, AssistCtx, AssistId};
22// ``` 21// ```
23// pub(crate) fn frobnicate() {} 22// pub(crate) fn frobnicate() {}
24// ``` 23// ```
25pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> {
26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { 25 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
27 return change_vis(ctx, vis); 26 return change_vis(ctx, vis);
28 } 27 }
29 add_vis(ctx) 28 add_vis(ctx)
30} 29}
31 30
32fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 31fn add_vis(ctx: AssistCtx) -> Option<Assist> {
33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { 32 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
34 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 33 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
35 _ => false, 34 _ => false,
@@ -75,7 +74,7 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
75 .unwrap_or_else(|| node.text_range().start()) 74 .unwrap_or_else(|| node.text_range().start())
76} 75}
77 76
78fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { 77fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
79 if vis.syntax().text() == "pub" { 78 if vis.syntax().text() == "pub" {
80 return ctx.add_assist( 79 return ctx.add_assist(
81 AssistId("change_visibility"), 80 AssistId("change_visibility"),
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 487ee9eef..22f88884f 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -1,6 +1,5 @@
1use std::{iter::once, ops::RangeInclusive}; 1use std::{iter::once, ops::RangeInclusive};
2 2
3use hir::db::HirDatabase;
4use ra_syntax::{ 3use ra_syntax::{
5 algo::replace_children, 4 algo::replace_children,
6 ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat}, 5 ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat},
@@ -11,6 +10,7 @@ use ra_syntax::{
11 10
12use crate::{ 11use crate::{
13 assist_ctx::{Assist, AssistCtx}, 12 assist_ctx::{Assist, AssistCtx},
13 utils::invert_boolean_expression,
14 AssistId, 14 AssistId,
15}; 15};
16 16
@@ -36,7 +36,7 @@ use crate::{
36// bar(); 36// bar();
37// } 37// }
38// ``` 38// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() { 41 if if_expr.else_branch().is_some() {
42 return None; 42 return None;
@@ -100,9 +100,13 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
100 let new_block = match if_let_pat { 100 let new_block = match if_let_pat {
101 None => { 101 None => {
102 // If. 102 // If.
103 let early_expression = &(early_expression.syntax().to_string() + ";"); 103 let new_expr = {
104 let new_expr = if_indent_level 104 let then_branch =
105 .increase_indent(make::if_expression(&cond_expr, early_expression)); 105 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
106 let cond = invert_boolean_expression(cond_expr);
107 let e = make::expr_if(cond, then_branch);
108 if_indent_level.increase_indent(e)
109 };
106 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 110 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
107 } 111 }
108 Some((path, bound_ident)) => { 112 Some((path, bound_ident)) => {
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 01758d23a..0908fc246 100644
--- a/crates/ra_assists/src/assists/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 31// }
32// } 32// }
33// ``` 33// ```
34pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 34pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; 35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
36 let match_arm_list = match_expr.match_arm_list()?; 36 let match_arm_list = match_expr.match_arm_list()?;
37 37
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 2074087cd..bfcc09e90 100644
--- a/crates/ra_assists/src/assists/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
3 2
4use crate::{Assist, AssistCtx, AssistId}; 3use crate::{Assist, AssistCtx, AssistId};
@@ -18,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
18// let _ = 2 + 90; 17// let _ = 2 + 90;
19// } 18// }
20// ``` 19// ```
21pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 20pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
22 let expr = ctx.find_node_at_offset::<BinExpr>()?; 21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
23 let lhs = expr.lhs()?.syntax().clone(); 22 let lhs = expr.lhs()?.syntax().clone();
24 let rhs = expr.rhs()?.syntax().clone(); 23 let rhs = expr.rhs()?.syntax().clone();
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index dd0c405ed..1dacf29f8 100644
--- a/crates/ra_assists/src/assists/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
3 2
4use crate::{Assist, AssistCtx, AssistId}; 3use crate::{Assist, AssistCtx, AssistId};
@@ -18,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
18// ((3, 4), (1, 2)); 17// ((3, 4), (1, 2));
19// } 18// }
20// ``` 19// ```
21pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 20pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
22 let comma = ctx.find_token_at_offset(T![,])?; 21 let comma = ctx.find_token_at_offset(T![,])?;
23 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
24 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
diff --git a/crates/ra_assists/src/assists/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index 50b3fa492..f56769624 100644
--- a/crates/ra_assists/src/assists/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 algo::non_trivia_sibling, 2 algo::non_trivia_sibling,
4 ast::{self, AstNode}, 3 ast::{self, AstNode},
@@ -18,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
18// ``` 17// ```
19// fn foo<T: Copy + Clone>() { } 18// fn foo<T: Copy + Clone>() { }
20// ``` 19// ```
21pub(crate) fn flip_trait_bound(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 20pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
22 // We want to replicate the behavior of `flip_binexpr` by only suggesting 21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
23 // the assist when the cursor is on a `+` 22 // the assist when the cursor is on a `+`
24 let plus = ctx.find_token_at_offset(T![+])?; 23 let plus = ctx.find_token_at_offset(T![+])?;
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index d0c5c3b8c..91b588243 100644
--- a/crates/ra_assists/src/assists/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast::{self, AstNode, AstToken}, 2 ast::{self, AstNode, AstToken},
4 TextRange, 3 TextRange,
@@ -23,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
23// (1 + 2) * 4; 22// (1 + 2) * 4;
24// } 23// }
25// ``` 24// ```
26pub(crate) fn inline_local_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
27 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; 26 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
28 let bind_pat = match let_stmt.pat()? { 27 let bind_pat = match let_stmt.pat()? {
29 ast::Pat::BindPat(pat) => pat, 28 ast::Pat::BindPat(pat) => pat,
@@ -47,6 +46,9 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<
47 }; 46 };
48 let analyzer = ctx.source_analyzer(bind_pat.syntax(), None); 47 let analyzer = ctx.source_analyzer(bind_pat.syntax(), None);
49 let refs = analyzer.find_all_refs(&bind_pat); 48 let refs = analyzer.find_all_refs(&bind_pat);
49 if refs.is_empty() {
50 return None;
51 };
50 52
51 let mut wrap_in_parens = vec![true; refs.len()]; 53 let mut wrap_in_parens = vec![true; refs.len()];
52 54
@@ -645,4 +647,16 @@ fn foo() {
645}", 647}",
646 ); 648 );
647 } 649 }
650
651 #[test]
652 fn test_not_applicable_if_variable_unused() {
653 check_assist_not_applicable(
654 inline_local_variable,
655 "
656fn foo() {
657 let <|>a = 0;
658}
659 ",
660 )
661 }
648} 662}
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index 19e211e0f..7312ce687 100644
--- a/crates/ra_assists/src/assists/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -1,5 +1,4 @@
1use format_buf::format; 1use format_buf::format;
2use hir::db::HirDatabase;
3use ra_syntax::{ 2use ra_syntax::{
4 ast::{self, AstNode}, 3 ast::{self, AstNode},
5 SyntaxKind::{ 4 SyntaxKind::{
@@ -28,7 +27,7 @@ use crate::{Assist, AssistCtx, AssistId};
28// var_name * 4; 27// var_name * 4;
29// } 28// }
30// ``` 29// ```
31pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 30pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
32 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
33 return None; 32 return None;
34 } 33 }
diff --git a/crates/ra_assists/src/assists/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index 16352c040..a594e7e0c 100644
--- a/crates/ra_assists/src/assists/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -1,8 +1,7 @@
1use hir::db::HirDatabase;
2use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
3use ra_syntax::T; 2use ra_syntax::T;
4 3
5use crate::{Assist, AssistCtx, AssistId}; 4use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
6 5
7// Assist: invert_if 6// Assist: invert_if
8// 7//
@@ -23,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
23// } 22// }
24// ``` 23// ```
25 24
26pub(crate) fn invert_if(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
27 let if_keyword = ctx.find_token_at_offset(T![if])?; 26 let if_keyword = ctx.find_token_at_offset(T![if])?;
28 let expr = ast::IfExpr::cast(if_keyword.parent())?; 27 let expr = ast::IfExpr::cast(if_keyword.parent())?;
29 let if_range = if_keyword.text_range(); 28 let if_range = if_keyword.text_range();
@@ -36,8 +35,8 @@ pub(crate) fn invert_if(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
36 let then_node = expr.then_branch()?.syntax().clone(); 35 let then_node = expr.then_branch()?.syntax().clone();
37 36
38 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { 37 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? {
39 let flip_cond = invert_boolean_expression(&cond)?;
40 let cond_range = cond.syntax().text_range(); 38 let cond_range = cond.syntax().text_range();
39 let flip_cond = invert_boolean_expression(cond);
41 let else_node = else_block.syntax(); 40 let else_node = else_block.syntax();
42 let else_range = else_node.text_range(); 41 let else_range = else_node.text_range();
43 let then_range = then_node.text_range(); 42 let then_range = then_node.text_range();
@@ -52,20 +51,6 @@ pub(crate) fn invert_if(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
52 None 51 None
53} 52}
54 53
55pub(crate) fn invert_boolean_expression(expr: &ast::Expr) -> Option<ast::Expr> {
56 match expr {
57 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
58 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
59 _ => None,
60 },
61 ast::Expr::PrefixExpr(pe) => match pe.op_kind()? {
62 ast::PrefixOp::Not => pe.expr(),
63 _ => None,
64 },
65 _ => None,
66 }
67}
68
69#[cfg(test)] 54#[cfg(test)]
70mod tests { 55mod tests {
71 use super::*; 56 use super::*;
@@ -91,12 +76,16 @@ mod tests {
91 } 76 }
92 77
93 #[test] 78 #[test]
94 fn invert_if_doesnt_apply_with_cursor_not_on_if() { 79 fn invert_if_general_case() {
95 check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }") 80 check_assist(
81 invert_if,
82 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
83 "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }",
84 )
96 } 85 }
97 86
98 #[test] 87 #[test]
99 fn invert_if_doesnt_apply_without_negated() { 88 fn invert_if_doesnt_apply_with_cursor_not_on_if() {
100 check_assist_not_applicable(invert_if, "fn f() { i<|>f cond { 3 * 2 } else { 1 } }") 89 check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
101 } 90 }
102} 91}
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index aca391155..670614dd8 100644
--- a/crates/ra_assists/src/assists/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -1,6 +1,11 @@
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; 1use std::iter::successors;
2use hir::db::HirDatabase; 2
3use ra_syntax::ast::{AstNode, MatchArm}; 3use ra_syntax::{
4 ast::{self, AstNode},
5 Direction, TextUnit,
6};
7
8use crate::{Assist, AssistCtx, AssistId, TextRange};
4 9
5// Assist: merge_match_arms 10// Assist: merge_match_arms
6// 11//
@@ -26,63 +31,81 @@ use ra_syntax::ast::{AstNode, MatchArm};
26// } 31// }
27// } 32// }
28// ``` 33// ```
29pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 34pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
30 let current_arm = ctx.find_node_at_offset::<MatchArm>()?; 35 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
31
32 // We check if the following match arm matches this one. We could, but don't,
33 // compare to the previous match arm as well.
34 let next = current_arm.syntax().next_sibling();
35 let next_arm = MatchArm::cast(next?)?;
36
37 // Don't try to handle arms with guards for now - can add support for this later 36 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() || next_arm.guard().is_some() { 37 if current_arm.guard().is_some() {
39 return None; 38 return None;
40 } 39 }
41
42 let current_expr = current_arm.expr()?; 40 let current_expr = current_arm.expr()?;
43 let next_expr = next_arm.expr()?; 41 let current_text_range = current_arm.syntax().text_range();
44 42
45 // Check for match arm equality by comparing lengths and then string contents 43 enum CursorPos {
46 if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { 44 InExpr(TextUnit),
47 return None; 45 InPat(TextUnit),
48 } 46 }
49 if current_expr.syntax().text() != next_expr.syntax().text() { 47 let cursor_pos = ctx.frange.range.start();
48 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
49 CursorPos::InExpr(current_text_range.end() - cursor_pos)
50 } else {
51 CursorPos::InPat(cursor_pos)
52 };
53
54 // We check if the following match arms match this one. We could, but don't,
55 // compare to the previous match arm as well.
56 let arms_to_merge = successors(Some(current_arm), next_arm)
57 .take_while(|arm| {
58 if arm.guard().is_some() {
59 return false;
60 }
61 match arm.expr() {
62 Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
63 None => false,
64 }
65 })
66 .collect::<Vec<_>>();
67
68 if arms_to_merge.len() <= 1 {
50 return None; 69 return None;
51 } 70 }
52 71
53 let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
54
55 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { 72 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| {
56 fn contains_placeholder(a: &MatchArm) -> bool { 73 let pats = if arms_to_merge.iter().any(contains_placeholder) {
57 a.pats().any(|x| match x {
58 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
59 _ => false,
60 })
61 }
62
63 let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
64 "_".into() 74 "_".into()
65 } else { 75 } else {
66 let ps: Vec<String> = current_arm 76 arms_to_merge
67 .pats() 77 .iter()
78 .flat_map(ast::MatchArm::pats)
68 .map(|x| x.syntax().to_string()) 79 .map(|x| x.syntax().to_string())
69 .chain(next_arm.pats().map(|x| x.syntax().to_string())) 80 .collect::<Vec<String>>()
70 .collect(); 81 .join(" | ")
71 ps.join(" | ")
72 }; 82 };
73 83
74 let arm = format!("{} => {}", pats, current_expr.syntax().text()); 84 let arm = format!("{} => {}", pats, current_expr.syntax().text());
75 let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
76 85
77 let start = current_arm.syntax().text_range().start(); 86 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
78 let end = next_arm.syntax().text_range().end(); 87 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
79 88
80 edit.target(current_arm.syntax().text_range()); 89 edit.target(current_text_range);
90 edit.set_cursor(match cursor_pos {
91 CursorPos::InExpr(back_offset) => start + TextUnit::from_usize(arm.len()) - back_offset,
92 CursorPos::InPat(offset) => offset,
93 });
81 edit.replace(TextRange::from_to(start, end), arm); 94 edit.replace(TextRange::from_to(start, end), arm);
82 edit.set_cursor(start + offset);
83 }) 95 })
84} 96}
85 97
98fn contains_placeholder(a: &ast::MatchArm) -> bool {
99 a.pats().any(|x| match x {
100 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
101 _ => false,
102 })
103}
104
105fn next_arm(arm: &ast::MatchArm) -> Option<ast::MatchArm> {
106 arm.syntax().siblings(Direction::Next).skip(1).find_map(ast::MatchArm::cast)
107}
108
86#[cfg(test)] 109#[cfg(test)]
87mod tests { 110mod tests {
88 use super::merge_match_arms; 111 use super::merge_match_arms;
@@ -185,6 +208,37 @@ mod tests {
185 } 208 }
186 209
187 #[test] 210 #[test]
211 fn merges_all_subsequent_arms() {
212 check_assist(
213 merge_match_arms,
214 r#"
215 enum X { A, B, C, D, E }
216
217 fn main() {
218 match X::A {
219 X::A<|> => 92,
220 X::B => 92,
221 X::C => 92,
222 X::D => 62,
223 _ => panic!(),
224 }
225 }
226 "#,
227 r#"
228 enum X { A, B, C, D, E }
229
230 fn main() {
231 match X::A {
232 X::A<|> | X::B | X::C => 92,
233 X::D => 62,
234 _ => panic!(),
235 }
236 }
237 "#,
238 )
239 }
240
241 #[test]
188 fn merge_match_arms_rejects_guards() { 242 fn merge_match_arms_rejects_guards() {
189 check_assist_not_applicable( 243 check_assist_not_applicable(
190 merge_match_arms, 244 merge_match_arms,
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index 355adddc3..90793b5fc 100644
--- a/crates/ra_assists/src/assists/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner}, 2 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner},
4 SyntaxElement, 3 SyntaxElement,
@@ -22,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
22// f(x) 21// f(x)
23// } 22// }
24// ``` 23// ```
25pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; 25 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
27 26
28 let mut type_params = type_param_list.type_params(); 27 let mut type_params = type_param_list.type_params();
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index 41a31e677..2b91ce7c4 100644
--- a/crates/ra_assists/src/assists/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast, 2 ast,
4 ast::{AstNode, AstToken, IfExpr, MatchArm}, 3 ast::{AstNode, AstToken, IfExpr, MatchArm},
@@ -32,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
32// } 31// }
33// } 32// }
34// ``` 33// ```
35pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 34pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
36 let match_arm = ctx.find_node_at_offset::<MatchArm>()?; 35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
37 let guard = match_arm.guard()?; 36 let guard = match_arm.guard()?;
38 let space_before_guard = guard.syntax().prev_sibling_or_token(); 37 let space_before_guard = guard.syntax().prev_sibling_or_token();
@@ -89,7 +88,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option
89// } 88// }
90// } 89// }
91// ``` 90// ```
92pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 91pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
93 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; 92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
94 let last_match_pat = match_arm.pats().last()?; 93 let last_match_pat = match_arm.pats().last()?;
95 94
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index e79c51673..2c0a1e126 100644
--- a/crates/ra_assists/src/assists/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast, AstToken, 2 ast, AstToken,
4 SyntaxKind::{RAW_STRING, STRING}, 3 SyntaxKind::{RAW_STRING, STRING},
@@ -22,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
22// r#"Hello, World!"#; 21// r#"Hello, World!"#;
23// } 22// }
24// ``` 23// ```
25pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 25 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 26 let value = token.value()?;
28 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { 27 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| {
@@ -51,7 +50,7 @@ pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
51// "Hello, \"World!\""; 50// "Hello, \"World!\"";
52// } 51// }
53// ``` 52// ```
54pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 53pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 54 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?; 55 let value = token.value()?;
57 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { 56 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| {
@@ -77,7 +76,7 @@ pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi
77// r##"Hello, World!"##; 76// r##"Hello, World!"##;
78// } 77// }
79// ``` 78// ```
80pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 79pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?; 80 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { 81 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| {
83 edit.target(token.text_range()); 82 edit.target(token.text_range());
@@ -101,7 +100,7 @@ pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
101// r"Hello, World!"; 100// r"Hello, World!";
102// } 101// }
103// ``` 102// ```
104pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 103pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
105 let token = ctx.find_token_at_offset(RAW_STRING)?; 104 let token = ctx.find_token_at_offset(RAW_STRING)?;
106 let text = token.text().as_str(); 105 let text = token.text().as_str();
107 if text.starts_with("r\"") { 106 if text.starts_with("r\"") {
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index cf211ab84..5085649b4 100644
--- a/crates/ra_assists/src/assists/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -1,4 +1,3 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ 1use ra_syntax::{
3 ast::{self, AstNode}, 2 ast::{self, AstNode},
4 TextUnit, T, 3 TextUnit, T,
@@ -21,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId};
21// 92; 20// 92;
22// } 21// }
23// ``` 22// ```
24pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 23pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
25 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
26 25
27 if !is_valid_macrocall(&macro_call, "dbg")? { 26 if !is_valid_macrocall(&macro_call, "dbg")? {
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index c9b62e5ff..e6cd50bc1 100644
--- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -1,9 +1,11 @@
1use format_buf::format; 1use ra_fmt::unwrap_trivial_block;
2use hir::db::HirDatabase; 2use ra_syntax::{
3use ra_fmt::extract_trivial_expression; 3 ast::{self, make},
4use ra_syntax::{ast, AstNode}; 4 AstNode,
5};
5 6
6use crate::{Assist, AssistCtx, AssistId}; 7use crate::{Assist, AssistCtx, AssistId};
8use ast::edit::IndentLevel;
7 9
8// Assist: replace_if_let_with_match 10// Assist: replace_if_let_with_match
9// 11//
@@ -31,7 +33,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 33// }
32// } 34// }
33// ``` 35// ```
34pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 36pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
35 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 37 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
36 let cond = if_expr.condition()?; 38 let cond = if_expr.condition()?;
37 let pat = cond.pat()?; 39 let pat = cond.pat()?;
@@ -43,32 +45,24 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Opt
43 }; 45 };
44 46
45 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| { 47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| {
46 let match_expr = build_match_expr(expr, pat, then_block, else_block); 48 let match_expr = {
47 edit.target(if_expr.syntax().text_range()); 49 let then_arm = {
48 edit.replace_node_and_indent(if_expr.syntax(), match_expr); 50 let then_expr = unwrap_trivial_block(then_block);
49 edit.set_cursor(if_expr.syntax().text_range().start()) 51 make::match_arm(vec![pat], then_expr)
50 }) 52 };
51} 53 let else_arm = {
54 let else_expr = unwrap_trivial_block(else_block);
55 make::match_arm(vec![make::placeholder_pat().into()], else_expr)
56 };
57 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
58 };
52 59
53fn build_match_expr( 60 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
54 expr: ast::Expr,
55 pat1: ast::Pat,
56 arm1: ast::BlockExpr,
57 arm2: ast::BlockExpr,
58) -> String {
59 let mut buf = String::new();
60 format!(buf, "match {} {{\n", expr.syntax().text());
61 format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1));
62 format!(buf, " _ => {}\n", format_arm(&arm2));
63 buf.push_str("}");
64 buf
65}
66 61
67fn format_arm(block: &ast::BlockExpr) -> String { 62 edit.target(if_expr.syntax().text_range());
68 match extract_trivial_expression(block) { 63 edit.set_cursor(if_expr.syntax().text_range().start());
69 Some(e) if !e.syntax().text().contains_char('\n') => format!("{},", e.syntax().text()), 64 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr.into());
70 _ => block.syntax().text().to_string(), 65 })
71 }
72} 66}
73 67
74#[cfg(test)] 68#[cfg(test)]
diff --git a/crates/ra_assists/src/assists/add_import.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index bf6cfe865..b70c88ec2 100644
--- a/crates/ra_assists/src/assists/add_import.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,4 +1,4 @@
1use hir::{self, db::HirDatabase}; 1use hir::{self, ModPath};
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, NameOwner}, 3 ast::{self, NameOwner},
4 AstNode, Direction, SmolStr, 4 AstNode, Direction, SmolStr,
@@ -12,18 +12,18 @@ use crate::{
12 AssistId, 12 AssistId,
13}; 13};
14 14
15/// This function produces sequence of text edits into edit 15/// Creates and inserts a use statement for the given path to import.
16/// to import the target path in the most appropriate scope given 16/// The use statement is inserted in the scope most appropriate to the
17/// the cursor position 17/// the cursor position given, additionally merged with the existing use imports.
18pub fn auto_import_text_edit( 18pub fn insert_use_statement(
19 // Ideally the position of the cursor, used to 19 // Ideally the position of the cursor, used to
20 position: &SyntaxNode, 20 position: &SyntaxNode,
21 // The statement to use as anchor (last resort) 21 // The statement to use as anchor (last resort)
22 anchor: &SyntaxNode, 22 anchor: &SyntaxNode,
23 // The path to import as a sequence of strings 23 path_to_import: &ModPath,
24 target: &[SmolStr],
25 edit: &mut TextEditBuilder, 24 edit: &mut TextEditBuilder,
26) { 25) {
26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
27 let container = position.ancestors().find_map(|n| { 27 let container = position.ancestors().find_map(|n| {
28 if let Some(module) = ast::Module::cast(n.clone()) { 28 if let Some(module) = ast::Module::cast(n.clone()) {
29 return module.item_list().map(|it| it.syntax().clone()); 29 return module.item_list().map(|it| it.syntax().clone());
@@ -32,14 +32,14 @@ pub fn auto_import_text_edit(
32 }); 32 });
33 33
34 if let Some(container) = container { 34 if let Some(container) = container {
35 let action = best_action_for_target(container, anchor.clone(), target); 35 let action = best_action_for_target(container, anchor.clone(), &target);
36 make_assist(&action, target, edit); 36 make_assist(&action, &target, edit);
37 } 37 }
38} 38}
39 39
40// Assist: add_import 40// Assist: replace_qualified_name_with_use
41// 41//
42// Adds a use statement for a given fully-qualified path. 42// Adds a use statement for a given fully-qualified name.
43// 43//
44// ``` 44// ```
45// fn process(map: std::collections::<|>HashMap<String, String>) {} 45// fn process(map: std::collections::<|>HashMap<String, String>) {}
@@ -50,7 +50,7 @@ pub fn auto_import_text_edit(
50// 50//
51// fn process(map: HashMap<String, String>) {} 51// fn process(map: HashMap<String, String>) {}
52// ``` 52// ```
53pub(crate) fn add_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 53pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> {
54 let path: ast::Path = ctx.find_node_at_offset()?; 54 let path: ast::Path = ctx.find_node_at_offset()?;
55 // We don't want to mess with use statements 55 // We don't want to mess with use statements
56 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 56 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
@@ -72,9 +72,13 @@ pub(crate) fn add_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
72 } 72 }
73 }; 73 };
74 74
75 ctx.add_assist(AssistId("add_import"), format!("Import {}", fmt_segments(&segments)), |edit| { 75 ctx.add_assist(
76 apply_auto_import(&position, &path, &segments, edit.text_edit_builder()); 76 AssistId("replace_qualified_name_with_use"),
77 }) 77 "Replace qualified path with use",
78 |edit| {
79 replace_with_use(&position, &path, &segments, edit.text_edit_builder());
80 },
81 )
78} 82}
79 83
80fn collect_path_segments_raw( 84fn collect_path_segments_raw(
@@ -107,12 +111,6 @@ fn collect_path_segments_raw(
107 Some(segments.len() - oldlen) 111 Some(segments.len() - oldlen)
108} 112}
109 113
110fn fmt_segments(segments: &[SmolStr]) -> String {
111 let mut buf = String::new();
112 fmt_segments_raw(segments, &mut buf);
113 buf
114}
115
116fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { 114fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) {
117 let mut iter = segments.iter(); 115 let mut iter = segments.iter();
118 if let Some(s) = iter.next() { 116 if let Some(s) = iter.next() {
@@ -558,7 +556,7 @@ fn make_assist_add_nested_import(
558 } 556 }
559} 557}
560 558
561fn apply_auto_import( 559fn replace_with_use(
562 container: &SyntaxNode, 560 container: &SyntaxNode,
563 path: &ast::Path, 561 path: &ast::Path,
564 target: &[SmolStr], 562 target: &[SmolStr],
@@ -567,7 +565,7 @@ fn apply_auto_import(
567 let action = best_action_for_target(container.clone(), path.syntax().clone(), target); 565 let action = best_action_for_target(container.clone(), path.syntax().clone(), target);
568 make_assist(&action, target, edit); 566 make_assist(&action, target, edit);
569 if let Some(last) = path.segment() { 567 if let Some(last) = path.segment() {
570 // Here we are assuming the assist will provide a correct use statement 568 // Here we are assuming the assist will provide a correct use statement
571 // so we can delete the path qualifier 569 // so we can delete the path qualifier
572 edit.delete(TextRange::from_to( 570 edit.delete(TextRange::from_to(
573 path.syntax().text_range().start(), 571 path.syntax().text_range().start(),
@@ -603,9 +601,9 @@ mod tests {
603 use super::*; 601 use super::*;
604 602
605 #[test] 603 #[test]
606 fn test_auto_import_add_use_no_anchor() { 604 fn test_replace_add_use_no_anchor() {
607 check_assist( 605 check_assist(
608 add_import, 606 replace_qualified_name_with_use,
609 " 607 "
610std::fmt::Debug<|> 608std::fmt::Debug<|>
611 ", 609 ",
@@ -617,9 +615,9 @@ Debug<|>
617 ); 615 );
618 } 616 }
619 #[test] 617 #[test]
620 fn test_auto_import_add_use_no_anchor_with_item_below() { 618 fn test_replace_add_use_no_anchor_with_item_below() {
621 check_assist( 619 check_assist(
622 add_import, 620 replace_qualified_name_with_use,
623 " 621 "
624std::fmt::Debug<|> 622std::fmt::Debug<|>
625 623
@@ -638,9 +636,9 @@ fn main() {
638 } 636 }
639 637
640 #[test] 638 #[test]
641 fn test_auto_import_add_use_no_anchor_with_item_above() { 639 fn test_replace_add_use_no_anchor_with_item_above() {
642 check_assist( 640 check_assist(
643 add_import, 641 replace_qualified_name_with_use,
644 " 642 "
645fn main() { 643fn main() {
646} 644}
@@ -659,9 +657,9 @@ Debug<|>
659 } 657 }
660 658
661 #[test] 659 #[test]
662 fn test_auto_import_add_use_no_anchor_2seg() { 660 fn test_replace_add_use_no_anchor_2seg() {
663 check_assist( 661 check_assist(
664 add_import, 662 replace_qualified_name_with_use,
665 " 663 "
666std::fmt<|>::Debug 664std::fmt<|>::Debug
667 ", 665 ",
@@ -674,9 +672,9 @@ fmt<|>::Debug
674 } 672 }
675 673
676 #[test] 674 #[test]
677 fn test_auto_import_add_use() { 675 fn test_replace_add_use() {
678 check_assist( 676 check_assist(
679 add_import, 677 replace_qualified_name_with_use,
680 " 678 "
681use stdx; 679use stdx;
682 680
@@ -694,9 +692,9 @@ impl Debug<|> for Foo {
694 } 692 }
695 693
696 #[test] 694 #[test]
697 fn test_auto_import_file_use_other_anchor() { 695 fn test_replace_file_use_other_anchor() {
698 check_assist( 696 check_assist(
699 add_import, 697 replace_qualified_name_with_use,
700 " 698 "
701impl std::fmt::Debug<|> for Foo { 699impl std::fmt::Debug<|> for Foo {
702} 700}
@@ -711,9 +709,9 @@ impl Debug<|> for Foo {
711 } 709 }
712 710
713 #[test] 711 #[test]
714 fn test_auto_import_add_use_other_anchor_indent() { 712 fn test_replace_add_use_other_anchor_indent() {
715 check_assist( 713 check_assist(
716 add_import, 714 replace_qualified_name_with_use,
717 " 715 "
718 impl std::fmt::Debug<|> for Foo { 716 impl std::fmt::Debug<|> for Foo {
719 } 717 }
@@ -728,9 +726,9 @@ impl Debug<|> for Foo {
728 } 726 }
729 727
730 #[test] 728 #[test]
731 fn test_auto_import_split_different() { 729 fn test_replace_split_different() {
732 check_assist( 730 check_assist(
733 add_import, 731 replace_qualified_name_with_use,
734 " 732 "
735use std::fmt; 733use std::fmt;
736 734
@@ -747,9 +745,9 @@ impl io<|> for Foo {
747 } 745 }
748 746
749 #[test] 747 #[test]
750 fn test_auto_import_split_self_for_use() { 748 fn test_replace_split_self_for_use() {
751 check_assist( 749 check_assist(
752 add_import, 750 replace_qualified_name_with_use,
753 " 751 "
754use std::fmt; 752use std::fmt;
755 753
@@ -766,9 +764,9 @@ impl Debug<|> for Foo {
766 } 764 }
767 765
768 #[test] 766 #[test]
769 fn test_auto_import_split_self_for_target() { 767 fn test_replace_split_self_for_target() {
770 check_assist( 768 check_assist(
771 add_import, 769 replace_qualified_name_with_use,
772 " 770 "
773use std::fmt::Debug; 771use std::fmt::Debug;
774 772
@@ -785,9 +783,9 @@ impl fmt<|> for Foo {
785 } 783 }
786 784
787 #[test] 785 #[test]
788 fn test_auto_import_add_to_nested_self_nested() { 786 fn test_replace_add_to_nested_self_nested() {
789 check_assist( 787 check_assist(
790 add_import, 788 replace_qualified_name_with_use,
791 " 789 "
792use std::fmt::{Debug, nested::{Display}}; 790use std::fmt::{Debug, nested::{Display}};
793 791
@@ -804,9 +802,9 @@ impl nested<|> for Foo {
804 } 802 }
805 803
806 #[test] 804 #[test]
807 fn test_auto_import_add_to_nested_self_already_included() { 805 fn test_replace_add_to_nested_self_already_included() {
808 check_assist( 806 check_assist(
809 add_import, 807 replace_qualified_name_with_use,
810 " 808 "
811use std::fmt::{Debug, nested::{self, Display}}; 809use std::fmt::{Debug, nested::{self, Display}};
812 810
@@ -823,9 +821,9 @@ impl nested<|> for Foo {
823 } 821 }
824 822
825 #[test] 823 #[test]
826 fn test_auto_import_add_to_nested_nested() { 824 fn test_replace_add_to_nested_nested() {
827 check_assist( 825 check_assist(
828 add_import, 826 replace_qualified_name_with_use,
829 " 827 "
830use std::fmt::{Debug, nested::{Display}}; 828use std::fmt::{Debug, nested::{Display}};
831 829
@@ -842,9 +840,9 @@ impl Debug<|> for Foo {
842 } 840 }
843 841
844 #[test] 842 #[test]
845 fn test_auto_import_split_common_target_longer() { 843 fn test_replace_split_common_target_longer() {
846 check_assist( 844 check_assist(
847 add_import, 845 replace_qualified_name_with_use,
848 " 846 "
849use std::fmt::Debug; 847use std::fmt::Debug;
850 848
@@ -861,9 +859,9 @@ impl Display<|> for Foo {
861 } 859 }
862 860
863 #[test] 861 #[test]
864 fn test_auto_import_split_common_use_longer() { 862 fn test_replace_split_common_use_longer() {
865 check_assist( 863 check_assist(
866 add_import, 864 replace_qualified_name_with_use,
867 " 865 "
868use std::fmt::nested::Debug; 866use std::fmt::nested::Debug;
869 867
@@ -880,9 +878,9 @@ impl Display<|> for Foo {
880 } 878 }
881 879
882 #[test] 880 #[test]
883 fn test_auto_import_use_nested_import() { 881 fn test_replace_use_nested_import() {
884 check_assist( 882 check_assist(
885 add_import, 883 replace_qualified_name_with_use,
886 " 884 "
887use crate::{ 885use crate::{
888 ty::{Substs, Ty}, 886 ty::{Substs, Ty},
@@ -903,9 +901,9 @@ fn foo() { lower<|>::trait_env() }
903 } 901 }
904 902
905 #[test] 903 #[test]
906 fn test_auto_import_alias() { 904 fn test_replace_alias() {
907 check_assist( 905 check_assist(
908 add_import, 906 replace_qualified_name_with_use,
909 " 907 "
910use std::fmt as foo; 908use std::fmt as foo;
911 909
@@ -922,9 +920,9 @@ impl Debug<|> for Foo {
922 } 920 }
923 921
924 #[test] 922 #[test]
925 fn test_auto_import_not_applicable_one_segment() { 923 fn test_replace_not_applicable_one_segment() {
926 check_assist_not_applicable( 924 check_assist_not_applicable(
927 add_import, 925 replace_qualified_name_with_use,
928 " 926 "
929impl foo<|> for Foo { 927impl foo<|> for Foo {
930} 928}
@@ -933,9 +931,9 @@ impl foo<|> for Foo {
933 } 931 }
934 932
935 #[test] 933 #[test]
936 fn test_auto_import_not_applicable_in_use() { 934 fn test_replace_not_applicable_in_use() {
937 check_assist_not_applicable( 935 check_assist_not_applicable(
938 add_import, 936 replace_qualified_name_with_use,
939 " 937 "
940use std::fmt<|>; 938use std::fmt<|>;
941", 939",
@@ -943,9 +941,9 @@ use std::fmt<|>;
943 } 941 }
944 942
945 #[test] 943 #[test]
946 fn test_auto_import_add_use_no_anchor_in_mod_mod() { 944 fn test_replace_add_use_no_anchor_in_mod_mod() {
947 check_assist( 945 check_assist(
948 add_import, 946 replace_qualified_name_with_use,
949 " 947 "
950mod foo { 948mod foo {
951 mod bar { 949 mod bar {
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index 6038c4858..2c3f07a79 100644
--- a/crates/ra_assists/src/assists/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -1,6 +1,5 @@
1use std::iter::successors; 1use std::iter::successors;
2 2
3use hir::db::HirDatabase;
4use ra_syntax::{ast, AstNode, TextUnit, T}; 3use ra_syntax::{ast, AstNode, TextUnit, T};
5 4
6use crate::{Assist, AssistCtx, AssistId}; 5use crate::{Assist, AssistCtx, AssistId};
@@ -16,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId};
16// ``` 15// ```
17// use std::{collections::HashMap}; 16// use std::{collections::HashMap};
18// ``` 17// ```
19pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 18pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
20 let colon_colon = ctx.find_token_at_offset(T![::])?; 19 let colon_colon = ctx.find_token_at_offset(T![::])?;
21 let path = ast::Path::cast(colon_colon.parent())?; 20 let path = ast::Path::cast(colon_colon.parent())?;
22 let top_path = successors(Some(path), |it| it.parent_path()).last()?; 21 let top_path = successors(Some(path), |it| it.parent_path()).last()?;
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 3337805a5..828a8e9e8 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -9,18 +9,16 @@ mod assist_ctx;
9mod marks; 9mod marks;
10#[cfg(test)] 10#[cfg(test)]
11mod doc_tests; 11mod doc_tests;
12#[cfg(test)] 12mod utils;
13mod test_db;
14pub mod ast_transform; 13pub mod ast_transform;
15 14
16use either::Either;
17use hir::db::HirDatabase;
18use ra_db::FileRange; 15use ra_db::FileRange;
16use ra_ide_db::RootDatabase;
19use ra_syntax::{TextRange, TextUnit}; 17use ra_syntax::{TextRange, TextUnit};
20use ra_text_edit::TextEdit; 18use ra_text_edit::TextEdit;
21 19
22pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; 20pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler};
23pub use crate::assists::add_import::auto_import_text_edit; 21pub use crate::handlers::replace_qualified_name_with_use::insert_use_statement;
24 22
25/// Unique identifier of the assist, should not be shown to the user 23/// Unique identifier of the assist, should not be shown to the user
26/// directly. 24/// directly.
@@ -34,81 +32,64 @@ pub struct AssistLabel {
34 pub id: AssistId, 32 pub id: AssistId,
35} 33}
36 34
35#[derive(Clone, Debug)]
36pub struct GroupLabel(pub String);
37
38impl AssistLabel {
39 pub(crate) fn new(label: String, id: AssistId) -> AssistLabel {
40 // FIXME: make fields private, so that this invariant can't be broken
41 assert!(label.chars().nth(0).unwrap().is_uppercase());
42 AssistLabel { label: label.into(), id }
43 }
44}
45
37#[derive(Debug, Clone)] 46#[derive(Debug, Clone)]
38pub struct AssistAction { 47pub struct AssistAction {
39 pub label: Option<String>,
40 pub edit: TextEdit, 48 pub edit: TextEdit,
41 pub cursor_position: Option<TextUnit>, 49 pub cursor_position: Option<TextUnit>,
50 // FIXME: This belongs to `AssistLabel`
42 pub target: Option<TextRange>, 51 pub target: Option<TextRange>,
43} 52}
44 53
45#[derive(Debug, Clone)] 54#[derive(Debug, Clone)]
46pub struct ResolvedAssist { 55pub struct ResolvedAssist {
47 pub label: AssistLabel, 56 pub label: AssistLabel,
48 pub action_data: Either<AssistAction, Vec<AssistAction>>, 57 pub group_label: Option<GroupLabel>,
49} 58 pub action: AssistAction,
50
51impl ResolvedAssist {
52 pub fn get_first_action(&self) -> AssistAction {
53 match &self.action_data {
54 Either::Left(action) => action.clone(),
55 Either::Right(actions) => actions[0].clone(),
56 }
57 }
58} 59}
59 60
60/// Return all the assists applicable at the given position. 61/// Return all the assists applicable at the given position.
61/// 62///
62/// Assists are returned in the "unresolved" state, that is only labels are 63/// Assists are returned in the "unresolved" state, that is only labels are
63/// returned, without actual edits. 64/// returned, without actual edits.
64pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel> 65pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> {
65where 66 let ctx = AssistCtx::new(db, range, false);
66 H: HirDatabase + 'static, 67 handlers::all()
67{ 68 .iter()
68 AssistCtx::with_ctx(db, range, false, |ctx| { 69 .filter_map(|f| f(ctx.clone()))
69 assists::all() 70 .flat_map(|it| it.0)
70 .iter() 71 .map(|a| a.label)
71 .filter_map(|f| f(ctx.clone())) 72 .collect()
72 .map(|a| match a {
73 Assist::Unresolved { label } => label,
74 Assist::Resolved { .. } => unreachable!(),
75 })
76 .collect()
77 })
78} 73}
79 74
80/// Return all the assists applicable at the given position. 75/// Return all the assists applicable at the given position.
81/// 76///
82/// Assists are returned in the "resolved" state, that is with edit fully 77/// Assists are returned in the "resolved" state, that is with edit fully
83/// computed. 78/// computed.
84pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> 79pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> {
85where 80 let ctx = AssistCtx::new(db, range, true);
86 H: HirDatabase + 'static, 81 let mut a = handlers::all()
87{ 82 .iter()
88 use std::cmp::Ordering; 83 .filter_map(|f| f(ctx.clone()))
89 84 .flat_map(|it| it.0)
90 AssistCtx::with_ctx(db, range, true, |ctx| { 85 .map(|it| it.into_resolved().unwrap())
91 let mut a = assists::all() 86 .collect::<Vec<_>>();
92 .iter() 87 a.sort_by_key(|it| it.action.target.map_or(TextUnit::from(!0u32), |it| it.len()));
93 .filter_map(|f| f(ctx.clone())) 88 a
94 .map(|a| match a {
95 Assist::Resolved { assist } => assist,
96 Assist::Unresolved { .. } => unreachable!(),
97 })
98 .collect::<Vec<_>>();
99 a.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) {
100 (Some(a), Some(b)) => a.len().cmp(&b.len()),
101 (Some(_), None) => Ordering::Less,
102 (None, Some(_)) => Ordering::Greater,
103 (None, None) => Ordering::Equal,
104 });
105 a
106 })
107} 89}
108 90
109mod assists { 91mod handlers {
110 use crate::{Assist, AssistCtx}; 92 use crate::AssistHandler;
111 use hir::db::HirDatabase;
112 93
113 mod add_derive; 94 mod add_derive;
114 mod add_explicit_type; 95 mod add_explicit_type;
@@ -116,6 +97,7 @@ mod assists {
116 mod add_custom_impl; 97 mod add_custom_impl;
117 mod add_new; 98 mod add_new;
118 mod apply_demorgan; 99 mod apply_demorgan;
100 mod auto_import;
119 mod invert_if; 101 mod invert_if;
120 mod flip_comma; 102 mod flip_comma;
121 mod flip_binexpr; 103 mod flip_binexpr;
@@ -129,13 +111,13 @@ mod assists {
129 mod replace_if_let_with_match; 111 mod replace_if_let_with_match;
130 mod split_import; 112 mod split_import;
131 mod remove_dbg; 113 mod remove_dbg;
132 pub(crate) mod add_import; 114 pub(crate) mod replace_qualified_name_with_use;
133 mod add_missing_impl_members; 115 mod add_missing_impl_members;
134 mod move_guard; 116 mod move_guard;
135 mod move_bounds; 117 mod move_bounds;
136 mod early_return; 118 mod early_return;
137 119
138 pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 120 pub(crate) fn all() -> &'static [AssistHandler] {
139 &[ 121 &[
140 add_derive::add_derive, 122 add_derive::add_derive,
141 add_explicit_type::add_explicit_type, 123 add_explicit_type::add_explicit_type,
@@ -154,7 +136,7 @@ mod assists {
154 replace_if_let_with_match::replace_if_let_with_match, 136 replace_if_let_with_match::replace_if_let_with_match,
155 split_import::split_import, 137 split_import::split_import,
156 remove_dbg::remove_dbg, 138 remove_dbg::remove_dbg,
157 add_import::add_import, 139 replace_qualified_name_with_use::replace_qualified_name_with_use,
158 add_missing_impl_members::add_missing_impl_members, 140 add_missing_impl_members::add_missing_impl_members,
159 add_missing_impl_members::add_missing_default_members, 141 add_missing_impl_members::add_missing_default_members,
160 inline_local_variable::inline_local_variable, 142 inline_local_variable::inline_local_variable,
@@ -166,33 +148,39 @@ mod assists {
166 raw_string::make_usual_string, 148 raw_string::make_usual_string,
167 raw_string::remove_hash, 149 raw_string::remove_hash,
168 early_return::convert_to_guarded_return, 150 early_return::convert_to_guarded_return,
151 auto_import::auto_import,
169 ] 152 ]
170 } 153 }
171} 154}
172 155
173#[cfg(test)] 156#[cfg(test)]
174mod helpers { 157mod helpers {
175 use ra_db::{fixture::WithFixture, FileRange}; 158 use std::sync::Arc;
159
160 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
161 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
176 use ra_syntax::TextRange; 162 use ra_syntax::TextRange;
177 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; 163 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
178 164
179 use crate::{test_db::TestDB, Assist, AssistCtx}; 165 use crate::{AssistCtx, AssistHandler};
166
167 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
168 let (mut db, file_id) = RootDatabase::with_single_file(text);
169 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
170 // but it looks like this might need specialization? :(
171 let local_roots = vec![db.file_source_root(file_id)];
172 db.set_local_roots(Arc::new(local_roots));
173 (db, file_id)
174 }
180 175
181 pub(crate) fn check_assist( 176 pub(crate) fn check_assist(assist: AssistHandler, before: &str, after: &str) {
182 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
183 before: &str,
184 after: &str,
185 ) {
186 let (before_cursor_pos, before) = extract_offset(before); 177 let (before_cursor_pos, before) = extract_offset(before);
187 let (db, file_id) = TestDB::with_single_file(&before); 178 let (db, file_id) = with_single_file(&before);
188 let frange = 179 let frange =
189 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 180 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
190 let assist = 181 let assist =
191 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 182 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
192 let action = match assist { 183 let action = assist.0[0].action.clone().unwrap();
193 Assist::Unresolved { .. } => unreachable!(),
194 Assist::Resolved { assist } => assist.get_first_action(),
195 };
196 184
197 let actual = action.edit.apply(&before); 185 let actual = action.edit.apply(&before);
198 let actual_cursor_pos = match action.cursor_position { 186 let actual_cursor_pos = match action.cursor_position {
@@ -206,20 +194,13 @@ mod helpers {
206 assert_eq_text!(after, &actual); 194 assert_eq_text!(after, &actual);
207 } 195 }
208 196
209 pub(crate) fn check_assist_range( 197 pub(crate) fn check_assist_range(assist: AssistHandler, before: &str, after: &str) {
210 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
211 before: &str,
212 after: &str,
213 ) {
214 let (range, before) = extract_range(before); 198 let (range, before) = extract_range(before);
215 let (db, file_id) = TestDB::with_single_file(&before); 199 let (db, file_id) = with_single_file(&before);
216 let frange = FileRange { file_id, range }; 200 let frange = FileRange { file_id, range };
217 let assist = 201 let assist =
218 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 202 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
219 let action = match assist { 203 let action = assist.0[0].action.clone().unwrap();
220 Assist::Unresolved { .. } => unreachable!(),
221 Assist::Resolved { assist } => assist.get_first_action(),
222 };
223 204
224 let mut actual = action.edit.apply(&before); 205 let mut actual = action.edit.apply(&before);
225 if let Some(pos) = action.cursor_position { 206 if let Some(pos) = action.cursor_position {
@@ -228,85 +209,65 @@ mod helpers {
228 assert_eq_text!(after, &actual); 209 assert_eq_text!(after, &actual);
229 } 210 }
230 211
231 pub(crate) fn check_assist_target( 212 pub(crate) fn check_assist_target(assist: AssistHandler, before: &str, target: &str) {
232 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
233 before: &str,
234 target: &str,
235 ) {
236 let (before_cursor_pos, before) = extract_offset(before); 213 let (before_cursor_pos, before) = extract_offset(before);
237 let (db, file_id) = TestDB::with_single_file(&before); 214 let (db, file_id) = with_single_file(&before);
238 let frange = 215 let frange =
239 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 216 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
240 let assist = 217 let assist =
241 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 218 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
242 let action = match assist { 219 let action = assist.0[0].action.clone().unwrap();
243 Assist::Unresolved { .. } => unreachable!(),
244 Assist::Resolved { assist } => assist.get_first_action(),
245 };
246 220
247 let range = action.target.expect("expected target on action"); 221 let range = action.target.expect("expected target on action");
248 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); 222 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
249 } 223 }
250 224
251 pub(crate) fn check_assist_range_target( 225 pub(crate) fn check_assist_range_target(assist: AssistHandler, before: &str, target: &str) {
252 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
253 before: &str,
254 target: &str,
255 ) {
256 let (range, before) = extract_range(before); 226 let (range, before) = extract_range(before);
257 let (db, file_id) = TestDB::with_single_file(&before); 227 let (db, file_id) = with_single_file(&before);
258 let frange = FileRange { file_id, range }; 228 let frange = FileRange { file_id, range };
259 let assist = 229 let assist =
260 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 230 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
261 let action = match assist { 231 let action = assist.0[0].action.clone().unwrap();
262 Assist::Unresolved { .. } => unreachable!(),
263 Assist::Resolved { assist } => assist.get_first_action(),
264 };
265 232
266 let range = action.target.expect("expected target on action"); 233 let range = action.target.expect("expected target on action");
267 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); 234 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
268 } 235 }
269 236
270 pub(crate) fn check_assist_not_applicable( 237 pub(crate) fn check_assist_not_applicable(assist: AssistHandler, before: &str) {
271 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
272 before: &str,
273 ) {
274 let (before_cursor_pos, before) = extract_offset(before); 238 let (before_cursor_pos, before) = extract_offset(before);
275 let (db, file_id) = TestDB::with_single_file(&before); 239 let (db, file_id) = with_single_file(&before);
276 let frange = 240 let frange =
277 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 241 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
278 let assist = AssistCtx::with_ctx(&db, frange, true, assist); 242 let assist = assist(AssistCtx::new(&db, frange, true));
279 assert!(assist.is_none()); 243 assert!(assist.is_none());
280 } 244 }
281 245
282 pub(crate) fn check_assist_range_not_applicable( 246 pub(crate) fn check_assist_range_not_applicable(assist: AssistHandler, before: &str) {
283 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
284 before: &str,
285 ) {
286 let (range, before) = extract_range(before); 247 let (range, before) = extract_range(before);
287 let (db, file_id) = TestDB::with_single_file(&before); 248 let (db, file_id) = with_single_file(&before);
288 let frange = FileRange { file_id, range }; 249 let frange = FileRange { file_id, range };
289 let assist = AssistCtx::with_ctx(&db, frange, true, assist); 250 let assist = assist(AssistCtx::new(&db, frange, true));
290 assert!(assist.is_none()); 251 assert!(assist.is_none());
291 } 252 }
292} 253}
293 254
294#[cfg(test)] 255#[cfg(test)]
295mod tests { 256mod tests {
296 use ra_db::{fixture::WithFixture, FileRange}; 257 use ra_db::FileRange;
297 use ra_syntax::TextRange; 258 use ra_syntax::TextRange;
298 use test_utils::{extract_offset, extract_range}; 259 use test_utils::{extract_offset, extract_range};
299 260
300 use crate::test_db::TestDB; 261 use crate::{helpers, resolved_assists};
301 262
302 #[test] 263 #[test]
303 fn assist_order_field_struct() { 264 fn assist_order_field_struct() {
304 let before = "struct Foo { <|>bar: u32 }"; 265 let before = "struct Foo { <|>bar: u32 }";
305 let (before_cursor_pos, before) = extract_offset(before); 266 let (before_cursor_pos, before) = extract_offset(before);
306 let (db, file_id) = TestDB::with_single_file(&before); 267 let (db, file_id) = helpers::with_single_file(&before);
307 let frange = 268 let frange =
308 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 269 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
309 let assists = super::assists(&db, frange); 270 let assists = resolved_assists(&db, frange);
310 let mut assists = assists.iter(); 271 let mut assists = assists.iter();
311 272
312 assert_eq!( 273 assert_eq!(
@@ -327,9 +288,9 @@ mod tests {
327 } 288 }
328 }"; 289 }";
329 let (range, before) = extract_range(before); 290 let (range, before) = extract_range(before);
330 let (db, file_id) = TestDB::with_single_file(&before); 291 let (db, file_id) = helpers::with_single_file(&before);
331 let frange = FileRange { file_id, range }; 292 let frange = FileRange { file_id, range };
332 let assists = super::assists(&db, frange); 293 let assists = resolved_assists(&db, frange);
333 let mut assists = assists.iter(); 294 let mut assists = assists.iter();
334 295
335 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); 296 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable");
diff --git a/crates/ra_assists/src/test_db.rs b/crates/ra_assists/src/test_db.rs
deleted file mode 100644
index d5249f308..000000000
--- a/crates/ra_assists/src/test_db.rs
+++ /dev/null
@@ -1,45 +0,0 @@
1//! Database used for testing `ra_assists`.
2
3use std::sync::Arc;
4
5use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath};
6
7#[salsa::database(
8 ra_db::SourceDatabaseExtStorage,
9 ra_db::SourceDatabaseStorage,
10 hir::db::InternDatabaseStorage,
11 hir::db::AstDatabaseStorage,
12 hir::db::DefDatabaseStorage,
13 hir::db::HirDatabaseStorage
14)]
15#[derive(Debug, Default)]
16pub struct TestDB {
17 runtime: salsa::Runtime<TestDB>,
18}
19
20impl salsa::Database for TestDB {
21 fn salsa_runtime(&self) -> &salsa::Runtime<Self> {
22 &self.runtime
23 }
24 fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime<Self> {
25 &mut self.runtime
26 }
27}
28
29impl std::panic::RefUnwindSafe for TestDB {}
30
31impl FileLoader for TestDB {
32 fn file_text(&self, file_id: FileId) -> Arc<String> {
33 FileLoaderDelegate(self).file_text(file_id)
34 }
35 fn resolve_relative_path(
36 &self,
37 anchor: FileId,
38 relative_path: &RelativePath,
39 ) -> Option<FileId> {
40 FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
41 }
42 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
43 FileLoaderDelegate(self).relevant_crates(file_id)
44 }
45}
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
new file mode 100644
index 000000000..0d5722295
--- /dev/null
+++ b/crates/ra_assists/src/utils.rs
@@ -0,0 +1,27 @@
1//! Assorted functions shared by several assists.
2
3use ra_syntax::{
4 ast::{self, make},
5 T,
6};
7
8pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
9 if let Some(expr) = invert_special_case(&expr) {
10 return expr;
11 }
12 make::expr_prefix(T![!], expr)
13}
14
15fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
16 match expr {
17 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
18 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
19 ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
20 _ => None,
21 },
22 ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => pe.expr(),
23 // FIXME:
24 // ast::Expr::Literal(true | false )
25 _ => None,
26 }
27}