diff options
author | Kevin DeLorey <[email protected]> | 2020-02-09 16:25:47 +0000 |
---|---|---|
committer | Kevin DeLorey <[email protected]> | 2020-02-09 16:37:43 +0000 |
commit | a957c473fdb79880c39b73dc9e0c923093cf16ac (patch) | |
tree | f998b548f530ce604651e0e6af314ed2ec74b3b5 /crates/ra_assists/src | |
parent | 22caf982b99c54058e2e9200aeea0e61cada284a (diff) | |
parent | 1b9b13b4b4a75b5531c3f046ce6bf72d681f2732 (diff) |
Merge branch 'master' into kdelorey/complete-trait-impl
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 140 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 12 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 53 | ||||
-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.rs | 293 | ||||
-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.rs | 209 | ||||
-rw-r--r-- | crates/ra_assists/src/test_db.rs | 45 | ||||
-rw-r--r-- | crates/ra_assists/src/utils.rs | 27 |
31 files changed, 839 insertions, 472 deletions
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. |
2 | use either::Either; | 2 | use hir::{InFile, SourceAnalyzer, SourceBinder}; |
3 | use hir::{db::HirDatabase, InFile, SourceAnalyzer, SourceBinder}; | 3 | use ra_db::{FileRange, SourceDatabase}; |
4 | use ra_db::FileRange; | ||
5 | use ra_fmt::{leading_indent, reindent}; | 4 | use ra_fmt::{leading_indent, reindent}; |
5 | use ra_ide_db::RootDatabase; | ||
6 | use ra_syntax::{ | 6 | use 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 | }; |
11 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
12 | 12 | ||
13 | use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; |
14 | 14 | ||
15 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
16 | pub(crate) enum Assist { | 16 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); |
17 | Unresolved { label: AssistLabel }, | 17 | |
18 | Resolved { assist: ResolvedAssist }, | 18 | #[derive(Clone, Debug)] |
19 | pub(crate) struct AssistInfo { | ||
20 | pub(crate) label: AssistLabel, | ||
21 | pub(crate) group_label: Option<GroupLabel>, | ||
22 | pub(crate) action: Option<AssistAction>, | ||
23 | } | ||
24 | |||
25 | impl 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 | ||
45 | pub(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)] |
52 | pub(crate) struct AssistCtx<'a, DB> { | 78 | pub(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 | ||
59 | impl<'a, DB> Clone for AssistCtx<'a, DB> { | 85 | impl 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 | ||
70 | impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | 96 | impl<'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 | ||
158 | pub(crate) struct AssistGroup<'a> { | ||
159 | ctx: AssistCtx<'a>, | ||
160 | group_name: String, | ||
161 | assists: Vec<AssistInfo>, | ||
162 | } | ||
163 | |||
164 | impl<'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)] |
163 | pub(crate) struct ActionBuilder { | 193 | pub(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 | ||
170 | impl ActionBuilder { | 199 | impl 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 | ||
6 | mod generated; | 6 | mod generated; |
7 | 7 | ||
8 | use ra_db::{fixture::WithFixture, FileRange}; | 8 | use ra_db::FileRange; |
9 | use test_utils::{assert_eq_text, extract_range_or_offset}; | 9 | use test_utils::{assert_eq_text, extract_range_or_offset}; |
10 | 10 | ||
11 | use crate::test_db::TestDB; | 11 | use crate::resolved_assists; |
12 | 12 | ||
13 | fn check(assist_id: &str, before: &str, after: &str) { | 13 | fn 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 | ||
3 | use super::check; | 3 | use super::check; |
4 | 4 | ||
@@ -161,21 +161,6 @@ impl Trait<u32> for () { | |||
161 | } | 161 | } |
162 | 162 | ||
163 | #[test] | 163 | #[test] |
164 | fn doctest_add_import() { | ||
165 | check( | ||
166 | "add_import", | ||
167 | r#####" | ||
168 | fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
169 | "#####, | ||
170 | r#####" | ||
171 | use std::collections::HashMap; | ||
172 | |||
173 | fn process(map: HashMap<String, String>) {} | ||
174 | "#####, | ||
175 | ) | ||
176 | } | ||
177 | |||
178 | #[test] | ||
179 | fn doctest_add_new() { | 164 | fn 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] |
203 | fn doctest_auto_import() { | ||
204 | check( | ||
205 | "auto_import", | ||
206 | r#####" | ||
207 | fn main() { | ||
208 | let map = HashMap<|>::new(); | ||
209 | } | ||
210 | pub mod std { pub mod collections { pub struct HashMap { } } } | ||
211 | "#####, | ||
212 | r#####" | ||
213 | use std::collections::HashMap; | ||
214 | |||
215 | fn main() { | ||
216 | let map = HashMap::new(); | ||
217 | } | ||
218 | pub mod std { pub mod collections { pub struct HashMap { } } } | ||
219 | "#####, | ||
220 | ) | ||
221 | } | ||
222 | |||
223 | #[test] | ||
218 | fn doctest_change_visibility() { | 224 | fn 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] |
580 | fn doctest_replace_qualified_name_with_use() { | ||
581 | check( | ||
582 | "replace_qualified_name_with_use", | ||
583 | r#####" | ||
584 | fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
585 | "#####, | ||
586 | r#####" | ||
587 | use std::collections::HashMap; | ||
588 | |||
589 | fn process(map: HashMap<String, String>) {} | ||
590 | "#####, | ||
591 | ) | ||
592 | } | ||
593 | |||
594 | #[test] | ||
574 | fn doctest_split_import() { | 595 | fn 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 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use crate::{Assist, AssistCtx, AssistId}; |
4 | use hir::db::HirDatabase; | 4 | |
5 | use join_to_string::join; | 5 | use join_to_string::join; |
6 | use ra_syntax::{ | 6 | use 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 | // ``` |
32 | pub(crate) fn add_custom_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 32 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
28 | pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 27 | pub(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 @@ | |||
1 | use hir::{db::HirDatabase, HirDisplay}; | 1 | use hir::HirDisplay; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{self, AstNode, LetStmt, NameOwner}, | 3 | ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner}, |
4 | TextRange, T, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
24 | pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(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 @@ | |||
1 | use format_buf::format; | 1 | use format_buf::format; |
2 | use hir::db::HirDatabase; | 2 | |
3 | use join_to_string::join; | 3 | use join_to_string::join; |
4 | use ra_syntax::{ | 4 | use 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 | // ``` |
30 | pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 30 | pub(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 | // ``` |
46 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 46 | pub(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 | // ``` |
87 | pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 87 | pub(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 | ||
96 | fn add_missing_impl_members_inner( | 96 | fn 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 @@ | |||
1 | use format_buf::format; | 1 | use format_buf::format; |
2 | use hir::{db::HirDatabase, InFile}; | 2 | use hir::{Adt, InFile}; |
3 | use join_to_string::join; | 3 | use join_to_string::join; |
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | ast::{ | 5 | ast::{ |
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
31 | // } | 31 | // } |
32 | // | 32 | // |
33 | // ``` | 33 | // ``` |
34 | pub(crate) fn add_new(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 34 | pub(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) |
131 | fn find_struct_impl( | 131 | fn 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 @@ | |||
1 | use super::invert_if::invert_boolean_expression; | ||
2 | use hir::db::HirDatabase; | ||
3 | use ra_syntax::ast::{self, AstNode}; | 1 | use ra_syntax::ast::{self, AstNode}; |
4 | 2 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
26 | pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(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 @@ | |||
1 | use ra_ide_db::imports_locator::ImportsLocator; | ||
2 | use ra_syntax::ast::{self, AstNode}; | ||
3 | |||
4 | use crate::{ | ||
5 | assist_ctx::{Assist, AssistCtx}, | ||
6 | insert_use_statement, AssistId, | ||
7 | }; | ||
8 | use 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 | // ``` | ||
29 | pub(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)] | ||
85 | mod 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
25 | pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(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 | ||
32 | fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 31 | fn 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 | ||
78 | fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { | 77 | fn 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 @@ | |||
1 | use std::{iter::once, ops::RangeInclusive}; | 1 | use std::{iter::once, ops::RangeInclusive}; |
2 | 2 | ||
3 | use hir::db::HirDatabase; | ||
4 | use ra_syntax::{ | 3 | use 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 | ||
12 | use crate::{ | 11 | use 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 | // ``` |
39 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 39 | pub(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 | // ``` |
34 | pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 34 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | 1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; |
3 | 2 | ||
4 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
21 | pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 20 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; | 1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; |
3 | 2 | ||
4 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
21 | pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 20 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
21 | pub(crate) fn flip_trait_bound(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 20 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
26 | pub(crate) fn inline_local_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 25 | pub(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 | " | ||
656 | fn 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 @@ | |||
1 | use format_buf::format; | 1 | use format_buf::format; |
2 | use hir::db::HirDatabase; | ||
3 | use ra_syntax::{ | 2 | use 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 | // ``` |
31 | pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 30 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::ast::{self, AstNode}; | 1 | use ra_syntax::ast::{self, AstNode}; |
3 | use ra_syntax::T; | 2 | use ra_syntax::T; |
4 | 3 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 4 | use 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 | ||
26 | pub(crate) fn invert_if(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 25 | pub(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 | ||
55 | pub(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)] |
70 | mod tests { | 55 | mod 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 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; | 1 | use std::iter::successors; |
2 | use hir::db::HirDatabase; | 2 | |
3 | use ra_syntax::ast::{AstNode, MatchArm}; | 3 | use ra_syntax::{ |
4 | ast::{self, AstNode}, | ||
5 | Direction, TextUnit, | ||
6 | }; | ||
7 | |||
8 | use 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 | // ``` |
29 | pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 34 | pub(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(¤t_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 | ||
98 | fn 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 | |||
105 | fn 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)] |
87 | mod tests { | 110 | mod 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
25 | pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
35 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 34 | pub(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 | // ``` |
92 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 91 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
25 | pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(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 | // ``` |
54 | pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 53 | pub(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 | // ``` |
80 | pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 79 | pub(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 | // ``` |
104 | pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 103 | pub(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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | 1 | use 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 | // ``` |
24 | pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 23 | pub(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(¯o_call, "dbg")? { | 26 | if !is_valid_macrocall(¯o_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 @@ | |||
1 | use format_buf::format; | 1 | use ra_fmt::unwrap_trivial_block; |
2 | use hir::db::HirDatabase; | 2 | use ra_syntax::{ |
3 | use ra_fmt::extract_trivial_expression; | 3 | ast::{self, make}, |
4 | use ra_syntax::{ast, AstNode}; | 4 | AstNode, |
5 | }; | ||
5 | 6 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{Assist, AssistCtx, AssistId}; |
8 | use 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 | // ``` |
34 | pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 36 | pub(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 | ||
53 | fn 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 | ||
67 | fn 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 @@ | |||
1 | use hir::{self, db::HirDatabase}; | 1 | use hir::{self, ModPath}; |
2 | use ra_syntax::{ | 2 | use 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. |
18 | pub fn auto_import_text_edit( | 18 | pub 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 | // ``` |
53 | pub(crate) fn add_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 53 | pub(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 | ||
80 | fn collect_path_segments_raw( | 84 | fn 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 | ||
110 | fn fmt_segments(segments: &[SmolStr]) -> String { | ||
111 | let mut buf = String::new(); | ||
112 | fmt_segments_raw(segments, &mut buf); | ||
113 | buf | ||
114 | } | ||
115 | |||
116 | fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { | 114 | fn 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 | ||
561 | fn apply_auto_import( | 559 | fn 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 | " |
610 | std::fmt::Debug<|> | 608 | std::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 | " |
624 | std::fmt::Debug<|> | 622 | std::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 | " |
645 | fn main() { | 643 | fn 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 | " |
666 | std::fmt<|>::Debug | 664 | std::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 | " |
681 | use stdx; | 679 | use 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 | " |
701 | impl std::fmt::Debug<|> for Foo { | 699 | impl 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 | " |
735 | use std::fmt; | 733 | use 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 | " |
754 | use std::fmt; | 752 | use 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 | " |
773 | use std::fmt::Debug; | 771 | use 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 | " |
792 | use std::fmt::{Debug, nested::{Display}}; | 790 | use 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 | " |
811 | use std::fmt::{Debug, nested::{self, Display}}; | 809 | use 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 | " |
830 | use std::fmt::{Debug, nested::{Display}}; | 828 | use 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 | " |
849 | use std::fmt::Debug; | 847 | use 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 | " |
868 | use std::fmt::nested::Debug; | 866 | use 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 | " |
887 | use crate::{ | 885 | use 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 | " |
910 | use std::fmt as foo; | 908 | use 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 | " |
929 | impl foo<|> for Foo { | 927 | impl 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 | " |
940 | use std::fmt<|>; | 938 | use 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 | " |
950 | mod foo { | 948 | mod 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 @@ | |||
1 | use std::iter::successors; | 1 | use std::iter::successors; |
2 | 2 | ||
3 | use hir::db::HirDatabase; | ||
4 | use ra_syntax::{ast, AstNode, TextUnit, T}; | 3 | use ra_syntax::{ast, AstNode, TextUnit, T}; |
5 | 4 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use 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 | // ``` |
19 | pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 18 | pub(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; | |||
9 | mod marks; | 9 | mod marks; |
10 | #[cfg(test)] | 10 | #[cfg(test)] |
11 | mod doc_tests; | 11 | mod doc_tests; |
12 | #[cfg(test)] | 12 | mod utils; |
13 | mod test_db; | ||
14 | pub mod ast_transform; | 13 | pub mod ast_transform; |
15 | 14 | ||
16 | use either::Either; | ||
17 | use hir::db::HirDatabase; | ||
18 | use ra_db::FileRange; | 15 | use ra_db::FileRange; |
16 | use ra_ide_db::RootDatabase; | ||
19 | use ra_syntax::{TextRange, TextUnit}; | 17 | use ra_syntax::{TextRange, TextUnit}; |
20 | use ra_text_edit::TextEdit; | 18 | use ra_text_edit::TextEdit; |
21 | 19 | ||
22 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; | 20 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler}; |
23 | pub use crate::assists::add_import::auto_import_text_edit; | 21 | pub 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)] | ||
36 | pub struct GroupLabel(pub String); | ||
37 | |||
38 | impl 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)] |
38 | pub struct AssistAction { | 47 | pub 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)] |
46 | pub struct ResolvedAssist { | 55 | pub 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 | |||
51 | impl 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. |
64 | pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel> | 65 | pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> { |
65 | where | 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. |
84 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> | 79 | pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { |
85 | where | 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 | ||
109 | mod assists { | 91 | mod 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)] |
174 | mod helpers { | 157 | mod 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)] |
295 | mod tests { | 256 | mod 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 | |||
3 | use std::sync::Arc; | ||
4 | |||
5 | use 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)] | ||
16 | pub struct TestDB { | ||
17 | runtime: salsa::Runtime<TestDB>, | ||
18 | } | ||
19 | |||
20 | impl 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 | |||
29 | impl std::panic::RefUnwindSafe for TestDB {} | ||
30 | |||
31 | impl 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 | |||
3 | use ra_syntax::{ | ||
4 | ast::{self, make}, | ||
5 | T, | ||
6 | }; | ||
7 | |||
8 | pub(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 | |||
15 | fn 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 | } | ||