aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-12-26 11:14:19 +0000
committerGitHub <[email protected]>2020-12-26 11:14:19 +0000
commit1d874a2a65278548a781c6cc518ae7dd7c741229 (patch)
tree107c3baf8ac0d8b386f7b1441544011e23caf70b /crates/assists
parent44893bbcc58abc630e5263d3261236ca1cc21041 (diff)
parent2f2267553769bd7c63ab49c85e4c67a7339355c3 (diff)
Merge #7043
7043: Simplify assists resolution API r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/assist_context.rs51
-rw-r--r--crates/assists/src/lib.rs41
-rw-r--r--crates/assists/src/tests.rs48
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
20use crate::{ 20use 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> {
105pub(crate) struct Assists { 105pub(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
112impl Assists { 112impl 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
79pub 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
84impl Assist { 87impl 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}