diff options
author | Aleksey Kladov <[email protected]> | 2020-05-17 11:09:53 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-05-19 19:28:27 +0100 |
commit | c847c079fd66d7ed09c64ff398baf05317b16500 (patch) | |
tree | f7560983031a6f122156b3d4c3fccaf3f4c23d5c /crates/ra_assists | |
parent | 131849f2abd94dc8143f0c5d65e022136f29561a (diff) |
Add AssistConfig
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/src/assist_config.rs | 27 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_context.rs | 51 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 15 | ||||
-rw-r--r-- | crates/ra_assists/src/tests.rs | 38 |
4 files changed, 103 insertions, 28 deletions
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs new file mode 100644 index 000000000..c0a0226fb --- /dev/null +++ b/crates/ra_assists/src/assist_config.rs | |||
@@ -0,0 +1,27 @@ | |||
1 | //! Settings for tweaking assists. | ||
2 | //! | ||
3 | //! The fun thing here is `SnippetCap` -- this type can only be created in this | ||
4 | //! module, and we use to statically check that we only produce snippet | ||
5 | //! assists if we are allowed to. | ||
6 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
8 | pub struct AssistConfig { | ||
9 | pub snippet_cap: Option<SnippetCap>, | ||
10 | } | ||
11 | |||
12 | impl AssistConfig { | ||
13 | pub fn allow_snippets(&mut self, yes: bool) { | ||
14 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } | ||
15 | } | ||
16 | } | ||
17 | |||
18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
19 | pub struct SnippetCap { | ||
20 | _private: (), | ||
21 | } | ||
22 | |||
23 | impl Default for AssistConfig { | ||
24 | fn default() -> Self { | ||
25 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } | ||
26 | } | ||
27 | } | ||
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index a680f752b..b90bbf8b2 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -15,7 +15,10 @@ use ra_syntax::{ | |||
15 | }; | 15 | }; |
16 | use ra_text_edit::TextEditBuilder; | 16 | use ra_text_edit::TextEditBuilder; |
17 | 17 | ||
18 | use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; | 18 | use crate::{ |
19 | assist_config::{AssistConfig, SnippetCap}, | ||
20 | Assist, AssistId, GroupLabel, ResolvedAssist, | ||
21 | }; | ||
19 | 22 | ||
20 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 23 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
21 | /// | 24 | /// |
@@ -48,6 +51,7 @@ use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; | |||
48 | /// moment, because the LSP API is pretty awkward in this place, and it's much | 51 | /// moment, because the LSP API is pretty awkward in this place, and it's much |
49 | /// easier to just compute the edit eagerly :-) | 52 | /// easier to just compute the edit eagerly :-) |
50 | pub(crate) struct AssistContext<'a> { | 53 | pub(crate) struct AssistContext<'a> { |
54 | pub(crate) config: &'a AssistConfig, | ||
51 | pub(crate) sema: Semantics<'a, RootDatabase>, | 55 | pub(crate) sema: Semantics<'a, RootDatabase>, |
52 | pub(crate) db: &'a RootDatabase, | 56 | pub(crate) db: &'a RootDatabase, |
53 | pub(crate) frange: FileRange, | 57 | pub(crate) frange: FileRange, |
@@ -55,10 +59,14 @@ pub(crate) struct AssistContext<'a> { | |||
55 | } | 59 | } |
56 | 60 | ||
57 | impl<'a> AssistContext<'a> { | 61 | impl<'a> AssistContext<'a> { |
58 | pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> { | 62 | pub(crate) fn new( |
63 | sema: Semantics<'a, RootDatabase>, | ||
64 | config: &'a AssistConfig, | ||
65 | frange: FileRange, | ||
66 | ) -> AssistContext<'a> { | ||
59 | let source_file = sema.parse(frange.file_id); | 67 | let source_file = sema.parse(frange.file_id); |
60 | let db = sema.db; | 68 | let db = sema.db; |
61 | AssistContext { sema, db, frange, source_file } | 69 | AssistContext { config, sema, db, frange, source_file } |
62 | } | 70 | } |
63 | 71 | ||
64 | // NB, this ignores active selection. | 72 | // NB, this ignores active selection. |
@@ -165,11 +173,17 @@ pub(crate) struct AssistBuilder { | |||
165 | edit: TextEditBuilder, | 173 | edit: TextEditBuilder, |
166 | cursor_position: Option<TextSize>, | 174 | cursor_position: Option<TextSize>, |
167 | file: FileId, | 175 | file: FileId, |
176 | is_snippet: bool, | ||
168 | } | 177 | } |
169 | 178 | ||
170 | impl AssistBuilder { | 179 | impl AssistBuilder { |
171 | pub(crate) fn new(file: FileId) -> AssistBuilder { | 180 | pub(crate) fn new(file: FileId) -> AssistBuilder { |
172 | AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file } | 181 | AssistBuilder { |
182 | edit: TextEditBuilder::default(), | ||
183 | cursor_position: None, | ||
184 | file, | ||
185 | is_snippet: false, | ||
186 | } | ||
173 | } | 187 | } |
174 | 188 | ||
175 | /// Remove specified `range` of text. | 189 | /// Remove specified `range` of text. |
@@ -180,10 +194,30 @@ impl AssistBuilder { | |||
180 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { | 194 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { |
181 | self.edit.insert(offset, text.into()) | 195 | self.edit.insert(offset, text.into()) |
182 | } | 196 | } |
197 | /// Append specified `text` at the given `offset` | ||
198 | pub(crate) fn insert_snippet( | ||
199 | &mut self, | ||
200 | _cap: SnippetCap, | ||
201 | offset: TextSize, | ||
202 | text: impl Into<String>, | ||
203 | ) { | ||
204 | self.is_snippet = true; | ||
205 | self.edit.insert(offset, text.into()) | ||
206 | } | ||
183 | /// Replaces specified `range` of text with a given string. | 207 | /// Replaces specified `range` of text with a given string. |
184 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 208 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
185 | self.edit.replace(range, replace_with.into()) | 209 | self.edit.replace(range, replace_with.into()) |
186 | } | 210 | } |
211 | /// Append specified `text` at the given `offset` | ||
212 | pub(crate) fn replace_snippet( | ||
213 | &mut self, | ||
214 | _cap: SnippetCap, | ||
215 | range: TextRange, | ||
216 | replace_with: impl Into<String>, | ||
217 | ) { | ||
218 | self.is_snippet = true; | ||
219 | self.edit.replace(range, replace_with.into()) | ||
220 | } | ||
187 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | 221 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { |
188 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | 222 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) |
189 | } | 223 | } |
@@ -227,7 +261,12 @@ impl AssistBuilder { | |||
227 | if edit.is_empty() && self.cursor_position.is_none() { | 261 | if edit.is_empty() && self.cursor_position.is_none() { |
228 | panic!("Only call `add_assist` if the assist can be applied") | 262 | panic!("Only call `add_assist` if the assist can be applied") |
229 | } | 263 | } |
230 | SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } | 264 | let mut res = |
231 | .into_source_change(self.file) | 265 | SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } |
266 | .into_source_change(self.file); | ||
267 | if self.is_snippet { | ||
268 | res.is_snippet = true; | ||
269 | } | ||
270 | res | ||
232 | } | 271 | } |
233 | } | 272 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index b6dc7cb1b..7f0a723c9 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -10,6 +10,7 @@ macro_rules! eprintln { | |||
10 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; | 10 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; |
11 | } | 11 | } |
12 | 12 | ||
13 | mod assist_config; | ||
13 | mod assist_context; | 14 | mod assist_context; |
14 | mod marks; | 15 | mod marks; |
15 | #[cfg(test)] | 16 | #[cfg(test)] |
@@ -24,6 +25,8 @@ use ra_syntax::TextRange; | |||
24 | 25 | ||
25 | pub(crate) use crate::assist_context::{AssistContext, Assists}; | 26 | pub(crate) use crate::assist_context::{AssistContext, Assists}; |
26 | 27 | ||
28 | pub use assist_config::AssistConfig; | ||
29 | |||
27 | /// Unique identifier of the assist, should not be shown to the user | 30 | /// Unique identifier of the assist, should not be shown to the user |
28 | /// directly. | 31 | /// directly. |
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
@@ -54,9 +57,9 @@ impl Assist { | |||
54 | /// | 57 | /// |
55 | /// Assists are returned in the "unresolved" state, that is only labels are | 58 | /// Assists are returned in the "unresolved" state, that is only labels are |
56 | /// returned, without actual edits. | 59 | /// returned, without actual edits. |
57 | pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> { | 60 | pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> { |
58 | let sema = Semantics::new(db); | 61 | let sema = Semantics::new(db); |
59 | let ctx = AssistContext::new(sema, range); | 62 | let ctx = AssistContext::new(sema, config, range); |
60 | let mut acc = Assists::new_unresolved(&ctx); | 63 | let mut acc = Assists::new_unresolved(&ctx); |
61 | handlers::all().iter().for_each(|handler| { | 64 | handlers::all().iter().for_each(|handler| { |
62 | handler(&mut acc, &ctx); | 65 | handler(&mut acc, &ctx); |
@@ -68,9 +71,13 @@ impl Assist { | |||
68 | /// | 71 | /// |
69 | /// Assists are returned in the "resolved" state, that is with edit fully | 72 | /// Assists are returned in the "resolved" state, that is with edit fully |
70 | /// computed. | 73 | /// computed. |
71 | pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { | 74 | pub fn resolved( |
75 | db: &RootDatabase, | ||
76 | config: &AssistConfig, | ||
77 | range: FileRange, | ||
78 | ) -> Vec<ResolvedAssist> { | ||
72 | let sema = Semantics::new(db); | 79 | let sema = Semantics::new(db); |
73 | let ctx = AssistContext::new(sema, range); | 80 | let ctx = AssistContext::new(sema, config, range); |
74 | let mut acc = Assists::new_resolved(&ctx); | 81 | let mut acc = Assists::new_resolved(&ctx); |
75 | handlers::all().iter().for_each(|handler| { | 82 | handlers::all().iter().for_each(|handler| { |
76 | handler(&mut acc, &ctx); | 83 | handler(&mut acc, &ctx); |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index a3eacb8f1..9ba3da786 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -11,7 +11,7 @@ use test_utils::{ | |||
11 | RangeOrOffset, | 11 | RangeOrOffset, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | use crate::{handlers::Handler, Assist, AssistContext, Assists}; | 14 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; |
15 | 15 | ||
16 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 16 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
17 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 17 | let (mut db, file_id) = RootDatabase::with_single_file(text); |
@@ -41,14 +41,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
41 | let (db, file_id) = crate::tests::with_single_file(&before); | 41 | let (db, file_id) = crate::tests::with_single_file(&before); |
42 | let frange = FileRange { file_id, range: selection.into() }; | 42 | let frange = FileRange { file_id, range: selection.into() }; |
43 | 43 | ||
44 | let mut assist = Assist::resolved(&db, frange) | 44 | let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) |
45 | .into_iter() | 45 | .into_iter() |
46 | .find(|assist| assist.assist.id.0 == assist_id) | 46 | .find(|assist| assist.assist.id.0 == assist_id) |
47 | .unwrap_or_else(|| { | 47 | .unwrap_or_else(|| { |
48 | panic!( | 48 | panic!( |
49 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 49 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
50 | assist_id, | 50 | assist_id, |
51 | Assist::resolved(&db, frange) | 51 | Assist::resolved(&db, &AssistConfig::default(), frange) |
52 | .into_iter() | 52 | .into_iter() |
53 | .map(|assist| assist.assist.id.0) | 53 | .map(|assist| assist.assist.id.0) |
54 | .collect::<Vec<_>>() | 54 | .collect::<Vec<_>>() |
@@ -90,7 +90,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { | |||
90 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | 90 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; |
91 | 91 | ||
92 | let sema = Semantics::new(&db); | 92 | let sema = Semantics::new(&db); |
93 | let ctx = AssistContext::new(sema, frange); | 93 | let config = AssistConfig::default(); |
94 | let ctx = AssistContext::new(sema, &config, frange); | ||
94 | let mut acc = Assists::new_resolved(&ctx); | 95 | let mut acc = Assists::new_resolved(&ctx); |
95 | handler(&mut acc, &ctx); | 96 | handler(&mut acc, &ctx); |
96 | let mut res = acc.finish_resolved(); | 97 | let mut res = acc.finish_resolved(); |
@@ -103,19 +104,20 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { | |||
103 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); | 104 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); |
104 | change.edit.apply(&mut actual); | 105 | change.edit.apply(&mut actual); |
105 | 106 | ||
106 | match source_change.cursor_position { | 107 | if !source_change.is_snippet { |
107 | None => { | 108 | match source_change.cursor_position { |
108 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { | 109 | None => { |
109 | let off = change | 110 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { |
110 | .edit | 111 | let off = change |
111 | .apply_to_offset(before_cursor_pos) | 112 | .edit |
112 | .expect("cursor position is affected by the edit"); | 113 | .apply_to_offset(before_cursor_pos) |
113 | actual = add_cursor(&actual, off) | 114 | .expect("cursor position is affected by the edit"); |
115 | actual = add_cursor(&actual, off) | ||
116 | } | ||
114 | } | 117 | } |
115 | } | 118 | Some(off) => actual = add_cursor(&actual, off.offset), |
116 | Some(off) => actual = add_cursor(&actual, off.offset), | 119 | }; |
117 | }; | 120 | } |
118 | |||
119 | assert_eq_text!(after, &actual); | 121 | assert_eq_text!(after, &actual); |
120 | } | 122 | } |
121 | (Some(assist), ExpectedResult::Target(target)) => { | 123 | (Some(assist), ExpectedResult::Target(target)) => { |
@@ -136,7 +138,7 @@ fn assist_order_field_struct() { | |||
136 | let (before_cursor_pos, before) = extract_offset(before); | 138 | let (before_cursor_pos, before) = extract_offset(before); |
137 | let (db, file_id) = with_single_file(&before); | 139 | let (db, file_id) = with_single_file(&before); |
138 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 140 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
139 | let assists = Assist::resolved(&db, frange); | 141 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); |
140 | let mut assists = assists.iter(); | 142 | let mut assists = assists.iter(); |
141 | 143 | ||
142 | assert_eq!( | 144 | assert_eq!( |
@@ -159,7 +161,7 @@ fn assist_order_if_expr() { | |||
159 | let (range, before) = extract_range(before); | 161 | let (range, before) = extract_range(before); |
160 | let (db, file_id) = with_single_file(&before); | 162 | let (db, file_id) = with_single_file(&before); |
161 | let frange = FileRange { file_id, range }; | 163 | let frange = FileRange { file_id, range }; |
162 | let assists = Assist::resolved(&db, frange); | 164 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); |
163 | let mut assists = assists.iter(); | 165 | let mut assists = assists.iter(); |
164 | 166 | ||
165 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | 167 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); |