aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/completion/presentation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/completion/presentation.rs')
-rw-r--r--crates/ra_ide/src/completion/presentation.rs419
1 files changed, 359 insertions, 60 deletions
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 55f75b15a..6a6ddc7bd 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -11,7 +11,7 @@ use crate::{
11 CompletionKind, Completions, 11 CompletionKind, Completions,
12 }, 12 },
13 display::{const_label, macro_label, type_label, FunctionSignature}, 13 display::{const_label, macro_label, type_label, FunctionSignature},
14 RootDatabase, 14 CompletionScore, RootDatabase,
15}; 15};
16 16
17impl Completions { 17impl Completions {
@@ -22,16 +22,20 @@ impl Completions {
22 ty: &Type, 22 ty: &Type,
23 ) { 23 ) {
24 let is_deprecated = is_deprecated(field, ctx.db); 24 let is_deprecated = is_deprecated(field, ctx.db);
25 CompletionItem::new( 25 let ty = ty.display(ctx.db).to_string();
26 CompletionKind::Reference, 26 let name = field.name(ctx.db);
27 ctx.source_range(), 27 let mut completion_item =
28 field.name(ctx.db).to_string(), 28 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
29 ) 29 .kind(CompletionItemKind::Field)
30 .kind(CompletionItemKind::Field) 30 .detail(ty.clone())
31 .detail(ty.display(ctx.db).to_string()) 31 .set_documentation(field.docs(ctx.db))
32 .set_documentation(field.docs(ctx.db)) 32 .set_deprecated(is_deprecated);
33 .set_deprecated(is_deprecated) 33
34 .add_to(self); 34 if let Some(score) = compute_score(ctx, &ty, &name.to_string()) {
35 completion_item = completion_item.set_score(score);
36 }
37
38 completion_item.add_to(self);
35 } 39 }
36 40
37 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { 41 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
@@ -110,44 +114,23 @@ impl Completions {
110 114
111 // Add `<>` for generic types 115 // Add `<>` for generic types
112 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { 116 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis {
113 let has_non_default_type_params = match resolution { 117 if let Some(cap) = ctx.config.snippet_cap {
114 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), 118 let has_non_default_type_params = match resolution {
115 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), 119 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
116 _ => false, 120 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
117 }; 121 _ => false,
118 if has_non_default_type_params { 122 };
119 tested_by!(inserts_angle_brackets_for_generics); 123 if has_non_default_type_params {
120 completion_item = completion_item 124 tested_by!(inserts_angle_brackets_for_generics);
121 .lookup_by(local_name.clone()) 125 completion_item = completion_item
122 .label(format!("{}<…>", local_name)) 126 .lookup_by(local_name.clone())
123 .insert_snippet(format!("{}<$0>", local_name)); 127 .label(format!("{}<…>", local_name))
124 } 128 .insert_snippet(cap, format!("{}<$0>", local_name));
125 }
126
127 completion_item.kind(kind).set_documentation(docs).add_to(self)
128 }
129
130 fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
131 let mut votes = [0, 0, 0];
132 for (idx, s) in docs.match_indices(&macro_name) {
133 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
134 // Ensure to match the full word
135 if after.starts_with('!')
136 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
137 {
138 // It may have spaces before the braces like `foo! {}`
139 match after[1..].chars().find(|&c| !c.is_whitespace()) {
140 Some('{') => votes[0] += 1,
141 Some('[') => votes[1] += 1,
142 Some('(') => votes[2] += 1,
143 _ => {}
144 } 129 }
145 } 130 }
146 } 131 }
147 132
148 // Insert a space before `{}`. 133 completion_item.kind(kind).set_documentation(docs).add_to(self)
149 // We prefer the last one when some votes equal.
150 *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
151 } 134 }
152 135
153 pub(crate) fn add_macro( 136 pub(crate) fn add_macro(
@@ -156,6 +139,12 @@ impl Completions {
156 name: Option<String>, 139 name: Option<String>,
157 macro_: hir::MacroDef, 140 macro_: hir::MacroDef,
158 ) { 141 ) {
142 // FIXME: Currently proc-macro do not have ast-node,
143 // such that it does not have source
144 if macro_.is_proc_macro() {
145 return;
146 }
147
159 let name = match name { 148 let name = match name {
160 Some(it) => it, 149 Some(it) => it,
161 None => return, 150 None => return,
@@ -165,22 +154,31 @@ impl Completions {
165 let detail = macro_label(&ast_node); 154 let detail = macro_label(&ast_node);
166 155
167 let docs = macro_.docs(ctx.db); 156 let docs = macro_.docs(ctx.db);
168 let macro_declaration = format!("{}!", name);
169 157
170 let mut builder = 158 let mut builder = CompletionItem::new(
171 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), &macro_declaration) 159 CompletionKind::Reference,
172 .kind(CompletionItemKind::Macro) 160 ctx.source_range(),
173 .set_documentation(docs.clone()) 161 &format!("{}!", name),
174 .set_deprecated(is_deprecated(macro_, ctx.db)) 162 )
175 .detail(detail); 163 .kind(CompletionItemKind::Macro)
164 .set_documentation(docs.clone())
165 .set_deprecated(is_deprecated(macro_, ctx.db))
166 .detail(detail);
176 167
177 builder = if ctx.use_item_syntax.is_some() || ctx.is_macro_call { 168 let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call;
178 tested_by!(dont_insert_macro_call_parens_unncessary); 169 builder = match ctx.config.snippet_cap {
179 builder.insert_text(name) 170 Some(cap) if needs_bang => {
180 } else { 171 let docs = docs.as_ref().map_or("", |s| s.as_str());
181 let macro_braces_to_insert = 172 let (bra, ket) = guess_macro_braces(&name, docs);
182 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); 173 builder
183 builder.insert_snippet(macro_declaration + macro_braces_to_insert) 174 .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket))
175 .label(format!("{}!{}…{}", name, bra, ket))
176 }
177 None if needs_bang => builder.insert_text(format!("{}!", name)),
178 _ => {
179 tested_by!(dont_insert_macro_call_parens_unncessary);
180 builder.insert_text(name)
181 }
184 }; 182 };
185 183
186 self.add(builder); 184 self.add(builder);
@@ -294,6 +292,42 @@ impl Completions {
294 } 292 }
295} 293}
296 294
295pub(crate) fn compute_score(
296 ctx: &CompletionContext,
297 // FIXME: this definitely should be a `Type`
298 ty: &str,
299 name: &str,
300) -> Option<CompletionScore> {
301 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
302 tested_by!(test_struct_field_completion_in_record_lit);
303 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
304 (
305 struct_field.name(ctx.db).to_string(),
306 struct_field.signature_ty(ctx.db).display(ctx.db).to_string(),
307 )
308 } else if let Some(active_parameter) = &ctx.active_parameter {
309 tested_by!(test_struct_field_completion_in_func_call);
310 (active_parameter.name.clone(), active_parameter.ty.clone())
311 } else {
312 return None;
313 };
314
315 // Compute score
316 // For the same type
317 if &active_type != ty {
318 return None;
319 }
320
321 let mut res = CompletionScore::TypeMatch;
322
323 // If same type + same name then go top position
324 if active_name == name {
325 res = CompletionScore::TypeAndNameMatch
326 }
327
328 Some(res)
329}
330
297enum Params { 331enum Params {
298 Named(Vec<String>), 332 Named(Vec<String>),
299 Anonymous(usize), 333 Anonymous(usize),
@@ -320,6 +354,10 @@ impl Builder {
320 if ctx.use_item_syntax.is_some() || ctx.is_call { 354 if ctx.use_item_syntax.is_some() || ctx.is_call {
321 return self; 355 return self;
322 } 356 }
357 let cap = match ctx.config.snippet_cap {
358 Some(it) => it,
359 None => return self,
360 };
323 // If not an import, add parenthesis automatically. 361 // If not an import, add parenthesis automatically.
324 tested_by!(inserts_parens_for_function_calls); 362 tested_by!(inserts_parens_for_function_calls);
325 363
@@ -341,7 +379,7 @@ impl Builder {
341 379
342 (snippet, format!("{}(…)", name)) 380 (snippet, format!("{}(…)", name))
343 }; 381 };
344 self.lookup_by(name).label(label).insert_snippet(snippet) 382 self.lookup_by(name).label(label).insert_snippet(cap, snippet)
345 } 383 }
346} 384}
347 385
@@ -349,6 +387,34 @@ fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
349 node.attrs(db).by_key("deprecated").exists() 387 node.attrs(db).by_key("deprecated").exists()
350} 388}
351 389
390fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
391 let mut votes = [0, 0, 0];
392 for (idx, s) in docs.match_indices(&macro_name) {
393 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
394 // Ensure to match the full word
395 if after.starts_with('!')
396 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
397 {
398 // It may have spaces before the braces like `foo! {}`
399 match after[1..].chars().find(|&c| !c.is_whitespace()) {
400 Some('{') => votes[0] += 1,
401 Some('[') => votes[1] += 1,
402 Some('(') => votes[2] += 1,
403 _ => {}
404 }
405 }
406 }
407
408 // Insert a space before `{}`.
409 // We prefer the last one when some votes equal.
410 let (_vote, (bra, ket)) = votes
411 .iter()
412 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
413 .max_by_key(|&(&vote, _)| vote)
414 .unwrap();
415 (*bra, *ket)
416}
417
352#[cfg(test)] 418#[cfg(test)]
353mod tests { 419mod tests {
354 use insta::assert_debug_snapshot; 420 use insta::assert_debug_snapshot;
@@ -1025,4 +1091,237 @@ mod tests {
1025 "### 1091 "###
1026 ); 1092 );
1027 } 1093 }
1094
1095 #[test]
1096 fn test_struct_field_completion_in_func_call() {
1097 covers!(test_struct_field_completion_in_func_call);
1098 assert_debug_snapshot!(
1099 do_reference_completion(
1100 r"
1101 struct A { another_field: i64, the_field: u32, my_string: String }
1102 fn test(my_param: u32) -> u32 { my_param }
1103 fn foo(a: A) {
1104 test(a.<|>)
1105 }
1106 ",
1107 ),
1108 @r###"
1109 [
1110 CompletionItem {
1111 label: "another_field",
1112 source_range: [201; 201),
1113 delete: [201; 201),
1114 insert: "another_field",
1115 kind: Field,
1116 detail: "i64",
1117 },
1118 CompletionItem {
1119 label: "my_string",
1120 source_range: [201; 201),
1121 delete: [201; 201),
1122 insert: "my_string",
1123 kind: Field,
1124 detail: "{unknown}",
1125 },
1126 CompletionItem {
1127 label: "the_field",
1128 source_range: [201; 201),
1129 delete: [201; 201),
1130 insert: "the_field",
1131 kind: Field,
1132 detail: "u32",
1133 score: TypeMatch,
1134 },
1135 ]
1136 "###
1137 );
1138 }
1139
1140 #[test]
1141 fn test_struct_field_completion_in_func_call_with_type_and_name() {
1142 assert_debug_snapshot!(
1143 do_reference_completion(
1144 r"
1145 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1146 fn test(the_field: u32) -> u32 { the_field }
1147 fn foo(a: A) {
1148 test(a.<|>)
1149 }
1150 ",
1151 ),
1152 @r###"
1153 [
1154 CompletionItem {
1155 label: "another_field",
1156 source_range: [208; 208),
1157 delete: [208; 208),
1158 insert: "another_field",
1159 kind: Field,
1160 detail: "i64",
1161 },
1162 CompletionItem {
1163 label: "another_good_type",
1164 source_range: [208; 208),
1165 delete: [208; 208),
1166 insert: "another_good_type",
1167 kind: Field,
1168 detail: "u32",
1169 score: TypeMatch,
1170 },
1171 CompletionItem {
1172 label: "the_field",
1173 source_range: [208; 208),
1174 delete: [208; 208),
1175 insert: "the_field",
1176 kind: Field,
1177 detail: "u32",
1178 score: TypeAndNameMatch,
1179 },
1180 ]
1181 "###
1182 );
1183 }
1184
1185 #[test]
1186 fn test_struct_field_completion_in_record_lit() {
1187 covers!(test_struct_field_completion_in_func_call);
1188 assert_debug_snapshot!(
1189 do_reference_completion(
1190 r"
1191 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1192 struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
1193 fn foo(a: A) {
1194 let b = B {
1195 the_field: a.<|>
1196 };
1197 }
1198 ",
1199 ),
1200 @r###"
1201 [
1202 CompletionItem {
1203 label: "another_field",
1204 source_range: [270; 270),
1205 delete: [270; 270),
1206 insert: "another_field",
1207 kind: Field,
1208 detail: "i64",
1209 },
1210 CompletionItem {
1211 label: "another_good_type",
1212 source_range: [270; 270),
1213 delete: [270; 270),
1214 insert: "another_good_type",
1215 kind: Field,
1216 detail: "u32",
1217 score: TypeMatch,
1218 },
1219 CompletionItem {
1220 label: "the_field",
1221 source_range: [270; 270),
1222 delete: [270; 270),
1223 insert: "the_field",
1224 kind: Field,
1225 detail: "u32",
1226 score: TypeAndNameMatch,
1227 },
1228 ]
1229 "###
1230 );
1231 }
1232
1233 #[test]
1234 fn test_struct_field_completion_in_record_lit_and_fn_call() {
1235 assert_debug_snapshot!(
1236 do_reference_completion(
1237 r"
1238 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1239 struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
1240 fn test(the_field: i64) -> i64 { the_field }
1241 fn foo(a: A) {
1242 let b = B {
1243 the_field: test(a.<|>)
1244 };
1245 }
1246 ",
1247 ),
1248 @r###"
1249 [
1250 CompletionItem {
1251 label: "another_field",
1252 source_range: [336; 336),
1253 delete: [336; 336),
1254 insert: "another_field",
1255 kind: Field,
1256 detail: "i64",
1257 score: TypeMatch,
1258 },
1259 CompletionItem {
1260 label: "another_good_type",
1261 source_range: [336; 336),
1262 delete: [336; 336),
1263 insert: "another_good_type",
1264 kind: Field,
1265 detail: "u32",
1266 },
1267 CompletionItem {
1268 label: "the_field",
1269 source_range: [336; 336),
1270 delete: [336; 336),
1271 insert: "the_field",
1272 kind: Field,
1273 detail: "u32",
1274 },
1275 ]
1276 "###
1277 );
1278 }
1279
1280 #[test]
1281 fn test_struct_field_completion_in_fn_call_and_record_lit() {
1282 assert_debug_snapshot!(
1283 do_reference_completion(
1284 r"
1285 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1286 struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
1287 fn test(the_field: i64) -> i64 { the_field }
1288 fn foo(a: A) {
1289 test(B {
1290 the_field: a.<|>
1291 });
1292 }
1293 ",
1294 ),
1295 @r###"
1296 [
1297 CompletionItem {
1298 label: "another_field",
1299 source_range: [328; 328),
1300 delete: [328; 328),
1301 insert: "another_field",
1302 kind: Field,
1303 detail: "i64",
1304 },
1305 CompletionItem {
1306 label: "another_good_type",
1307 source_range: [328; 328),
1308 delete: [328; 328),
1309 insert: "another_good_type",
1310 kind: Field,
1311 detail: "u32",
1312 score: TypeMatch,
1313 },
1314 CompletionItem {
1315 label: "the_field",
1316 source_range: [328; 328),
1317 delete: [328; 328),
1318 insert: "the_field",
1319 kind: Field,
1320 detail: "u32",
1321 score: TypeAndNameMatch,
1322 },
1323 ]
1324 "###
1325 );
1326 }
1028} 1327}