Skip to content

Commit fc8a1cd

Browse files
committed
Introduce render module
1 parent 245e1b5 commit fc8a1cd

File tree

7 files changed

+457
-246
lines changed

7 files changed

+457
-246
lines changed

crates/completion/src/completions.rs

Lines changed: 14 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ pub(crate) mod macro_in_item_position;
1414
pub(crate) mod trait_impl;
1515
pub(crate) mod mod_;
1616

17-
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
18-
use itertools::Itertools;
17+
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type};
1918
use syntax::{ast::NameOwner, display::*};
2019
use test_utils::mark;
2120

2221
use crate::{
23-
item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
24-
CompletionScore, RootDatabase,
22+
item::Builder,
23+
render::{EnumVariantRender, FunctionRender, MacroRender},
24+
CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore,
25+
RootDatabase,
2526
};
2627

2728
/// Represents an in-progress set of completions being built.
@@ -189,50 +190,14 @@ impl Completions {
189190
name: Option<String>,
190191
macro_: hir::MacroDef,
191192
) {
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-
198193
let name = match name {
199194
Some(it) => it,
200195
None => return,
201196
};
202197

203-
let ast_node = macro_.source(ctx.db).value;
204-
let detail = macro_label(&ast_node);
205-
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());
198+
if let Some(item) = MacroRender::new(ctx.into(), name, macro_).render() {
199+
self.add(item);
200+
}
236201
}
237202

238203
pub(crate) fn add_function(
@@ -241,50 +206,9 @@ impl Completions {
241206
func: hir::Function,
242207
local_name: Option<String>,
243208
) {
244-
fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String {
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();
209+
let item = FunctionRender::new(ctx.into(), local_name, func).render();
284210

285-
builder = builder.add_call_parens(ctx, name, Params::Named(params));
286-
287-
self.add(builder.build())
211+
self.add(item)
288212
}
289213

290214
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
@@ -325,7 +249,8 @@ impl Completions {
325249
variant: hir::EnumVariant,
326250
path: ModPath,
327251
) {
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);
329254
}
330255

331256
pub(crate) fn add_enum_variant(
@@ -334,63 +259,8 @@ impl Completions {
334259
variant: hir::EnumVariant,
335260
local_name: Option<String>,
336261
) {
337-
self.add_enum_variant_impl(ctx, variant, local_name, None)
338-
}
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);
262+
let item = EnumVariantRender::new(ctx.into(), local_name, variant, None).render();
263+
self.add(item);
394264
}
395265
}
396266

@@ -434,112 +304,10 @@ fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<Compl
434304
compute_score_from_active(&active_type, &active_name, ty, name)
435305
}
436306

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-
511307
fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
512308
node.attrs(db).by_key("deprecated").exists()
513309
}
514310

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(&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-
543311
#[cfg(test)]
544312
mod tests {
545313
use std::cmp::Reverse;

crates/completion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod patterns;
77
mod generated_lint_completions;
88
#[cfg(test)]
99
mod test_utils;
10+
mod render;
1011

1112
mod completions;
1213

crates/completion/src/render.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! `render` module provides utilities for rendering completion suggestions
2+
//! into code pieces that will be presented to user.
3+
4+
mod macro_;
5+
mod function;
6+
mod builder_ext;
7+
mod enum_variant;
8+
9+
use hir::HasAttrs;
10+
use ide_db::RootDatabase;
11+
use syntax::TextRange;
12+
13+
use crate::{config::SnippetCap, CompletionContext};
14+
15+
pub(crate) use crate::render::{
16+
enum_variant::EnumVariantRender, function::FunctionRender, macro_::MacroRender,
17+
};
18+
19+
#[derive(Debug)]
20+
pub(crate) struct RenderContext<'a> {
21+
completion: &'a CompletionContext<'a>,
22+
}
23+
24+
impl<'a> RenderContext<'a> {
25+
pub fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
26+
RenderContext { completion }
27+
}
28+
29+
pub fn snippet_cap(&self) -> Option<SnippetCap> {
30+
self.completion.config.snippet_cap.clone()
31+
}
32+
33+
pub fn db(&self) -> &'a RootDatabase {
34+
&self.completion.db
35+
}
36+
37+
pub fn source_range(&self) -> TextRange {
38+
self.completion.source_range()
39+
}
40+
41+
pub fn is_deprecated(&self, node: impl HasAttrs) -> bool {
42+
node.attrs(self.db()).by_key("deprecated").exists()
43+
}
44+
}
45+
46+
impl<'a> From<&'a CompletionContext<'a>> for RenderContext<'a> {
47+
fn from(ctx: &'a CompletionContext<'a>) -> RenderContext<'a> {
48+
RenderContext::new(ctx)
49+
}
50+
}

0 commit comments

Comments
 (0)