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