Skip to main content

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(
121    ctx: &CompletionContext<'_, '_>,
122    cap: SnippetCap,
123    label: &str,
124    snippet: &str,
125) -> Builder {
126    let mut item =
127        CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label, ctx.edition);
128    item.insert_snippet(cap, snippet);
129    item
130}
131
132fn add_custom_completions(
133    acc: &mut Completions,
134    ctx: &CompletionContext<'_, '_>,
135    cap: SnippetCap,
136    scope: SnippetScope,
137) -> Option<()> {
138    ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
139    ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
140        |(trigger, snip)| {
141            let imports = match snip.imports(ctx) {
142                Some(imports) => imports,
143                None => return,
144            };
145            let body = snip.snippet();
146            let mut builder = snippet(ctx, cap, trigger, &body);
147            builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```")));
148            for import in imports.into_iter() {
149                builder.add_import(import);
150            }
151            builder.set_detail(snip.description.clone());
152            builder.add_to(acc, ctx.db);
153        },
154    );
155    None
156}
157
158#[cfg(test)]
159mod tests {
160    use crate::{
161        CompletionConfig, Snippet,
162        tests::{TEST_CONFIG, check_edit_with_config},
163    };
164
165    #[test]
166    fn custom_snippet_completion() {
167        check_edit_with_config(
168            CompletionConfig {
169                snippets: vec![
170                    Snippet::new(
171                        &["break".into()],
172                        &[],
173                        &["ControlFlow::Break(())".into()],
174                        "",
175                        &["core::ops::ControlFlow".into()],
176                        crate::SnippetScope::Expr,
177                    )
178                    .unwrap(),
179                ],
180                ..TEST_CONFIG
181            },
182            "break",
183            r#"
184//- minicore: try
185fn main() { $0 }
186"#,
187            r#"
188use core::ops::ControlFlow;
189
190fn main() { ControlFlow::Break(()) }
191"#,
192        );
193    }
194}