aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/lib.rs')
-rw-r--r--crates/ra_assists/src/lib.rs209
1 files changed, 85 insertions, 124 deletions
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 3337805a5..828a8e9e8 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -9,18 +9,16 @@ mod assist_ctx;
9mod marks; 9mod marks;
10#[cfg(test)] 10#[cfg(test)]
11mod doc_tests; 11mod doc_tests;
12#[cfg(test)] 12mod utils;
13mod test_db;
14pub mod ast_transform; 13pub mod ast_transform;
15 14
16use either::Either;
17use hir::db::HirDatabase;
18use ra_db::FileRange; 15use ra_db::FileRange;
16use ra_ide_db::RootDatabase;
19use ra_syntax::{TextRange, TextUnit}; 17use ra_syntax::{TextRange, TextUnit};
20use ra_text_edit::TextEdit; 18use ra_text_edit::TextEdit;
21 19
22pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; 20pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler};
23pub use crate::assists::add_import::auto_import_text_edit; 21pub use crate::handlers::replace_qualified_name_with_use::insert_use_statement;
24 22
25/// Unique identifier of the assist, should not be shown to the user 23/// Unique identifier of the assist, should not be shown to the user
26/// directly. 24/// directly.
@@ -34,81 +32,64 @@ pub struct AssistLabel {
34 pub id: AssistId, 32 pub id: AssistId,
35} 33}
36 34
35#[derive(Clone, Debug)]
36pub struct GroupLabel(pub String);
37
38impl AssistLabel {
39 pub(crate) fn new(label: String, id: AssistId) -> AssistLabel {
40 // FIXME: make fields private, so that this invariant can't be broken
41 assert!(label.chars().nth(0).unwrap().is_uppercase());
42 AssistLabel { label: label.into(), id }
43 }
44}
45
37#[derive(Debug, Clone)] 46#[derive(Debug, Clone)]
38pub struct AssistAction { 47pub struct AssistAction {
39 pub label: Option<String>,
40 pub edit: TextEdit, 48 pub edit: TextEdit,
41 pub cursor_position: Option<TextUnit>, 49 pub cursor_position: Option<TextUnit>,
50 // FIXME: This belongs to `AssistLabel`
42 pub target: Option<TextRange>, 51 pub target: Option<TextRange>,
43} 52}
44 53
45#[derive(Debug, Clone)] 54#[derive(Debug, Clone)]
46pub struct ResolvedAssist { 55pub struct ResolvedAssist {
47 pub label: AssistLabel, 56 pub label: AssistLabel,
48 pub action_data: Either<AssistAction, Vec<AssistAction>>, 57 pub group_label: Option<GroupLabel>,
49} 58 pub action: AssistAction,
50
51impl ResolvedAssist {
52 pub fn get_first_action(&self) -> AssistAction {
53 match &self.action_data {
54 Either::Left(action) => action.clone(),
55 Either::Right(actions) => actions[0].clone(),
56 }
57 }
58} 59}
59 60
60/// Return all the assists applicable at the given position. 61/// Return all the assists applicable at the given position.
61/// 62///
62/// Assists are returned in the "unresolved" state, that is only labels are 63/// Assists are returned in the "unresolved" state, that is only labels are
63/// returned, without actual edits. 64/// returned, without actual edits.
64pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel> 65pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> {
65where 66 let ctx = AssistCtx::new(db, range, false);
66 H: HirDatabase + 'static, 67 handlers::all()
67{ 68 .iter()
68 AssistCtx::with_ctx(db, range, false, |ctx| { 69 .filter_map(|f| f(ctx.clone()))
69 assists::all() 70 .flat_map(|it| it.0)
70 .iter() 71 .map(|a| a.label)
71 .filter_map(|f| f(ctx.clone())) 72 .collect()
72 .map(|a| match a {
73 Assist::Unresolved { label } => label,
74 Assist::Resolved { .. } => unreachable!(),
75 })
76 .collect()
77 })
78} 73}
79 74
80/// Return all the assists applicable at the given position. 75/// Return all the assists applicable at the given position.
81/// 76///
82/// Assists are returned in the "resolved" state, that is with edit fully 77/// Assists are returned in the "resolved" state, that is with edit fully
83/// computed. 78/// computed.
84pub fn assists<H>(db: &H, range: FileRange) -> Vec<ResolvedAssist> 79pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> {
85where 80 let ctx = AssistCtx::new(db, range, true);
86 H: HirDatabase + 'static, 81 let mut a = handlers::all()
87{ 82 .iter()
88 use std::cmp::Ordering; 83 .filter_map(|f| f(ctx.clone()))
89 84 .flat_map(|it| it.0)
90 AssistCtx::with_ctx(db, range, true, |ctx| { 85 .map(|it| it.into_resolved().unwrap())
91 let mut a = assists::all() 86 .collect::<Vec<_>>();
92 .iter() 87 a.sort_by_key(|it| it.action.target.map_or(TextUnit::from(!0u32), |it| it.len()));
93 .filter_map(|f| f(ctx.clone())) 88 a
94 .map(|a| match a {
95 Assist::Resolved { assist } => assist,
96 Assist::Unresolved { .. } => unreachable!(),
97 })
98 .collect::<Vec<_>>();
99 a.sort_by(|a, b| match (a.get_first_action().target, b.get_first_action().target) {
100 (Some(a), Some(b)) => a.len().cmp(&b.len()),
101 (Some(_), None) => Ordering::Less,
102 (None, Some(_)) => Ordering::Greater,
103 (None, None) => Ordering::Equal,
104 });
105 a
106 })
107} 89}
108 90
109mod assists { 91mod handlers {
110 use crate::{Assist, AssistCtx}; 92 use crate::AssistHandler;
111 use hir::db::HirDatabase;
112 93
113 mod add_derive; 94 mod add_derive;
114 mod add_explicit_type; 95 mod add_explicit_type;
@@ -116,6 +97,7 @@ mod assists {
116 mod add_custom_impl; 97 mod add_custom_impl;
117 mod add_new; 98 mod add_new;
118 mod apply_demorgan; 99 mod apply_demorgan;
100 mod auto_import;
119 mod invert_if; 101 mod invert_if;
120 mod flip_comma; 102 mod flip_comma;
121 mod flip_binexpr; 103 mod flip_binexpr;
@@ -129,13 +111,13 @@ mod assists {
129 mod replace_if_let_with_match; 111 mod replace_if_let_with_match;
130 mod split_import; 112 mod split_import;
131 mod remove_dbg; 113 mod remove_dbg;
132 pub(crate) mod add_import; 114 pub(crate) mod replace_qualified_name_with_use;
133 mod add_missing_impl_members; 115 mod add_missing_impl_members;
134 mod move_guard; 116 mod move_guard;
135 mod move_bounds; 117 mod move_bounds;
136 mod early_return; 118 mod early_return;
137 119
138 pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 120 pub(crate) fn all() -> &'static [AssistHandler] {
139 &[ 121 &[
140 add_derive::add_derive, 122 add_derive::add_derive,
141 add_explicit_type::add_explicit_type, 123 add_explicit_type::add_explicit_type,
@@ -154,7 +136,7 @@ mod assists {
154 replace_if_let_with_match::replace_if_let_with_match, 136 replace_if_let_with_match::replace_if_let_with_match,
155 split_import::split_import, 137 split_import::split_import,
156 remove_dbg::remove_dbg, 138 remove_dbg::remove_dbg,
157 add_import::add_import, 139 replace_qualified_name_with_use::replace_qualified_name_with_use,
158 add_missing_impl_members::add_missing_impl_members, 140 add_missing_impl_members::add_missing_impl_members,
159 add_missing_impl_members::add_missing_default_members, 141 add_missing_impl_members::add_missing_default_members,
160 inline_local_variable::inline_local_variable, 142 inline_local_variable::inline_local_variable,
@@ -166,33 +148,39 @@ mod assists {
166 raw_string::make_usual_string, 148 raw_string::make_usual_string,
167 raw_string::remove_hash, 149 raw_string::remove_hash,
168 early_return::convert_to_guarded_return, 150 early_return::convert_to_guarded_return,
151 auto_import::auto_import,
169 ] 152 ]
170 } 153 }
171} 154}
172 155
173#[cfg(test)] 156#[cfg(test)]
174mod helpers { 157mod helpers {
175 use ra_db::{fixture::WithFixture, FileRange}; 158 use std::sync::Arc;
159
160 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
161 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
176 use ra_syntax::TextRange; 162 use ra_syntax::TextRange;
177 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; 163 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
178 164
179 use crate::{test_db::TestDB, Assist, AssistCtx}; 165 use crate::{AssistCtx, AssistHandler};
166
167 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
168 let (mut db, file_id) = RootDatabase::with_single_file(text);
169 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
170 // but it looks like this might need specialization? :(
171 let local_roots = vec![db.file_source_root(file_id)];
172 db.set_local_roots(Arc::new(local_roots));
173 (db, file_id)
174 }
180 175
181 pub(crate) fn check_assist( 176 pub(crate) fn check_assist(assist: AssistHandler, before: &str, after: &str) {
182 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
183 before: &str,
184 after: &str,
185 ) {
186 let (before_cursor_pos, before) = extract_offset(before); 177 let (before_cursor_pos, before) = extract_offset(before);
187 let (db, file_id) = TestDB::with_single_file(&before); 178 let (db, file_id) = with_single_file(&before);
188 let frange = 179 let frange =
189 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 180 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
190 let assist = 181 let assist =
191 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 182 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
192 let action = match assist { 183 let action = assist.0[0].action.clone().unwrap();
193 Assist::Unresolved { .. } => unreachable!(),
194 Assist::Resolved { assist } => assist.get_first_action(),
195 };
196 184
197 let actual = action.edit.apply(&before); 185 let actual = action.edit.apply(&before);
198 let actual_cursor_pos = match action.cursor_position { 186 let actual_cursor_pos = match action.cursor_position {
@@ -206,20 +194,13 @@ mod helpers {
206 assert_eq_text!(after, &actual); 194 assert_eq_text!(after, &actual);
207 } 195 }
208 196
209 pub(crate) fn check_assist_range( 197 pub(crate) fn check_assist_range(assist: AssistHandler, before: &str, after: &str) {
210 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
211 before: &str,
212 after: &str,
213 ) {
214 let (range, before) = extract_range(before); 198 let (range, before) = extract_range(before);
215 let (db, file_id) = TestDB::with_single_file(&before); 199 let (db, file_id) = with_single_file(&before);
216 let frange = FileRange { file_id, range }; 200 let frange = FileRange { file_id, range };
217 let assist = 201 let assist =
218 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 202 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
219 let action = match assist { 203 let action = assist.0[0].action.clone().unwrap();
220 Assist::Unresolved { .. } => unreachable!(),
221 Assist::Resolved { assist } => assist.get_first_action(),
222 };
223 204
224 let mut actual = action.edit.apply(&before); 205 let mut actual = action.edit.apply(&before);
225 if let Some(pos) = action.cursor_position { 206 if let Some(pos) = action.cursor_position {
@@ -228,85 +209,65 @@ mod helpers {
228 assert_eq_text!(after, &actual); 209 assert_eq_text!(after, &actual);
229 } 210 }
230 211
231 pub(crate) fn check_assist_target( 212 pub(crate) fn check_assist_target(assist: AssistHandler, before: &str, target: &str) {
232 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
233 before: &str,
234 target: &str,
235 ) {
236 let (before_cursor_pos, before) = extract_offset(before); 213 let (before_cursor_pos, before) = extract_offset(before);
237 let (db, file_id) = TestDB::with_single_file(&before); 214 let (db, file_id) = with_single_file(&before);
238 let frange = 215 let frange =
239 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 216 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
240 let assist = 217 let assist =
241 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 218 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
242 let action = match assist { 219 let action = assist.0[0].action.clone().unwrap();
243 Assist::Unresolved { .. } => unreachable!(),
244 Assist::Resolved { assist } => assist.get_first_action(),
245 };
246 220
247 let range = action.target.expect("expected target on action"); 221 let range = action.target.expect("expected target on action");
248 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); 222 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
249 } 223 }
250 224
251 pub(crate) fn check_assist_range_target( 225 pub(crate) fn check_assist_range_target(assist: AssistHandler, before: &str, target: &str) {
252 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
253 before: &str,
254 target: &str,
255 ) {
256 let (range, before) = extract_range(before); 226 let (range, before) = extract_range(before);
257 let (db, file_id) = TestDB::with_single_file(&before); 227 let (db, file_id) = with_single_file(&before);
258 let frange = FileRange { file_id, range }; 228 let frange = FileRange { file_id, range };
259 let assist = 229 let assist =
260 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); 230 assist(AssistCtx::new(&db, frange, true)).expect("code action is not applicable");
261 let action = match assist { 231 let action = assist.0[0].action.clone().unwrap();
262 Assist::Unresolved { .. } => unreachable!(),
263 Assist::Resolved { assist } => assist.get_first_action(),
264 };
265 232
266 let range = action.target.expect("expected target on action"); 233 let range = action.target.expect("expected target on action");
267 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); 234 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
268 } 235 }
269 236
270 pub(crate) fn check_assist_not_applicable( 237 pub(crate) fn check_assist_not_applicable(assist: AssistHandler, before: &str) {
271 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
272 before: &str,
273 ) {
274 let (before_cursor_pos, before) = extract_offset(before); 238 let (before_cursor_pos, before) = extract_offset(before);
275 let (db, file_id) = TestDB::with_single_file(&before); 239 let (db, file_id) = with_single_file(&before);
276 let frange = 240 let frange =
277 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 241 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
278 let assist = AssistCtx::with_ctx(&db, frange, true, assist); 242 let assist = assist(AssistCtx::new(&db, frange, true));
279 assert!(assist.is_none()); 243 assert!(assist.is_none());
280 } 244 }
281 245
282 pub(crate) fn check_assist_range_not_applicable( 246 pub(crate) fn check_assist_range_not_applicable(assist: AssistHandler, before: &str) {
283 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
284 before: &str,
285 ) {
286 let (range, before) = extract_range(before); 247 let (range, before) = extract_range(before);
287 let (db, file_id) = TestDB::with_single_file(&before); 248 let (db, file_id) = with_single_file(&before);
288 let frange = FileRange { file_id, range }; 249 let frange = FileRange { file_id, range };
289 let assist = AssistCtx::with_ctx(&db, frange, true, assist); 250 let assist = assist(AssistCtx::new(&db, frange, true));
290 assert!(assist.is_none()); 251 assert!(assist.is_none());
291 } 252 }
292} 253}
293 254
294#[cfg(test)] 255#[cfg(test)]
295mod tests { 256mod tests {
296 use ra_db::{fixture::WithFixture, FileRange}; 257 use ra_db::FileRange;
297 use ra_syntax::TextRange; 258 use ra_syntax::TextRange;
298 use test_utils::{extract_offset, extract_range}; 259 use test_utils::{extract_offset, extract_range};
299 260
300 use crate::test_db::TestDB; 261 use crate::{helpers, resolved_assists};
301 262
302 #[test] 263 #[test]
303 fn assist_order_field_struct() { 264 fn assist_order_field_struct() {
304 let before = "struct Foo { <|>bar: u32 }"; 265 let before = "struct Foo { <|>bar: u32 }";
305 let (before_cursor_pos, before) = extract_offset(before); 266 let (before_cursor_pos, before) = extract_offset(before);
306 let (db, file_id) = TestDB::with_single_file(&before); 267 let (db, file_id) = helpers::with_single_file(&before);
307 let frange = 268 let frange =
308 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; 269 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
309 let assists = super::assists(&db, frange); 270 let assists = resolved_assists(&db, frange);
310 let mut assists = assists.iter(); 271 let mut assists = assists.iter();
311 272
312 assert_eq!( 273 assert_eq!(
@@ -327,9 +288,9 @@ mod tests {
327 } 288 }
328 }"; 289 }";
329 let (range, before) = extract_range(before); 290 let (range, before) = extract_range(before);
330 let (db, file_id) = TestDB::with_single_file(&before); 291 let (db, file_id) = helpers::with_single_file(&before);
331 let frange = FileRange { file_id, range }; 292 let frange = FileRange { file_id, range };
332 let assists = super::assists(&db, frange); 293 let assists = resolved_assists(&db, frange);
333 let mut assists = assists.iter(); 294 let mut assists = assists.iter();
334 295
335 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); 296 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable");