1 //! This file provides snippet completions, like `pd` => `eprintln!(...)`.
2
3 use hir::Documentation;
4 use ide_db::{imports::insert_use::ImportScope, SnippetCap};
5
6 use crate::{
7 context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified},
8 item::Builder,
9 CompletionContext, CompletionItem, CompletionItemKind, Completions, SnippetScope,
10 };
11
12 pub(crate) fn complete_expr_snippet(
13 acc: &mut Completions,
14 ctx: &CompletionContext<'_>,
15 path_ctx: &PathCompletionCtx,
16 &ExprCtx { in_block_expr, .. }: &ExprCtx,
17 ) {
18 if !matches!(path_ctx.qualified, Qualified::No) {
19 return;
20 }
21 if !ctx.qualifier_ctx.none() {
22 return;
23 }
24
25 let cap = match ctx.config.snippet_cap {
26 Some(it) => it,
27 None => return,
28 };
29
30 if !ctx.config.snippets.is_empty() {
31 add_custom_completions(acc, ctx, cap, SnippetScope::Expr);
32 }
33
34 if in_block_expr {
35 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc, ctx.db);
36 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc, ctx.db);
37 let item = snippet(
38 ctx,
39 cap,
40 "macro_rules",
41 "\
42 macro_rules! $1 {
43 ($2) => {
44 $0
45 };
46 }",
47 );
48 item.add_to(acc, ctx.db);
49 }
50 }
51
complete_item_snippet( acc: &mut Completions, ctx: &CompletionContext<'_>, path_ctx: &PathCompletionCtx, kind: &ItemListKind, )52 pub(crate) fn complete_item_snippet(
53 acc: &mut Completions,
54 ctx: &CompletionContext<'_>,
55 path_ctx: &PathCompletionCtx,
56 kind: &ItemListKind,
57 ) {
58 if !matches!(path_ctx.qualified, Qualified::No) {
59 return;
60 }
61 if !ctx.qualifier_ctx.none() {
62 return;
63 }
64 let cap = match ctx.config.snippet_cap {
65 Some(it) => it,
66 None => return,
67 };
68
69 if !ctx.config.snippets.is_empty() {
70 add_custom_completions(acc, ctx, cap, SnippetScope::Item);
71 }
72
73 // Test-related snippets shouldn't be shown in blocks.
74 if let ItemListKind::SourceFile | ItemListKind::Module = kind {
75 let mut item = snippet(
76 ctx,
77 cap,
78 "tmod (Test module)",
79 "\
80 #[cfg(test)]
81 mod tests {
82 use super::*;
83
84 #[test]
85 fn ${1:test_name}() {
86 $0
87 }
88 }",
89 );
90 item.lookup_by("tmod");
91 item.add_to(acc, ctx.db);
92
93 let mut item = snippet(
94 ctx,
95 cap,
96 "tfn (Test function)",
97 "\
98 #[test]
99 fn ${1:feature}() {
100 $0
101 }",
102 );
103 item.lookup_by("tfn");
104 item.add_to(acc, ctx.db);
105
106 let item = snippet(
107 ctx,
108 cap,
109 "macro_rules",
110 "\
111 macro_rules! $1 {
112 ($2) => {
113 $0
114 };
115 }",
116 );
117 item.add_to(acc, ctx.db);
118 }
119 }
120
snippet(ctx: &CompletionContext<'_>, cap: SnippetCap, label: &str, snippet: &str) -> Builder121 fn snippet(ctx: &CompletionContext<'_>, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
122 let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
123 item.insert_snippet(cap, snippet);
124 item
125 }
126
add_custom_completions( acc: &mut Completions, ctx: &CompletionContext<'_>, cap: SnippetCap, scope: SnippetScope, ) -> Option<()>127 fn add_custom_completions(
128 acc: &mut Completions,
129 ctx: &CompletionContext<'_>,
130 cap: SnippetCap,
131 scope: SnippetScope,
132 ) -> Option<()> {
133 if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
134 return None;
135 }
136 ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
137 |(trigger, snip)| {
138 let imports = match snip.imports(ctx) {
139 Some(imports) => imports,
140 None => return,
141 };
142 let body = snip.snippet();
143 let mut builder = snippet(ctx, cap, trigger, &body);
144 builder.documentation(Documentation::new(format!("```rust\n{body}\n```")));
145 for import in imports.into_iter() {
146 builder.add_import(import);
147 }
148 builder.set_detail(snip.description.clone());
149 builder.add_to(acc, ctx.db);
150 },
151 );
152 None
153 }
154
155 #[cfg(test)]
156 mod tests {
157 use crate::{
158 tests::{check_edit_with_config, TEST_CONFIG},
159 CompletionConfig, Snippet,
160 };
161
162 #[test]
custom_snippet_completion()163 fn custom_snippet_completion() {
164 check_edit_with_config(
165 CompletionConfig {
166 snippets: vec![Snippet::new(
167 &["break".into()],
168 &[],
169 &["ControlFlow::Break(())".into()],
170 "",
171 &["core::ops::ControlFlow".into()],
172 crate::SnippetScope::Expr,
173 )
174 .unwrap()],
175 ..TEST_CONFIG
176 },
177 "break",
178 r#"
179 //- minicore: try
180 fn main() { $0 }
181 "#,
182 r#"
183 use core::ops::ControlFlow;
184
185 fn main() { ControlFlow::Break(()) }
186 "#,
187 );
188 }
189 }
190