]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs
Auto merge of #99553 - ChrisDenton:lazy-compat-fn, r=Mark-Simulacrum
[rust.git] / src / tools / rust-analyzer / 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::{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);
36         snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
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);
49     }
50 }
51
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);
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);
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);
118     }
119 }
120
121 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
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{}\n```", body)));
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);
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]
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 }