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