1 //! This file provides snippet completions, like `pd` => `eprintln!(...)`.
2 
3 use hir::Documentation;
4 use ide_db::helpers::{insert_use::ImportScope, SnippetCap};
5 use syntax::T;
6 
7 use crate::{
8     context::PathCompletionContext, item::Builder, CompletionContext, CompletionItem,
9     CompletionItemKind, Completions, SnippetScope,
10 };
11 
snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder12 fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
13     let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
14     item.insert_snippet(cap, snippet);
15     item
16 }
17 
complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext)18 pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
19     if ctx.function_def.is_none() {
20         return;
21     }
22 
23     let can_be_stmt = match ctx.path_context {
24         Some(PathCompletionContext { is_trivial_path: true, can_be_stmt, .. }) => can_be_stmt,
25         _ => return,
26     };
27 
28     let cap = match ctx.config.snippet_cap {
29         Some(it) => it,
30         None => return,
31     };
32 
33     if !ctx.config.snippets.is_empty() {
34         add_custom_completions(acc, ctx, cap, SnippetScope::Expr);
35     }
36 
37     if can_be_stmt {
38         snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
39         snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
40     }
41 }
42 
complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext)43 pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
44     if !ctx.expects_item()
45         || ctx.previous_token_is(T![unsafe])
46         || ctx.path_qual().is_some()
47         || ctx.has_impl_or_trait_prev_sibling()
48     {
49         return;
50     }
51     if ctx.has_visibility_prev_sibling() {
52         return; // technically we could do some of these snippet completions if we were to put the
53                 // attributes before the vis node.
54     }
55     let cap = match ctx.config.snippet_cap {
56         Some(it) => it,
57         None => return,
58     };
59 
60     if !ctx.config.snippets.is_empty() {
61         add_custom_completions(acc, ctx, cap, SnippetScope::Item);
62     }
63 
64     let mut item = snippet(
65         ctx,
66         cap,
67         "tmod (Test module)",
68         "\
69 #[cfg(test)]
70 mod tests {
71     use super::*;
72 
73     #[test]
74     fn ${1:test_name}() {
75         $0
76     }
77 }",
78     );
79     item.lookup_by("tmod");
80     item.add_to(acc);
81 
82     let mut item = snippet(
83         ctx,
84         cap,
85         "tfn (Test function)",
86         "\
87 #[test]
88 fn ${1:feature}() {
89     $0
90 }",
91     );
92     item.lookup_by("tfn");
93     item.add_to(acc);
94 
95     let item = snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}");
96     item.add_to(acc);
97 }
98 
add_custom_completions( acc: &mut Completions, ctx: &CompletionContext, cap: SnippetCap, scope: SnippetScope, ) -> Option<()>99 fn add_custom_completions(
100     acc: &mut Completions,
101     ctx: &CompletionContext,
102     cap: SnippetCap,
103     scope: SnippetScope,
104 ) -> Option<()> {
105     let import_scope = ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
106     ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
107         |(trigger, snip)| {
108             let imports = match snip.imports(ctx, &import_scope) {
109                 Some(imports) => imports,
110                 None => return,
111             };
112             let body = snip.snippet();
113             let mut builder = snippet(ctx, cap, &trigger, &body);
114             builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
115             for import in imports.into_iter() {
116                 builder.add_import(import);
117             }
118             builder.set_detail(snip.description.clone());
119             builder.add_to(acc);
120         },
121     );
122     None
123 }
124 
125 #[cfg(test)]
126 mod tests {
127     use crate::{
128         tests::{check_edit_with_config, TEST_CONFIG},
129         CompletionConfig, Snippet,
130     };
131 
132     #[test]
custom_snippet_completion()133     fn custom_snippet_completion() {
134         check_edit_with_config(
135             CompletionConfig {
136                 snippets: vec![Snippet::new(
137                     &["break".into()],
138                     &[],
139                     &["ControlFlow::Break(())".into()],
140                     "",
141                     &["core::ops::ControlFlow".into()],
142                     crate::SnippetScope::Expr,
143                 )
144                 .unwrap()],
145                 ..TEST_CONFIG
146             },
147             "break",
148             r#"
149 //- minicore: try
150 fn main() { $0 }
151 "#,
152             r#"
153 use core::ops::ControlFlow;
154 
155 fn main() { ControlFlow::Break(()) }
156 "#,
157         );
158     }
159 }
160