ide_completion/completions/
snippet.rs

1//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
2
3use 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    // Test-related snippets shouldn't be shown in blocks.
73    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}