]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/snippet.rs
Merge #10649
[rust.git] / crates / ide_completion / src / completions / snippet.rs
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
12 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
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
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
99 fn add_custom_completions(
100     acc: &mut Completions,
101     ctx: &CompletionContext,
102     cap: SnippetCap,
103     scope: SnippetScope,
104 ) -> Option<()> {
105     let import_scope =
106         ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
107     ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
108         |(trigger, snip)| {
109             let imports = match snip.imports(ctx, &import_scope) {
110                 Some(imports) => imports,
111                 None => return,
112             };
113             let body = snip.snippet();
114             let mut builder = snippet(ctx, cap, &trigger, &body);
115             builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
116             for import in imports.into_iter() {
117                 builder.add_import(import);
118             }
119             builder.detail(snip.description.as_deref().unwrap_or_default());
120             builder.add_to(acc);
121         },
122     );
123     None
124 }
125
126 #[cfg(test)]
127 mod tests {
128     use crate::{
129         tests::{check_edit_with_config, TEST_CONFIG},
130         CompletionConfig, Snippet,
131     };
132
133     #[test]
134     fn custom_snippet_completion() {
135         check_edit_with_config(
136             CompletionConfig {
137                 snippets: vec![Snippet::new(
138                     &["break".into()],
139                     &[],
140                     &["ControlFlow::Break(())".into()],
141                     "",
142                     &["core::ops::ControlFlow".into()],
143                     crate::SnippetScope::Expr,
144                 )
145                 .unwrap()],
146                 ..TEST_CONFIG
147             },
148             "break",
149             r#"
150 //- minicore: try
151 fn main() { $0 }
152 "#,
153             r#"
154 use core::ops::ControlFlow;
155
156 fn main() { ControlFlow::Break(()) }
157 "#,
158         );
159     }
160 }