diff options
author | Kirill Bulatov <[email protected]> | 2019-12-24 00:19:09 +0000 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2020-01-26 22:16:29 +0000 |
commit | 316795e074dff8f627f8c70c85d236420ecfb3a6 (patch) | |
tree | da6e266139563ef314d0563a01723ae2264609d2 /crates/ra_assists/src/lib.rs | |
parent | d1330a4a65f0113c687716a5a679239af4df9c11 (diff) |
Initial auto import action implementation
Diffstat (limited to 'crates/ra_assists/src/lib.rs')
-rw-r--r-- | crates/ra_assists/src/lib.rs | 126 |
1 files changed, 113 insertions, 13 deletions
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3337805a5..4029962f7 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -14,9 +14,9 @@ mod test_db; | |||
14 | pub mod ast_transform; | 14 | pub mod ast_transform; |
15 | 15 | ||
16 | use either::Either; | 16 | use either::Either; |
17 | use hir::db::HirDatabase; | 17 | use hir::{db::HirDatabase, InFile, ModPath, Module}; |
18 | use ra_db::FileRange; | 18 | use ra_db::FileRange; |
19 | use ra_syntax::{TextRange, TextUnit}; | 19 | use ra_syntax::{ast::NameRef, TextRange, TextUnit}; |
20 | use ra_text_edit::TextEdit; | 20 | use ra_text_edit::TextEdit; |
21 | 21 | ||
22 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; | 22 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; |
@@ -77,6 +77,55 @@ where | |||
77 | }) | 77 | }) |
78 | } | 78 | } |
79 | 79 | ||
80 | /// A functionality for locating imports for the given name. | ||
81 | /// | ||
82 | /// Currently has to be a trait with the real implementation provided by the ra_ide_api crate, | ||
83 | /// due to the search functionality located there. | ||
84 | /// Later, this trait should be removed completely and the search functionality moved to a separate crate, | ||
85 | /// accessible from the ra_assists crate. | ||
86 | pub trait ImportsLocator<'a> { | ||
87 | /// Finds all imports for the given name and the module that contains this name. | ||
88 | fn find_imports( | ||
89 | &mut self, | ||
90 | name_to_import: InFile<&NameRef>, | ||
91 | module_with_name_to_import: Module, | ||
92 | ) -> Option<Vec<ModPath>>; | ||
93 | } | ||
94 | |||
95 | /// Return all the assists applicable at the given position | ||
96 | /// and additional assists that need the imports locator functionality to work. | ||
97 | /// | ||
98 | /// Assists are returned in the "resolved" state, that is with edit fully | ||
99 | /// computed. | ||
100 | pub fn assists_with_imports_locator<'a, H, F: 'a>( | ||
101 | db: &H, | ||
102 | range: FileRange, | ||
103 | mut imports_locator: F, | ||
104 | ) -> Vec<ResolvedAssist> | ||
105 | where | ||
106 | H: HirDatabase + 'static, | ||
107 | F: ImportsLocator<'a>, | ||
108 | { | ||
109 | AssistCtx::with_ctx(db, range, true, |ctx| { | ||
110 | let mut assists = assists::all() | ||
111 | .iter() | ||
112 | .map(|f| f(ctx.clone())) | ||
113 | .chain( | ||
114 | assists::all_with_imports_locator() | ||
115 | .iter() | ||
116 | .map(|f| f(ctx.clone(), &mut imports_locator)), | ||
117 | ) | ||
118 | .filter_map(std::convert::identity) | ||
119 | .map(|a| match a { | ||
120 | Assist::Resolved { assist } => assist, | ||
121 | Assist::Unresolved { .. } => unreachable!(), | ||
122 | }) | ||
123 | .collect(); | ||
124 | sort_assists(&mut assists); | ||
125 | assists | ||
126 | }) | ||
127 | } | ||
128 | |||
80 | /// Return all the assists applicable at the given position. | 129 | /// Return all the assists applicable at the given position. |
81 | /// | 130 | /// |
82 | /// Assists are returned in the "resolved" state, that is with edit fully | 131 | /// Assists are returned in the "resolved" state, that is with edit fully |
@@ -85,8 +134,6 @@ pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> | |||
85 | where | 134 | where |
86 | H: HirDatabase + 'static, | 135 | H: HirDatabase + 'static, |
87 | { | 136 | { |
88 | use std::cmp::Ordering; | ||
89 | |||
90 | AssistCtx::with_ctx(db, range, true, |ctx| { | 137 | AssistCtx::with_ctx(db, range, true, |ctx| { |
91 | let mut a = assists::all() | 138 | let mut a = assists::all() |
92 | .iter() | 139 | .iter() |
@@ -95,19 +142,24 @@ where | |||
95 | Assist::Resolved { assist } => assist, | 142 | Assist::Resolved { assist } => assist, |
96 | Assist::Unresolved { .. } => unreachable!(), | 143 | Assist::Unresolved { .. } => unreachable!(), |
97 | }) | 144 | }) |
98 | .collect::<Vec<_>>(); | 145 | .collect(); |
99 | a.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) { | 146 | sort_assists(&mut a); |
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 | 147 | a |
106 | }) | 148 | }) |
107 | } | 149 | } |
108 | 150 | ||
151 | fn sort_assists(assists: &mut Vec<ResolvedAssist>) { | ||
152 | use std::cmp::Ordering; | ||
153 | assists.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) { | ||
154 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | ||
155 | (Some(_), None) => Ordering::Less, | ||
156 | (None, Some(_)) => Ordering::Greater, | ||
157 | (None, None) => Ordering::Equal, | ||
158 | }); | ||
159 | } | ||
160 | |||
109 | mod assists { | 161 | mod assists { |
110 | use crate::{Assist, AssistCtx}; | 162 | use crate::{Assist, AssistCtx, ImportsLocator}; |
111 | use hir::db::HirDatabase; | 163 | use hir::db::HirDatabase; |
112 | 164 | ||
113 | mod add_derive; | 165 | mod add_derive; |
@@ -116,6 +168,7 @@ mod assists { | |||
116 | mod add_custom_impl; | 168 | mod add_custom_impl; |
117 | mod add_new; | 169 | mod add_new; |
118 | mod apply_demorgan; | 170 | mod apply_demorgan; |
171 | mod auto_import; | ||
119 | mod invert_if; | 172 | mod invert_if; |
120 | mod flip_comma; | 173 | mod flip_comma; |
121 | mod flip_binexpr; | 174 | mod flip_binexpr; |
@@ -168,6 +221,11 @@ mod assists { | |||
168 | early_return::convert_to_guarded_return, | 221 | early_return::convert_to_guarded_return, |
169 | ] | 222 | ] |
170 | } | 223 | } |
224 | |||
225 | pub(crate) fn all_with_imports_locator<'a, DB: HirDatabase, F: ImportsLocator<'a>>( | ||
226 | ) -> &'a [fn(AssistCtx<DB>, &mut F) -> Option<Assist>] { | ||
227 | &[auto_import::auto_import] | ||
228 | } | ||
171 | } | 229 | } |
172 | 230 | ||
173 | #[cfg(test)] | 231 | #[cfg(test)] |
@@ -176,7 +234,7 @@ mod helpers { | |||
176 | use ra_syntax::TextRange; | 234 | use ra_syntax::TextRange; |
177 | use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; | 235 | use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; |
178 | 236 | ||
179 | use crate::{test_db::TestDB, Assist, AssistCtx}; | 237 | use crate::{test_db::TestDB, Assist, AssistCtx, ImportsLocator}; |
180 | 238 | ||
181 | pub(crate) fn check_assist( | 239 | pub(crate) fn check_assist( |
182 | assist: fn(AssistCtx<TestDB>) -> Option<Assist>, | 240 | assist: fn(AssistCtx<TestDB>) -> Option<Assist>, |
@@ -206,6 +264,35 @@ mod helpers { | |||
206 | assert_eq_text!(after, &actual); | 264 | assert_eq_text!(after, &actual); |
207 | } | 265 | } |
208 | 266 | ||
267 | pub(crate) fn check_assist_with_imports_locator<'a, F: ImportsLocator<'a>>( | ||
268 | assist: fn(AssistCtx<TestDB>, &mut F) -> Option<Assist>, | ||
269 | imports_locator: &mut F, | ||
270 | before: &str, | ||
271 | after: &str, | ||
272 | ) { | ||
273 | let (before_cursor_pos, before) = extract_offset(before); | ||
274 | let (db, file_id) = TestDB::with_single_file(&before); | ||
275 | let frange = | ||
276 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | ||
277 | let assist = AssistCtx::with_ctx(&db, frange, true, |ctx| assist(ctx, imports_locator)) | ||
278 | .expect("code action is not applicable"); | ||
279 | let action = match assist { | ||
280 | Assist::Unresolved { .. } => unreachable!(), | ||
281 | Assist::Resolved { assist } => assist.get_first_action(), | ||
282 | }; | ||
283 | |||
284 | let actual = action.edit.apply(&before); | ||
285 | let actual_cursor_pos = match action.cursor_position { | ||
286 | None => action | ||
287 | .edit | ||
288 | .apply_to_offset(before_cursor_pos) | ||
289 | .expect("cursor position is affected by the edit"), | ||
290 | Some(off) => off, | ||
291 | }; | ||
292 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
293 | assert_eq_text!(after, &actual); | ||
294 | } | ||
295 | |||
209 | pub(crate) fn check_assist_range( | 296 | pub(crate) fn check_assist_range( |
210 | assist: fn(AssistCtx<TestDB>) -> Option<Assist>, | 297 | assist: fn(AssistCtx<TestDB>) -> Option<Assist>, |
211 | before: &str, | 298 | before: &str, |
@@ -279,6 +366,19 @@ mod helpers { | |||
279 | assert!(assist.is_none()); | 366 | assert!(assist.is_none()); |
280 | } | 367 | } |
281 | 368 | ||
369 | pub(crate) fn check_assist_with_imports_locator_not_applicable<'a, F: ImportsLocator<'a>>( | ||
370 | assist: fn(AssistCtx<TestDB>, &mut F) -> Option<Assist>, | ||
371 | imports_locator: &mut F, | ||
372 | before: &str, | ||
373 | ) { | ||
374 | let (before_cursor_pos, before) = extract_offset(before); | ||
375 | let (db, file_id) = TestDB::with_single_file(&before); | ||
376 | let frange = | ||
377 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | ||
378 | let assist = AssistCtx::with_ctx(&db, frange, true, |ctx| assist(ctx, imports_locator)); | ||
379 | assert!(assist.is_none()); | ||
380 | } | ||
381 | |||
282 | pub(crate) fn check_assist_range_not_applicable( | 382 | pub(crate) fn check_assist_range_not_applicable( |
283 | assist: fn(AssistCtx<TestDB>) -> Option<Assist>, | 383 | assist: fn(AssistCtx<TestDB>) -> Option<Assist>, |
284 | before: &str, | 384 | before: &str, |