]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! `assists` crate provides a bunch of code assists, also known as code actions |
2 | //! (in LSP) or intentions (in IntelliJ). | |
3 | //! | |
4 | //! An assist is a micro-refactoring, which is automatically activated in | |
5 | //! certain context. For example, if the cursor is over `,`, a "swap `,`" assist | |
6 | //! becomes available. | |
7 | //! | |
8 | //! ## Assists Guidelines | |
9 | //! | |
10 | //! Assists are the main mechanism to deliver advanced IDE features to the user, | |
11 | //! so we should pay extra attention to the UX. | |
12 | //! | |
13 | //! The power of assists comes from their context-awareness. The main problem | |
14 | //! with IDE features is that there are a lot of them, and it's hard to teach | |
15 | //! the user what's available. Assists solve this problem nicely: 💡 signifies | |
16 | //! that *something* is possible, and clicking on it reveals a *short* list of | |
17 | //! actions. Contrast it with Emacs `M-x`, which just spits an infinite list of | |
18 | //! all the features. | |
19 | //! | |
20 | //! Here are some considerations when creating a new assist: | |
21 | //! | |
22 | //! * It's good to preserve semantics, and it's good to keep the code compiling, | |
23 | //! but it isn't necessary. Example: "flip binary operation" might change | |
24 | //! semantics. | |
25 | //! * Assist shouldn't necessary make the code "better". A lot of assist come in | |
26 | //! pairs: "if let <-> match". | |
27 | //! * Assists should have as narrow scope as possible. Each new assists greatly | |
28 | //! improves UX for cases where the user actually invokes it, but it makes UX | |
29 | //! worse for every case where the user clicks 💡 to invoke some *other* | |
30 | //! assist. So, a rarely useful assist which is always applicable can be a net | |
31 | //! negative. | |
32 | //! * Rarely useful actions are tricky. Sometimes there are features which are | |
33 | //! clearly useful to some users, but are just noise most of the time. We | |
34 | //! don't have a good solution here, our current approach is to make this | |
35 | //! functionality available only if assist is applicable to the whole | |
36 | //! selection. Example: `sort_items` sorts items alphabetically. Naively, it | |
37 | //! should be available more or less everywhere, which isn't useful. So | |
38 | //! instead we only show it if the user *selects* the items they want to sort. | |
39 | //! * Consider grouping related assists together (see [`Assists::add_group`]). | |
40 | //! * Make assists robust. If the assist depends on results of type-inference too | |
41 | //! much, it might only fire in fully-correct code. This makes assist less | |
42 | //! useful and (worse) less predictable. The user should have a clear | |
43 | //! intuition when each particular assist is available. | |
44 | //! * Make small assists, which compose. Example: rather than auto-importing | |
45 | //! enums in `add_missing_match_arms`, we use fully-qualified names. There's a | |
46 | //! separate assist to shorten a fully-qualified name. | |
47 | //! * Distinguish between assists and fixits for diagnostics. Internally, fixits | |
48 | //! and assists are equivalent. They have the same "show a list + invoke a | |
49 | //! single element" workflow, and both use [`Assist`] data structure. The main | |
50 | //! difference is in the UX: while 💡 looks only at the cursor position, | |
51 | //! diagnostics squigglies and fixits are calculated for the whole file and | |
52 | //! are presented to the user eagerly. So, diagnostics should be fixable | |
53 | //! errors, while assists can be just suggestions for an alternative way to do | |
54 | //! something. If something *could* be a diagnostic, it should be a | |
55 | //! diagnostic. Conversely, it might be valuable to turn a diagnostic with a | |
56 | //! lot of false errors into an assist. | |
57 | //! | |
58 | //! See also this post: | |
59 | //! <https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html> | |
60 | ||
61 | #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] | |
62 | ||
63 | #[allow(unused)] | |
64 | macro_rules! eprintln { | |
65 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; | |
66 | } | |
67 | ||
68 | mod assist_config; | |
69 | mod assist_context; | |
70 | #[cfg(test)] | |
71 | mod tests; | |
72 | pub mod utils; | |
73 | ||
74 | use hir::Semantics; | |
75 | use ide_db::{base_db::FileRange, RootDatabase}; | |
76 | use syntax::TextRange; | |
77 | ||
78 | pub(crate) use crate::assist_context::{AssistContext, Assists}; | |
79 | ||
80 | pub use assist_config::AssistConfig; | |
81 | pub use ide_db::assists::{ | |
82 | Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, SingleResolve, | |
83 | }; | |
84 | ||
85 | /// Return all the assists applicable at the given position. | |
86 | /// | |
87 | // NOTE: We don't have a `Feature: ` section for assists, they are special-cased | |
88 | // in the manual. | |
89 | pub fn assists( | |
90 | db: &RootDatabase, | |
91 | config: &AssistConfig, | |
92 | resolve: AssistResolveStrategy, | |
93 | range: FileRange, | |
94 | ) -> Vec<Assist> { | |
95 | let sema = Semantics::new(db); | |
96 | let ctx = AssistContext::new(sema, config, range); | |
97 | let mut acc = Assists::new(&ctx, resolve); | |
98 | handlers::all().iter().for_each(|handler| { | |
99 | handler(&mut acc, &ctx); | |
100 | }); | |
101 | acc.finish() | |
102 | } | |
103 | ||
104 | mod handlers { | |
105 | use crate::{AssistContext, Assists}; | |
106 | ||
107 | pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>; | |
108 | ||
109 | mod add_explicit_type; | |
110 | mod add_label_to_loop; | |
111 | mod add_lifetime_to_type; | |
112 | mod add_missing_impl_members; | |
113 | mod add_turbo_fish; | |
114 | mod apply_demorgan; | |
115 | mod auto_import; | |
116 | mod change_visibility; | |
117 | mod convert_bool_then; | |
118 | mod convert_comment_block; | |
119 | mod convert_integer_literal; | |
120 | mod convert_into_to_from; | |
121 | mod convert_iter_for_each_to_for; | |
122 | mod convert_let_else_to_match; | |
123 | mod convert_tuple_struct_to_named_struct; | |
124 | mod convert_to_guarded_return; | |
f2b60f7d | 125 | mod convert_two_arm_bool_match_to_matches_macro; |
064997fb FG |
126 | mod convert_while_to_loop; |
127 | mod destructure_tuple_binding; | |
128 | mod expand_glob_import; | |
129 | mod extract_function; | |
130 | mod extract_module; | |
131 | mod extract_struct_from_enum_variant; | |
132 | mod extract_type_alias; | |
133 | mod extract_variable; | |
134 | mod add_missing_match_arms; | |
135 | mod fix_visibility; | |
136 | mod flip_binexpr; | |
137 | mod flip_comma; | |
138 | mod flip_trait_bound; | |
139 | mod generate_constant; | |
140 | mod generate_default_from_enum_variant; | |
141 | mod generate_default_from_new; | |
142 | mod generate_deref; | |
143 | mod generate_derive; | |
144 | mod generate_documentation_template; | |
145 | mod generate_enum_is_method; | |
146 | mod generate_enum_projection_method; | |
147 | mod generate_enum_variant; | |
148 | mod generate_from_impl_for_enum; | |
149 | mod generate_function; | |
150 | mod generate_getter; | |
151 | mod generate_impl; | |
152 | mod generate_is_empty_from_len; | |
153 | mod generate_new; | |
154 | mod generate_setter; | |
155 | mod generate_delegate_methods; | |
156 | mod add_return_type; | |
157 | mod inline_call; | |
158 | mod inline_local_variable; | |
159 | mod inline_type_alias; | |
160 | mod introduce_named_lifetime; | |
161 | mod invert_if; | |
162 | mod merge_imports; | |
163 | mod merge_match_arms; | |
164 | mod move_bounds; | |
165 | mod move_guard; | |
166 | mod move_module_to_file; | |
167 | mod move_to_mod_rs; | |
168 | mod move_from_mod_rs; | |
169 | mod number_representation; | |
170 | mod promote_local_to_const; | |
171 | mod pull_assignment_up; | |
172 | mod qualify_path; | |
173 | mod qualify_method_call; | |
174 | mod raw_string; | |
175 | mod remove_dbg; | |
176 | mod remove_mut; | |
177 | mod remove_unused_param; | |
178 | mod reorder_fields; | |
179 | mod reorder_impl_items; | |
180 | mod replace_try_expr_with_match; | |
181 | mod replace_derive_with_manual_impl; | |
182 | mod replace_if_let_with_match; | |
f2b60f7d | 183 | mod replace_or_with_or_else; |
064997fb FG |
184 | mod introduce_named_generic; |
185 | mod replace_let_with_if_let; | |
186 | mod replace_qualified_name_with_use; | |
187 | mod replace_string_with_char; | |
188 | mod replace_turbofish_with_explicit_type; | |
189 | mod split_import; | |
f2b60f7d | 190 | mod unmerge_match_arm; |
064997fb FG |
191 | mod sort_items; |
192 | mod toggle_ignore; | |
193 | mod unmerge_use; | |
194 | mod unnecessary_async; | |
195 | mod unwrap_block; | |
196 | mod unwrap_result_return_type; | |
197 | mod wrap_return_type_in_result; | |
198 | ||
199 | pub(crate) fn all() -> &'static [Handler] { | |
200 | &[ | |
201 | // These are alphabetic for the foolish consistency | |
202 | add_explicit_type::add_explicit_type, | |
203 | add_label_to_loop::add_label_to_loop, | |
204 | add_missing_match_arms::add_missing_match_arms, | |
205 | add_lifetime_to_type::add_lifetime_to_type, | |
206 | add_return_type::add_return_type, | |
207 | add_turbo_fish::add_turbo_fish, | |
208 | apply_demorgan::apply_demorgan, | |
209 | auto_import::auto_import, | |
210 | change_visibility::change_visibility, | |
211 | convert_bool_then::convert_bool_then_to_if, | |
212 | convert_bool_then::convert_if_to_bool_then, | |
213 | convert_comment_block::convert_comment_block, | |
214 | convert_integer_literal::convert_integer_literal, | |
215 | convert_into_to_from::convert_into_to_from, | |
216 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, | |
217 | convert_iter_for_each_to_for::convert_for_loop_with_for_each, | |
218 | convert_let_else_to_match::convert_let_else_to_match, | |
219 | convert_to_guarded_return::convert_to_guarded_return, | |
220 | convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, | |
f2b60f7d | 221 | convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro, |
064997fb FG |
222 | convert_while_to_loop::convert_while_to_loop, |
223 | destructure_tuple_binding::destructure_tuple_binding, | |
224 | expand_glob_import::expand_glob_import, | |
225 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | |
226 | extract_type_alias::extract_type_alias, | |
227 | fix_visibility::fix_visibility, | |
228 | flip_binexpr::flip_binexpr, | |
229 | flip_comma::flip_comma, | |
230 | flip_trait_bound::flip_trait_bound, | |
231 | generate_constant::generate_constant, | |
232 | generate_default_from_enum_variant::generate_default_from_enum_variant, | |
233 | generate_default_from_new::generate_default_from_new, | |
234 | generate_derive::generate_derive, | |
235 | generate_documentation_template::generate_documentation_template, | |
236 | generate_documentation_template::generate_doc_example, | |
237 | generate_enum_is_method::generate_enum_is_method, | |
238 | generate_enum_projection_method::generate_enum_as_method, | |
239 | generate_enum_projection_method::generate_enum_try_into_method, | |
240 | generate_enum_variant::generate_enum_variant, | |
241 | generate_from_impl_for_enum::generate_from_impl_for_enum, | |
242 | generate_function::generate_function, | |
243 | generate_impl::generate_impl, | |
244 | generate_is_empty_from_len::generate_is_empty_from_len, | |
245 | generate_new::generate_new, | |
246 | inline_call::inline_call, | |
247 | inline_call::inline_into_callers, | |
248 | inline_local_variable::inline_local_variable, | |
249 | inline_type_alias::inline_type_alias, | |
f2b60f7d | 250 | inline_type_alias::inline_type_alias_uses, |
064997fb FG |
251 | introduce_named_generic::introduce_named_generic, |
252 | introduce_named_lifetime::introduce_named_lifetime, | |
253 | invert_if::invert_if, | |
254 | merge_imports::merge_imports, | |
255 | merge_match_arms::merge_match_arms, | |
256 | move_bounds::move_bounds_to_where_clause, | |
257 | move_guard::move_arm_cond_to_match_guard, | |
258 | move_guard::move_guard_to_arm_body, | |
259 | move_module_to_file::move_module_to_file, | |
260 | move_to_mod_rs::move_to_mod_rs, | |
261 | move_from_mod_rs::move_from_mod_rs, | |
262 | number_representation::reformat_number_literal, | |
263 | pull_assignment_up::pull_assignment_up, | |
264 | promote_local_to_const::promote_local_to_const, | |
265 | qualify_path::qualify_path, | |
266 | qualify_method_call::qualify_method_call, | |
267 | raw_string::add_hash, | |
268 | raw_string::make_usual_string, | |
269 | raw_string::remove_hash, | |
270 | remove_dbg::remove_dbg, | |
271 | remove_mut::remove_mut, | |
272 | remove_unused_param::remove_unused_param, | |
273 | reorder_fields::reorder_fields, | |
274 | reorder_impl_items::reorder_impl_items, | |
275 | replace_try_expr_with_match::replace_try_expr_with_match, | |
276 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, | |
277 | replace_if_let_with_match::replace_if_let_with_match, | |
278 | replace_if_let_with_match::replace_match_with_if_let, | |
279 | replace_let_with_if_let::replace_let_with_if_let, | |
f2b60f7d FG |
280 | replace_or_with_or_else::replace_or_else_with_or, |
281 | replace_or_with_or_else::replace_or_with_or_else, | |
064997fb FG |
282 | replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, |
283 | replace_qualified_name_with_use::replace_qualified_name_with_use, | |
284 | sort_items::sort_items, | |
285 | split_import::split_import, | |
286 | toggle_ignore::toggle_ignore, | |
f2b60f7d | 287 | unmerge_match_arm::unmerge_match_arm, |
064997fb FG |
288 | unmerge_use::unmerge_use, |
289 | unnecessary_async::unnecessary_async, | |
290 | unwrap_block::unwrap_block, | |
291 | unwrap_result_return_type::unwrap_result_return_type, | |
292 | wrap_return_type_in_result::wrap_return_type_in_result, | |
293 | // These are manually sorted for better priorities. By default, | |
294 | // priority is determined by the size of the target range (smaller | |
295 | // target wins). If the ranges are equal, position in this list is | |
296 | // used as a tie-breaker. | |
297 | add_missing_impl_members::add_missing_impl_members, | |
298 | add_missing_impl_members::add_missing_default_members, | |
299 | // | |
300 | replace_string_with_char::replace_string_with_char, | |
301 | replace_string_with_char::replace_char_with_string, | |
302 | raw_string::make_raw_string, | |
303 | // | |
304 | extract_variable::extract_variable, | |
305 | extract_function::extract_function, | |
306 | extract_module::extract_module, | |
307 | // | |
308 | generate_getter::generate_getter, | |
309 | generate_getter::generate_getter_mut, | |
310 | generate_setter::generate_setter, | |
311 | generate_delegate_methods::generate_delegate_methods, | |
312 | generate_deref::generate_deref, | |
313 | // Are you sure you want to add new assist here, and not to the | |
314 | // sorted list above? | |
315 | ] | |
316 | } | |
317 | } |