aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/lib.rs
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-02-06 14:01:07 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-02-06 14:01:07 +0000
commitf6cf9393a8327b4c0385bccbc5be84a79bd50057 (patch)
tree4af15c8906b85de01a15c717bc1fac388952cd3d /crates/ra_assists/src/lib.rs
parent736a55c97e69f95e6ff4a0c3dafb2018e8ea05f9 (diff)
parent0c5fd8f7cbf04eda763e55bc9a38dad5f7ec917d (diff)
Merge #750
750: Move assists to a separate crate r=matklad a=matklad I am slowly coming to conclusion that ide_api_light does not make a lot of sense after all :D This PR moves assists to a separate crate, so that assists can use database (so, inspect types, do name-resolution, etc) Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/lib.rs')
-rw-r--r--crates/ra_assists/src/lib.rs170
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
8mod assist_ctx;
9
10use ra_text_edit::TextEdit;
11use ra_syntax::{TextUnit, SyntaxNode, Direction};
12use ra_db::FileRange;
13use hir::db::HirDatabase;
14
15pub(crate) use crate::assist_ctx::{AssistCtx, Assist};
16
17#[derive(Debug)]
18pub struct AssistLabel {
19 /// Short description of the assist, as shown in the UI.
20 pub label: String,
21}
22
23pub 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.
32pub fn applicable_assists<H>(db: &H, range: FileRange) -> Vec<AssistLabel>
33where
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.
52pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
53where
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
68mod add_derive;
69mod add_impl;
70mod flip_comma;
71mod change_visibility;
72mod fill_match_arms;
73mod introduce_variable;
74mod replace_if_let_with_match;
75mod split_import;
76fn 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
89fn 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)]
96mod 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}