aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/completions.rs
diff options
context:
space:
mode:
authorIgor Aleksanov <[email protected]>2020-11-01 09:35:04 +0000
committerIgor Aleksanov <[email protected]>2020-11-03 07:16:35 +0000
commitfc8a1cd8006b021541ff673ec7f37a0f4b7bef57 (patch)
treeb5153e990c97a86608ad69b4aacc2b2dd88f1614 /crates/completion/src/completions.rs
parent245e1b533b5be5ea4a917957fb02d7f57e6b4661 (diff)
Introduce render module
Diffstat (limited to 'crates/completion/src/completions.rs')
-rw-r--r--crates/completion/src/completions.rs260
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;
14pub(crate) mod trait_impl; 14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 15pub(crate) mod mod_;
16 16
17use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; 17use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type};
18use itertools::Itertools;
19use syntax::{ast::NameOwner, display::*}; 18use syntax::{ast::NameOwner, display::*};
20use test_utils::mark; 19use test_utils::mark;
21 20
22use crate::{ 21use 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
437enum Params {
438 Named(Vec<String>),
439 Anonymous(usize),
440}
441
442impl 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
455impl 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
511fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { 307fn 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
515fn 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(&macro_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)]
544mod tests { 312mod tests {
545 use std::cmp::Reverse; 313 use std::cmp::Reverse;