diff options
Diffstat (limited to 'crates/completion/src/completions.rs')
-rw-r--r-- | crates/completion/src/completions.rs | 260 |
1 files changed, 14 insertions, 246 deletions
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index d5fb85b79..1ca5cf33d 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs | |||
@@ -14,14 +14,15 @@ pub(crate) mod macro_in_item_position; | |||
14 | pub(crate) mod trait_impl; | 14 | pub(crate) mod trait_impl; |
15 | pub(crate) mod mod_; | 15 | pub(crate) mod mod_; |
16 | 16 | ||
17 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; | 17 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type}; |
18 | use itertools::Itertools; | ||
19 | use syntax::{ast::NameOwner, display::*}; | 18 | use syntax::{ast::NameOwner, display::*}; |
20 | use test_utils::mark; | 19 | use test_utils::mark; |
21 | 20 | ||
22 | use crate::{ | 21 | use crate::{ |
23 | item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | 22 | item::Builder, |
24 | CompletionScore, RootDatabase, | 23 | render::{EnumVariantRender, FunctionRender, MacroRender}, |
24 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore, | ||
25 | RootDatabase, | ||
25 | }; | 26 | }; |
26 | 27 | ||
27 | /// Represents an in-progress set of completions being built. | 28 | /// Represents an in-progress set of completions being built. |
@@ -189,50 +190,14 @@ impl Completions { | |||
189 | name: Option<String>, | 190 | name: Option<String>, |
190 | macro_: hir::MacroDef, | 191 | macro_: hir::MacroDef, |
191 | ) { | 192 | ) { |
192 | // FIXME: Currently proc-macro do not have ast-node, | ||
193 | // such that it does not have source | ||
194 | if macro_.is_proc_macro() { | ||
195 | return; | ||
196 | } | ||
197 | |||
198 | let name = match name { | 193 | let name = match name { |
199 | Some(it) => it, | 194 | Some(it) => it, |
200 | None => return, | 195 | None => return, |
201 | }; | 196 | }; |
202 | 197 | ||
203 | let ast_node = macro_.source(ctx.db).value; | 198 | if let Some(item) = MacroRender::new(ctx.into(), name, macro_).render() { |
204 | let detail = macro_label(&ast_node); | 199 | self.add(item); |
205 | 200 | } | |
206 | let docs = macro_.docs(ctx.db); | ||
207 | |||
208 | let mut builder = CompletionItem::new( | ||
209 | CompletionKind::Reference, | ||
210 | ctx.source_range(), | ||
211 | &format!("{}!", name), | ||
212 | ) | ||
213 | .kind(CompletionItemKind::Macro) | ||
214 | .set_documentation(docs.clone()) | ||
215 | .set_deprecated(is_deprecated(macro_, ctx.db)) | ||
216 | .detail(detail); | ||
217 | |||
218 | let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call; | ||
219 | builder = match ctx.config.snippet_cap { | ||
220 | Some(cap) if needs_bang => { | ||
221 | let docs = docs.as_ref().map_or("", |s| s.as_str()); | ||
222 | let (bra, ket) = guess_macro_braces(&name, docs); | ||
223 | builder | ||
224 | .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket)) | ||
225 | .label(format!("{}!{}…{}", name, bra, ket)) | ||
226 | .lookup_by(format!("{}!", name)) | ||
227 | } | ||
228 | None if needs_bang => builder.insert_text(format!("{}!", name)), | ||
229 | _ => { | ||
230 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
231 | builder.insert_text(name) | ||
232 | } | ||
233 | }; | ||
234 | |||
235 | self.add(builder.build()); | ||
236 | } | 201 | } |
237 | 202 | ||
238 | pub(crate) fn add_function( | 203 | pub(crate) fn add_function( |
@@ -241,50 +206,9 @@ impl Completions { | |||
241 | func: hir::Function, | 206 | func: hir::Function, |
242 | local_name: Option<String>, | 207 | local_name: Option<String>, |
243 | ) { | 208 | ) { |
244 | fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String { | 209 | let item = FunctionRender::new(ctx.into(), local_name, func).render(); |
245 | if let Some(derefed_ty) = ty.remove_ref() { | ||
246 | for (name, local) in ctx.locals.iter() { | ||
247 | if name == arg && local.ty(ctx.db) == derefed_ty { | ||
248 | return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string() | ||
249 | + &arg.to_string(); | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | arg.to_string() | ||
254 | }; | ||
255 | let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); | ||
256 | let ast_node = func.source(ctx.db).value; | ||
257 | |||
258 | let mut builder = | ||
259 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) | ||
260 | .kind(if func.self_param(ctx.db).is_some() { | ||
261 | CompletionItemKind::Method | ||
262 | } else { | ||
263 | CompletionItemKind::Function | ||
264 | }) | ||
265 | .set_documentation(func.docs(ctx.db)) | ||
266 | .set_deprecated(is_deprecated(func, ctx.db)) | ||
267 | .detail(function_declaration(&ast_node)); | ||
268 | |||
269 | let params_ty = func.params(ctx.db); | ||
270 | let params = ast_node | ||
271 | .param_list() | ||
272 | .into_iter() | ||
273 | .flat_map(|it| it.params()) | ||
274 | .zip(params_ty) | ||
275 | .flat_map(|(it, param_ty)| { | ||
276 | if let Some(pat) = it.pat() { | ||
277 | let name = pat.to_string(); | ||
278 | let arg = name.trim_start_matches("mut ").trim_start_matches('_'); | ||
279 | return Some(add_arg(arg, param_ty.ty(), ctx)); | ||
280 | } | ||
281 | None | ||
282 | }) | ||
283 | .collect(); | ||
284 | 210 | ||
285 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | 211 | self.add(item) |
286 | |||
287 | self.add(builder.build()) | ||
288 | } | 212 | } |
289 | 213 | ||
290 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { | 214 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { |
@@ -325,7 +249,8 @@ impl Completions { | |||
325 | variant: hir::EnumVariant, | 249 | variant: hir::EnumVariant, |
326 | path: ModPath, | 250 | path: ModPath, |
327 | ) { | 251 | ) { |
328 | self.add_enum_variant_impl(ctx, variant, None, Some(path)) | 252 | let item = EnumVariantRender::new(ctx.into(), None, variant, Some(path)).render(); |
253 | self.add(item); | ||
329 | } | 254 | } |
330 | 255 | ||
331 | pub(crate) fn add_enum_variant( | 256 | pub(crate) fn add_enum_variant( |
@@ -334,63 +259,8 @@ impl Completions { | |||
334 | variant: hir::EnumVariant, | 259 | variant: hir::EnumVariant, |
335 | local_name: Option<String>, | 260 | local_name: Option<String>, |
336 | ) { | 261 | ) { |
337 | self.add_enum_variant_impl(ctx, variant, local_name, None) | 262 | let item = EnumVariantRender::new(ctx.into(), local_name, variant, None).render(); |
338 | } | 263 | self.add(item); |
339 | |||
340 | fn add_enum_variant_impl( | ||
341 | &mut self, | ||
342 | ctx: &CompletionContext, | ||
343 | variant: hir::EnumVariant, | ||
344 | local_name: Option<String>, | ||
345 | path: Option<ModPath>, | ||
346 | ) { | ||
347 | let is_deprecated = is_deprecated(variant, ctx.db); | ||
348 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); | ||
349 | let (qualified_name, short_qualified_name) = match &path { | ||
350 | Some(path) => { | ||
351 | let full = path.to_string(); | ||
352 | let short = | ||
353 | path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); | ||
354 | (full, short) | ||
355 | } | ||
356 | None => (name.to_string(), name.to_string()), | ||
357 | }; | ||
358 | let detail_types = variant | ||
359 | .fields(ctx.db) | ||
360 | .into_iter() | ||
361 | .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); | ||
362 | let variant_kind = variant.kind(ctx.db); | ||
363 | let detail = match variant_kind { | ||
364 | StructKind::Tuple | StructKind::Unit => format!( | ||
365 | "({})", | ||
366 | detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ") | ||
367 | ), | ||
368 | StructKind::Record => format!( | ||
369 | "{{ {} }}", | ||
370 | detail_types | ||
371 | .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) | ||
372 | .format(", ") | ||
373 | ), | ||
374 | }; | ||
375 | let mut res = CompletionItem::new( | ||
376 | CompletionKind::Reference, | ||
377 | ctx.source_range(), | ||
378 | qualified_name.clone(), | ||
379 | ) | ||
380 | .kind(CompletionItemKind::EnumVariant) | ||
381 | .set_documentation(variant.docs(ctx.db)) | ||
382 | .set_deprecated(is_deprecated) | ||
383 | .detail(detail); | ||
384 | |||
385 | if variant_kind == StructKind::Tuple { | ||
386 | mark::hit!(inserts_parens_for_tuple_enums); | ||
387 | let params = Params::Anonymous(variant.fields(ctx.db).len()); | ||
388 | res = res.add_call_parens(ctx, short_qualified_name, params) | ||
389 | } else if path.is_some() { | ||
390 | res = res.lookup_by(short_qualified_name); | ||
391 | } | ||
392 | |||
393 | res.add_to(self); | ||
394 | } | 264 | } |
395 | } | 265 | } |
396 | 266 | ||
@@ -434,112 +304,10 @@ fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<Compl | |||
434 | compute_score_from_active(&active_type, &active_name, ty, name) | 304 | compute_score_from_active(&active_type, &active_name, ty, name) |
435 | } | 305 | } |
436 | 306 | ||
437 | enum Params { | ||
438 | Named(Vec<String>), | ||
439 | Anonymous(usize), | ||
440 | } | ||
441 | |||
442 | impl Params { | ||
443 | fn len(&self) -> usize { | ||
444 | match self { | ||
445 | Params::Named(xs) => xs.len(), | ||
446 | Params::Anonymous(len) => *len, | ||
447 | } | ||
448 | } | ||
449 | |||
450 | fn is_empty(&self) -> bool { | ||
451 | self.len() == 0 | ||
452 | } | ||
453 | } | ||
454 | |||
455 | impl Builder { | ||
456 | fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder { | ||
457 | if !ctx.config.add_call_parenthesis { | ||
458 | return self; | ||
459 | } | ||
460 | if ctx.use_item_syntax.is_some() { | ||
461 | mark::hit!(no_parens_in_use_item); | ||
462 | return self; | ||
463 | } | ||
464 | if ctx.is_pattern_call { | ||
465 | mark::hit!(dont_duplicate_pattern_parens); | ||
466 | return self; | ||
467 | } | ||
468 | if ctx.is_call { | ||
469 | return self; | ||
470 | } | ||
471 | |||
472 | // Don't add parentheses if the expected type is some function reference. | ||
473 | if let Some(ty) = &ctx.expected_type { | ||
474 | if ty.is_fn() { | ||
475 | mark::hit!(no_call_parens_if_fn_ptr_needed); | ||
476 | return self; | ||
477 | } | ||
478 | } | ||
479 | |||
480 | let cap = match ctx.config.snippet_cap { | ||
481 | Some(it) => it, | ||
482 | None => return self, | ||
483 | }; | ||
484 | // If not an import, add parenthesis automatically. | ||
485 | mark::hit!(inserts_parens_for_function_calls); | ||
486 | |||
487 | let (snippet, label) = if params.is_empty() { | ||
488 | (format!("{}()$0", name), format!("{}()", name)) | ||
489 | } else { | ||
490 | self = self.trigger_call_info(); | ||
491 | let snippet = match (ctx.config.add_call_argument_snippets, params) { | ||
492 | (true, Params::Named(params)) => { | ||
493 | let function_params_snippet = | ||
494 | params.iter().enumerate().format_with(", ", |(index, param_name), f| { | ||
495 | f(&format_args!("${{{}:{}}}", index + 1, param_name)) | ||
496 | }); | ||
497 | format!("{}({})$0", name, function_params_snippet) | ||
498 | } | ||
499 | _ => { | ||
500 | mark::hit!(suppress_arg_snippets); | ||
501 | format!("{}($0)", name) | ||
502 | } | ||
503 | }; | ||
504 | |||
505 | (snippet, format!("{}(…)", name)) | ||
506 | }; | ||
507 | self.lookup_by(name).label(label).insert_snippet(cap, snippet) | ||
508 | } | ||
509 | } | ||
510 | |||
511 | fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { | 307 | fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { |
512 | node.attrs(db).by_key("deprecated").exists() | 308 | node.attrs(db).by_key("deprecated").exists() |
513 | } | 309 | } |
514 | 310 | ||
515 | fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { | ||
516 | let mut votes = [0, 0, 0]; | ||
517 | for (idx, s) in docs.match_indices(¯o_name) { | ||
518 | let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); | ||
519 | // Ensure to match the full word | ||
520 | if after.starts_with('!') | ||
521 | && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) | ||
522 | { | ||
523 | // It may have spaces before the braces like `foo! {}` | ||
524 | match after[1..].chars().find(|&c| !c.is_whitespace()) { | ||
525 | Some('{') => votes[0] += 1, | ||
526 | Some('[') => votes[1] += 1, | ||
527 | Some('(') => votes[2] += 1, | ||
528 | _ => {} | ||
529 | } | ||
530 | } | ||
531 | } | ||
532 | |||
533 | // Insert a space before `{}`. | ||
534 | // We prefer the last one when some votes equal. | ||
535 | let (_vote, (bra, ket)) = votes | ||
536 | .iter() | ||
537 | .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) | ||
538 | .max_by_key(|&(&vote, _)| vote) | ||
539 | .unwrap(); | ||
540 | (*bra, *ket) | ||
541 | } | ||
542 | |||
543 | #[cfg(test)] | 311 | #[cfg(test)] |
544 | mod tests { | 312 | mod tests { |
545 | use std::cmp::Reverse; | 313 | use std::cmp::Reverse; |