1 //! `ra_assists` crate provides a bunch of code assists, also known as code
2 //! actions (in LSP) or intentions (in IntelliJ).
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
15 use hir::db::HirDatabase;
17 use ra_syntax::{TextRange, TextUnit};
18 use ra_text_edit::TextEdit;
20 pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
21 pub use crate::assists::add_import::auto_import_text_edit;
23 /// Unique identifier of the assist, should not be shown to the user
25 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
26 pub struct AssistId(pub &'static str);
28 #[derive(Debug, Clone)]
29 pub struct AssistLabel {
30 /// Short description of the assist, as shown in the UI.
35 #[derive(Debug, Clone)]
36 pub struct AssistAction {
38 pub cursor_position: Option<TextUnit>,
39 pub target: Option<TextRange>,
42 /// Return all the assists applicable at the given position.
44 /// Assists are returned in the "unresolved" state, that is only labels are
45 /// returned, without actual edits.
46 pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel>
48 H: HirDatabase + 'static,
50 AssistCtx::with_ctx(db, range, false, |ctx| {
53 .filter_map(|f| f(ctx.clone()))
55 Assist::Unresolved { label } => label,
56 Assist::Resolved { .. } => unreachable!(),
62 /// Return all the assists applicable at the given position.
64 /// Assists are returned in the "resolved" state, that is with edit fully
66 pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
68 H: HirDatabase + 'static,
70 use std::cmp::Ordering;
72 AssistCtx::with_ctx(db, range, true, |ctx| {
73 let mut a = assists::all()
75 .filter_map(|f| f(ctx.clone()))
77 Assist::Resolved { label, action } => (label, action),
78 Assist::Unresolved { .. } => unreachable!(),
81 a.sort_by(|a, b| match (a.1.target, b.1.target) {
82 (Some(a), Some(b)) => a.len().cmp(&b.len()),
83 (Some(_), None) => Ordering::Less,
84 (None, Some(_)) => Ordering::Greater,
85 (None, None) => Ordering::Equal,
92 use crate::{Assist, AssistCtx};
93 use hir::db::HirDatabase;
96 mod add_explicit_type;
103 mod flip_trait_bound;
104 mod change_visibility;
106 mod merge_match_arms;
107 mod introduce_variable;
108 mod inline_local_variable;
110 mod replace_if_let_with_match;
113 pub(crate) mod add_import;
114 mod add_missing_impl_members;
119 pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {
121 add_derive::add_derive,
122 add_explicit_type::add_explicit_type,
125 apply_demorgan::apply_demorgan,
126 invert_if::invert_if,
127 change_visibility::change_visibility,
128 fill_match_arms::fill_match_arms,
129 merge_match_arms::merge_match_arms,
130 flip_comma::flip_comma,
131 flip_binexpr::flip_binexpr,
132 flip_trait_bound::flip_trait_bound,
133 introduce_variable::introduce_variable,
134 replace_if_let_with_match::replace_if_let_with_match,
135 split_import::split_import,
136 remove_dbg::remove_dbg,
137 add_import::add_import,
138 add_missing_impl_members::add_missing_impl_members,
139 add_missing_impl_members::add_missing_default_members,
140 inline_local_variable::inline_local_varialbe,
141 move_guard::move_guard_to_arm_body,
142 move_guard::move_arm_cond_to_match_guard,
143 move_bounds::move_bounds_to_where_clause,
144 raw_string::add_hash,
145 raw_string::make_raw_string,
146 raw_string::make_usual_string,
147 raw_string::remove_hash,
148 early_return::convert_to_guarded_return,
155 use ra_db::{fixture::WithFixture, FileRange};
156 use ra_syntax::TextRange;
157 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
159 use crate::{test_db::TestDB, Assist, AssistCtx};
161 pub(crate) fn check_assist(
162 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
166 let (before_cursor_pos, before) = extract_offset(before);
167 let (db, file_id) = TestDB::with_single_file(&before);
169 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
171 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
172 let action = match assist {
173 Assist::Unresolved { .. } => unreachable!(),
174 Assist::Resolved { action, .. } => action,
177 let actual = action.edit.apply(&before);
178 let actual_cursor_pos = match action.cursor_position {
181 .apply_to_offset(before_cursor_pos)
182 .expect("cursor position is affected by the edit"),
185 let actual = add_cursor(&actual, actual_cursor_pos);
186 assert_eq_text!(after, &actual);
189 pub(crate) fn check_assist_range(
190 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
194 let (range, before) = extract_range(before);
195 let (db, file_id) = TestDB::with_single_file(&before);
196 let frange = FileRange { file_id, range };
198 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
199 let action = match assist {
200 Assist::Unresolved { .. } => unreachable!(),
201 Assist::Resolved { action, .. } => action,
204 let mut actual = action.edit.apply(&before);
205 if let Some(pos) = action.cursor_position {
206 actual = add_cursor(&actual, pos);
208 assert_eq_text!(after, &actual);
211 pub(crate) fn check_assist_target(
212 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
216 let (before_cursor_pos, before) = extract_offset(before);
217 let (db, file_id) = TestDB::with_single_file(&before);
219 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
221 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
222 let action = match assist {
223 Assist::Unresolved { .. } => unreachable!(),
224 Assist::Resolved { action, .. } => action,
227 let range = action.target.expect("expected target on action");
228 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
231 pub(crate) fn check_assist_range_target(
232 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
236 let (range, before) = extract_range(before);
237 let (db, file_id) = TestDB::with_single_file(&before);
238 let frange = FileRange { file_id, range };
240 AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable");
241 let action = match assist {
242 Assist::Unresolved { .. } => unreachable!(),
243 Assist::Resolved { action, .. } => action,
246 let range = action.target.expect("expected target on action");
247 assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target);
250 pub(crate) fn check_assist_not_applicable(
251 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
254 let (before_cursor_pos, before) = extract_offset(before);
255 let (db, file_id) = TestDB::with_single_file(&before);
257 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
258 let assist = AssistCtx::with_ctx(&db, frange, true, assist);
259 assert!(assist.is_none());
262 pub(crate) fn check_assist_range_not_applicable(
263 assist: fn(AssistCtx<TestDB>) -> Option<Assist>,
266 let (range, before) = extract_range(before);
267 let (db, file_id) = TestDB::with_single_file(&before);
268 let frange = FileRange { file_id, range };
269 let assist = AssistCtx::with_ctx(&db, frange, true, assist);
270 assert!(assist.is_none());
276 use ra_db::{fixture::WithFixture, FileRange};
277 use ra_syntax::TextRange;
278 use test_utils::{extract_offset, extract_range};
280 use crate::test_db::TestDB;
283 fn assist_order_field_struct() {
284 let before = "struct Foo { <|>bar: u32 }";
285 let (before_cursor_pos, before) = extract_offset(before);
286 let (db, file_id) = TestDB::with_single_file(&before);
288 FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
289 let assists = super::assists(&db, frange);
290 let mut assists = assists.iter();
292 assert_eq!(assists.next().expect("expected assist").0.label, "make pub(crate)");
293 assert_eq!(assists.next().expect("expected assist").0.label, "add `#[derive]`");
297 fn assist_order_if_expr() {
299 pub fn test_some_range(a: int) -> bool {
300 if let 2..6 = <|>5<|> {
306 let (range, before) = extract_range(before);
307 let (db, file_id) = TestDB::with_single_file(&before);
308 let frange = FileRange { file_id, range };
309 let assists = super::assists(&db, frange);
310 let mut assists = assists.iter();
312 assert_eq!(assists.next().expect("expected assist").0.label, "introduce variable");
313 assert_eq!(assists.next().expect("expected assist").0.label, "replace with match");