diff options
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/assist_context.rs | 51 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 41 | ||||
-rw-r--r-- | crates/assists/src/tests.rs | 48 |
3 files changed, 49 insertions, 91 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs index 80cf9aba1..4f59d39a9 100644 --- a/crates/assists/src/assist_context.rs +++ b/crates/assists/src/assist_context.rs | |||
@@ -19,7 +19,7 @@ use text_edit::{TextEdit, TextEditBuilder}; | |||
19 | 19 | ||
20 | use crate::{ | 20 | use crate::{ |
21 | assist_config::{AssistConfig, SnippetCap}, | 21 | assist_config::{AssistConfig, SnippetCap}, |
22 | Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist, | 22 | Assist, AssistId, AssistKind, GroupLabel, |
23 | }; | 23 | }; |
24 | 24 | ||
25 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 25 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
@@ -105,46 +105,23 @@ impl<'a> AssistContext<'a> { | |||
105 | pub(crate) struct Assists { | 105 | pub(crate) struct Assists { |
106 | resolve: bool, | 106 | resolve: bool, |
107 | file: FileId, | 107 | file: FileId, |
108 | buf: Vec<(Assist, Option<SourceChange>)>, | 108 | buf: Vec<Assist>, |
109 | allowed: Option<Vec<AssistKind>>, | 109 | allowed: Option<Vec<AssistKind>>, |
110 | } | 110 | } |
111 | 111 | ||
112 | impl Assists { | 112 | impl Assists { |
113 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { | 113 | pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists { |
114 | Assists { | 114 | Assists { |
115 | resolve: true, | 115 | resolve, |
116 | file: ctx.frange.file_id, | 116 | file: ctx.frange.file_id, |
117 | buf: Vec::new(), | 117 | buf: Vec::new(), |
118 | allowed: ctx.config.allowed.clone(), | 118 | allowed: ctx.config.allowed.clone(), |
119 | } | 119 | } |
120 | } | 120 | } |
121 | 121 | ||
122 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { | 122 | pub(crate) fn finish(mut self) -> Vec<Assist> { |
123 | Assists { | 123 | self.buf.sort_by_key(|assist| assist.target.len()); |
124 | resolve: false, | 124 | self.buf |
125 | file: ctx.frange.file_id, | ||
126 | buf: Vec::new(), | ||
127 | allowed: ctx.config.allowed.clone(), | ||
128 | } | ||
129 | } | ||
130 | |||
131 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { | ||
132 | assert!(!self.resolve); | ||
133 | self.finish() | ||
134 | .into_iter() | ||
135 | .map(|(label, edit)| { | ||
136 | assert!(edit.is_none()); | ||
137 | label | ||
138 | }) | ||
139 | .collect() | ||
140 | } | ||
141 | |||
142 | pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> { | ||
143 | assert!(self.resolve); | ||
144 | self.finish() | ||
145 | .into_iter() | ||
146 | .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() }) | ||
147 | .collect() | ||
148 | } | 125 | } |
149 | 126 | ||
150 | pub(crate) fn add( | 127 | pub(crate) fn add( |
@@ -158,7 +135,7 @@ impl Assists { | |||
158 | return None; | 135 | return None; |
159 | } | 136 | } |
160 | let label = Label::new(label.into()); | 137 | let label = Label::new(label.into()); |
161 | let assist = Assist { id, label, group: None, target }; | 138 | let assist = Assist { id, label, group: None, target, source_change: None }; |
162 | self.add_impl(assist, f) | 139 | self.add_impl(assist, f) |
163 | } | 140 | } |
164 | 141 | ||
@@ -174,11 +151,11 @@ impl Assists { | |||
174 | return None; | 151 | return None; |
175 | } | 152 | } |
176 | let label = Label::new(label.into()); | 153 | let label = Label::new(label.into()); |
177 | let assist = Assist { id, label, group: Some(group.clone()), target }; | 154 | let assist = Assist { id, label, group: Some(group.clone()), target, source_change: None }; |
178 | self.add_impl(assist, f) | 155 | self.add_impl(assist, f) |
179 | } | 156 | } |
180 | 157 | ||
181 | fn add_impl(&mut self, assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | 158 | fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { |
182 | let source_change = if self.resolve { | 159 | let source_change = if self.resolve { |
183 | let mut builder = AssistBuilder::new(self.file); | 160 | let mut builder = AssistBuilder::new(self.file); |
184 | f(&mut builder); | 161 | f(&mut builder); |
@@ -186,16 +163,12 @@ impl Assists { | |||
186 | } else { | 163 | } else { |
187 | None | 164 | None |
188 | }; | 165 | }; |
166 | assist.source_change = source_change.clone(); | ||
189 | 167 | ||
190 | self.buf.push((assist, source_change)); | 168 | self.buf.push(assist); |
191 | Some(()) | 169 | Some(()) |
192 | } | 170 | } |
193 | 171 | ||
194 | fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> { | ||
195 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); | ||
196 | self.buf | ||
197 | } | ||
198 | |||
199 | fn is_allowed(&self, id: &AssistId) -> bool { | 172 | fn is_allowed(&self, id: &AssistId) -> bool { |
200 | match &self.allowed { | 173 | match &self.allowed { |
201 | Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)), | 174 | Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)), |
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 6b89b2d04..fdec886e9 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -73,45 +73,32 @@ pub struct Assist { | |||
73 | /// Target ranges are used to sort assists: the smaller the target range, | 73 | /// Target ranges are used to sort assists: the smaller the target range, |
74 | /// the more specific assist is, and so it should be sorted first. | 74 | /// the more specific assist is, and so it should be sorted first. |
75 | pub target: TextRange, | 75 | pub target: TextRange, |
76 | } | 76 | /// Computing source change sometimes is much more costly then computing the |
77 | 77 | /// other fields. Additionally, the actual change is not required to show | |
78 | #[derive(Debug, Clone)] | 78 | /// the lightbulb UI, it only is needed when the user tries to apply an |
79 | pub struct ResolvedAssist { | 79 | /// assist. So, we compute it lazily: the API allow requesting assists with |
80 | pub assist: Assist, | 80 | /// or without source change. We could (and in fact, used to) distinguish |
81 | pub source_change: SourceChange, | 81 | /// between resolved and unresolved assists at the type level, but this is |
82 | /// cumbersome, especially if you want to embed an assist into another data | ||
83 | /// structure, such as a diagnostic. | ||
84 | pub source_change: Option<SourceChange>, | ||
82 | } | 85 | } |
83 | 86 | ||
84 | impl Assist { | 87 | impl Assist { |
85 | /// Return all the assists applicable at the given position. | 88 | /// Return all the assists applicable at the given position. |
86 | /// | 89 | pub fn get( |
87 | /// Assists are returned in the "unresolved" state, that is only labels are | ||
88 | /// returned, without actual edits. | ||
89 | pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> { | ||
90 | let sema = Semantics::new(db); | ||
91 | let ctx = AssistContext::new(sema, config, range); | ||
92 | let mut acc = Assists::new_unresolved(&ctx); | ||
93 | handlers::all().iter().for_each(|handler| { | ||
94 | handler(&mut acc, &ctx); | ||
95 | }); | ||
96 | acc.finish_unresolved() | ||
97 | } | ||
98 | |||
99 | /// Return all the assists applicable at the given position. | ||
100 | /// | ||
101 | /// Assists are returned in the "resolved" state, that is with edit fully | ||
102 | /// computed. | ||
103 | pub fn resolved( | ||
104 | db: &RootDatabase, | 90 | db: &RootDatabase, |
105 | config: &AssistConfig, | 91 | config: &AssistConfig, |
92 | resolve: bool, | ||
106 | range: FileRange, | 93 | range: FileRange, |
107 | ) -> Vec<ResolvedAssist> { | 94 | ) -> Vec<Assist> { |
108 | let sema = Semantics::new(db); | 95 | let sema = Semantics::new(db); |
109 | let ctx = AssistContext::new(sema, config, range); | 96 | let ctx = AssistContext::new(sema, config, range); |
110 | let mut acc = Assists::new_resolved(&ctx); | 97 | let mut acc = Assists::new(&ctx, resolve); |
111 | handlers::all().iter().for_each(|handler| { | 98 | handlers::all().iter().for_each(|handler| { |
112 | handler(&mut acc, &ctx); | 99 | handler(&mut acc, &ctx); |
113 | }); | 100 | }); |
114 | acc.finish_resolved() | 101 | acc.finish() |
115 | } | 102 | } |
116 | } | 103 | } |
117 | 104 | ||
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs index b41f4874a..21e448fb8 100644 --- a/crates/assists/src/tests.rs +++ b/crates/assists/src/tests.rs | |||
@@ -48,24 +48,25 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
48 | let before = db.file_text(file_id).to_string(); | 48 | let before = db.file_text(file_id).to_string(); |
49 | let frange = FileRange { file_id, range: selection.into() }; | 49 | let frange = FileRange { file_id, range: selection.into() }; |
50 | 50 | ||
51 | let assist = Assist::resolved(&db, &AssistConfig::default(), frange) | 51 | let assist = Assist::get(&db, &AssistConfig::default(), true, frange) |
52 | .into_iter() | 52 | .into_iter() |
53 | .find(|assist| assist.assist.id.0 == assist_id) | 53 | .find(|assist| assist.id.0 == assist_id) |
54 | .unwrap_or_else(|| { | 54 | .unwrap_or_else(|| { |
55 | panic!( | 55 | panic!( |
56 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 56 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
57 | assist_id, | 57 | assist_id, |
58 | Assist::resolved(&db, &AssistConfig::default(), frange) | 58 | Assist::get(&db, &AssistConfig::default(), false, frange) |
59 | .into_iter() | 59 | .into_iter() |
60 | .map(|assist| assist.assist.id.0) | 60 | .map(|assist| assist.id.0) |
61 | .collect::<Vec<_>>() | 61 | .collect::<Vec<_>>() |
62 | .join(", ") | 62 | .join(", ") |
63 | ) | 63 | ) |
64 | }); | 64 | }); |
65 | 65 | ||
66 | let actual = { | 66 | let actual = { |
67 | let source_change = assist.source_change.unwrap(); | ||
67 | let mut actual = before; | 68 | let mut actual = before; |
68 | for source_file_edit in assist.source_change.source_file_edits { | 69 | for source_file_edit in source_change.source_file_edits { |
69 | if source_file_edit.file_id == file_id { | 70 | if source_file_edit.file_id == file_id { |
70 | source_file_edit.edit.apply(&mut actual) | 71 | source_file_edit.edit.apply(&mut actual) |
71 | } | 72 | } |
@@ -90,18 +91,18 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: | |||
90 | let sema = Semantics::new(&db); | 91 | let sema = Semantics::new(&db); |
91 | let config = AssistConfig::default(); | 92 | let config = AssistConfig::default(); |
92 | let ctx = AssistContext::new(sema, &config, frange); | 93 | let ctx = AssistContext::new(sema, &config, frange); |
93 | let mut acc = Assists::new_resolved(&ctx); | 94 | let mut acc = Assists::new(&ctx, true); |
94 | handler(&mut acc, &ctx); | 95 | handler(&mut acc, &ctx); |
95 | let mut res = acc.finish_resolved(); | 96 | let mut res = acc.finish(); |
96 | 97 | ||
97 | let assist = match assist_label { | 98 | let assist = match assist_label { |
98 | Some(label) => res.into_iter().find(|resolved| resolved.assist.label == label), | 99 | Some(label) => res.into_iter().find(|resolved| resolved.label == label), |
99 | None => res.pop(), | 100 | None => res.pop(), |
100 | }; | 101 | }; |
101 | 102 | ||
102 | match (assist, expected) { | 103 | match (assist, expected) { |
103 | (Some(assist), ExpectedResult::After(after)) => { | 104 | (Some(assist), ExpectedResult::After(after)) => { |
104 | let mut source_change = assist.source_change; | 105 | let mut source_change = assist.source_change.unwrap(); |
105 | assert!(!source_change.source_file_edits.is_empty()); | 106 | assert!(!source_change.source_file_edits.is_empty()); |
106 | let skip_header = source_change.source_file_edits.len() == 1 | 107 | let skip_header = source_change.source_file_edits.len() == 1 |
107 | && source_change.file_system_edits.len() == 0; | 108 | && source_change.file_system_edits.len() == 0; |
@@ -138,7 +139,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: | |||
138 | assert_eq_text!(after, &buf); | 139 | assert_eq_text!(after, &buf); |
139 | } | 140 | } |
140 | (Some(assist), ExpectedResult::Target(target)) => { | 141 | (Some(assist), ExpectedResult::Target(target)) => { |
141 | let range = assist.assist.target; | 142 | let range = assist.target; |
142 | assert_eq_text!(&text_without_caret[range], target); | 143 | assert_eq_text!(&text_without_caret[range], target); |
143 | } | 144 | } |
144 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), | 145 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), |
@@ -155,14 +156,11 @@ fn assist_order_field_struct() { | |||
155 | let (before_cursor_pos, before) = extract_offset(before); | 156 | let (before_cursor_pos, before) = extract_offset(before); |
156 | let (db, file_id) = with_single_file(&before); | 157 | let (db, file_id) = with_single_file(&before); |
157 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 158 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
158 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); | 159 | let assists = Assist::get(&db, &AssistConfig::default(), false, frange); |
159 | let mut assists = assists.iter(); | 160 | let mut assists = assists.iter(); |
160 | 161 | ||
161 | assert_eq!( | 162 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
162 | assists.next().expect("expected assist").assist.label, | 163 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); |
163 | "Change visibility to pub(crate)" | ||
164 | ); | ||
165 | assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`"); | ||
166 | } | 164 | } |
167 | 165 | ||
168 | #[test] | 166 | #[test] |
@@ -178,11 +176,11 @@ fn assist_order_if_expr() { | |||
178 | let (range, before) = extract_range(before); | 176 | let (range, before) = extract_range(before); |
179 | let (db, file_id) = with_single_file(&before); | 177 | let (db, file_id) = with_single_file(&before); |
180 | let frange = FileRange { file_id, range }; | 178 | let frange = FileRange { file_id, range }; |
181 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); | 179 | let assists = Assist::get(&db, &AssistConfig::default(), false, frange); |
182 | let mut assists = assists.iter(); | 180 | let mut assists = assists.iter(); |
183 | 181 | ||
184 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | 182 | assert_eq!(assists.next().expect("expected assist").label, "Extract into variable"); |
185 | assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); | 183 | assert_eq!(assists.next().expect("expected assist").label, "Replace with match"); |
186 | } | 184 | } |
187 | 185 | ||
188 | #[test] | 186 | #[test] |
@@ -203,27 +201,27 @@ fn assist_filter_works() { | |||
203 | let mut cfg = AssistConfig::default(); | 201 | let mut cfg = AssistConfig::default(); |
204 | cfg.allowed = Some(vec![AssistKind::Refactor]); | 202 | cfg.allowed = Some(vec![AssistKind::Refactor]); |
205 | 203 | ||
206 | let assists = Assist::resolved(&db, &cfg, frange); | 204 | let assists = Assist::get(&db, &cfg, false, frange); |
207 | let mut assists = assists.iter(); | 205 | let mut assists = assists.iter(); |
208 | 206 | ||
209 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | 207 | assert_eq!(assists.next().expect("expected assist").label, "Extract into variable"); |
210 | assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); | 208 | assert_eq!(assists.next().expect("expected assist").label, "Replace with match"); |
211 | } | 209 | } |
212 | 210 | ||
213 | { | 211 | { |
214 | let mut cfg = AssistConfig::default(); | 212 | let mut cfg = AssistConfig::default(); |
215 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | 213 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); |
216 | let assists = Assist::resolved(&db, &cfg, frange); | 214 | let assists = Assist::get(&db, &cfg, false, frange); |
217 | assert_eq!(assists.len(), 1); | 215 | assert_eq!(assists.len(), 1); |
218 | 216 | ||
219 | let mut assists = assists.iter(); | 217 | let mut assists = assists.iter(); |
220 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | 218 | assert_eq!(assists.next().expect("expected assist").label, "Extract into variable"); |
221 | } | 219 | } |
222 | 220 | ||
223 | { | 221 | { |
224 | let mut cfg = AssistConfig::default(); | 222 | let mut cfg = AssistConfig::default(); |
225 | cfg.allowed = Some(vec![AssistKind::QuickFix]); | 223 | cfg.allowed = Some(vec![AssistKind::QuickFix]); |
226 | let assists = Assist::resolved(&db, &cfg, frange); | 224 | let assists = Assist::get(&db, &cfg, false, frange); |
227 | assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out"); | 225 | assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out"); |
228 | } | 226 | } |
229 | } | 227 | } |