diff options
Diffstat (limited to 'crates/ra_assists/src/lib.rs')
-rw-r--r-- | crates/ra_assists/src/lib.rs | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs new file mode 100644 index 000000000..4e97a84c2 --- /dev/null +++ b/crates/ra_assists/src/lib.rs | |||
@@ -0,0 +1,170 @@ | |||
1 | //! `ra_assits` crate provides a bunch of code assists, aslo known as code | ||
2 | //! actions (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 | mod assist_ctx; | ||
9 | |||
10 | use ra_text_edit::TextEdit; | ||
11 | use ra_syntax::{TextUnit, SyntaxNode, Direction}; | ||
12 | use ra_db::FileRange; | ||
13 | use hir::db::HirDatabase; | ||
14 | |||
15 | pub(crate) use crate::assist_ctx::{AssistCtx, Assist}; | ||
16 | |||
17 | #[derive(Debug)] | ||
18 | pub struct AssistLabel { | ||
19 | /// Short description of the assist, as shown in the UI. | ||
20 | pub label: String, | ||
21 | } | ||
22 | |||
23 | pub struct AssistAction { | ||
24 | pub edit: TextEdit, | ||
25 | pub cursor_position: Option<TextUnit>, | ||
26 | } | ||
27 | |||
28 | /// Return all the assists applicable at the given position. | ||
29 | /// | ||
30 | /// Assists are returned in the "unresolved" state, that is only labels are | ||
31 | /// returned, without actual edits. | ||
32 | pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel> | ||
33 | where | ||
34 | H: HirDatabase + 'static, | ||
35 | { | ||
36 | AssistCtx::with_ctx(db, range, false, |ctx| { | ||
37 | all_assists() | ||
38 | .iter() | ||
39 | .filter_map(|f| f(ctx.clone())) | ||
40 | .map(|a| match a { | ||
41 | Assist::Unresolved(label) => label, | ||
42 | Assist::Resolved(..) => unreachable!(), | ||
43 | }) | ||
44 | .collect() | ||
45 | }) | ||
46 | } | ||
47 | |||
48 | /// Return all the assists applicable at the given position. | ||
49 | /// | ||
50 | /// Assists are returned in the "resolved" state, that is with edit fully | ||
51 | /// computed. | ||
52 | pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)> | ||
53 | where | ||
54 | H: HirDatabase + 'static, | ||
55 | { | ||
56 | AssistCtx::with_ctx(db, range, false, |ctx| { | ||
57 | all_assists() | ||
58 | .iter() | ||
59 | .filter_map(|f| f(ctx.clone())) | ||
60 | .map(|a| match a { | ||
61 | Assist::Resolved(label, action) => (label, action), | ||
62 | Assist::Unresolved(..) => unreachable!(), | ||
63 | }) | ||
64 | .collect() | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | mod add_derive; | ||
69 | mod add_impl; | ||
70 | mod flip_comma; | ||
71 | mod change_visibility; | ||
72 | mod fill_match_arms; | ||
73 | mod introduce_variable; | ||
74 | mod replace_if_let_with_match; | ||
75 | mod split_import; | ||
76 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | ||
77 | &[ | ||
78 | add_derive::add_derive, | ||
79 | add_impl::add_impl, | ||
80 | change_visibility::change_visibility, | ||
81 | fill_match_arms::fill_match_arms, | ||
82 | flip_comma::flip_comma, | ||
83 | introduce_variable::introduce_variable, | ||
84 | replace_if_let_with_match::replace_if_let_with_match, | ||
85 | split_import::split_import, | ||
86 | ] | ||
87 | } | ||
88 | |||
89 | fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> { | ||
90 | node.siblings(direction) | ||
91 | .skip(1) | ||
92 | .find(|node| !node.kind().is_trivia()) | ||
93 | } | ||
94 | |||
95 | #[cfg(test)] | ||
96 | mod helpers { | ||
97 | use hir::mock::MockDatabase; | ||
98 | use ra_syntax::TextRange; | ||
99 | use ra_db::FileRange; | ||
100 | use test_utils::{extract_offset, assert_eq_text, add_cursor, extract_range}; | ||
101 | |||
102 | use crate::{AssistCtx, Assist}; | ||
103 | |||
104 | pub(crate) fn check_assist( | ||
105 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
106 | before: &str, | ||
107 | after: &str, | ||
108 | ) { | ||
109 | let (before_cursor_pos, before) = extract_offset(before); | ||
110 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
111 | let frange = FileRange { | ||
112 | file_id, | ||
113 | range: TextRange::offset_len(before_cursor_pos, 0.into()), | ||
114 | }; | ||
115 | let assist = | ||
116 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | ||
117 | let action = match assist { | ||
118 | Assist::Unresolved(_) => unreachable!(), | ||
119 | Assist::Resolved(_, it) => it, | ||
120 | }; | ||
121 | |||
122 | let actual = action.edit.apply(&before); | ||
123 | let actual_cursor_pos = match action.cursor_position { | ||
124 | None => action | ||
125 | .edit | ||
126 | .apply_to_offset(before_cursor_pos) | ||
127 | .expect("cursor position is affected by the edit"), | ||
128 | Some(off) => off, | ||
129 | }; | ||
130 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
131 | assert_eq_text!(after, &actual); | ||
132 | } | ||
133 | |||
134 | pub(crate) fn check_assist_range( | ||
135 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
136 | before: &str, | ||
137 | after: &str, | ||
138 | ) { | ||
139 | let (range, before) = extract_range(before); | ||
140 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
141 | let frange = FileRange { file_id, range }; | ||
142 | let assist = | ||
143 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | ||
144 | let action = match assist { | ||
145 | Assist::Unresolved(_) => unreachable!(), | ||
146 | Assist::Resolved(_, it) => it, | ||
147 | }; | ||
148 | |||
149 | let mut actual = action.edit.apply(&before); | ||
150 | if let Some(pos) = action.cursor_position { | ||
151 | actual = add_cursor(&actual, pos); | ||
152 | } | ||
153 | assert_eq_text!(after, &actual); | ||
154 | } | ||
155 | |||
156 | pub(crate) fn check_assist_not_applicable( | ||
157 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
158 | before: &str, | ||
159 | ) { | ||
160 | let (before_cursor_pos, before) = extract_offset(before); | ||
161 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | ||
162 | let frange = FileRange { | ||
163 | file_id, | ||
164 | range: TextRange::offset_len(before_cursor_pos, 0.into()), | ||
165 | }; | ||
166 | let assist = AssistCtx::with_ctx(&db, frange, true, assist); | ||
167 | assert!(assist.is_none()); | ||
168 | } | ||
169 | |||
170 | } | ||