Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/completions/snippet.rs')
| -rw-r--r-- | crates/ide-completion/src/completions/snippet.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs new file mode 100644 index 0000000000..c00de6ff50 --- /dev/null +++ b/crates/ide-completion/src/completions/snippet.rs @@ -0,0 +1,176 @@ +//! This file provides snippet completions, like `pd` => `eprintln!(...)`. + +use hir::Documentation; +use ide_db::{imports::insert_use::ImportScope, SnippetCap}; +use syntax::T; + +use crate::{ + context::PathCompletionCtx, item::Builder, CompletionContext, CompletionItem, + CompletionItemKind, Completions, SnippetScope, +}; + +fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder { + let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label); + item.insert_snippet(cap, snippet); + item +} + +pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { + if ctx.function_def.is_none() { + return; + } + + let can_be_stmt = match ctx.path_context { + Some(PathCompletionCtx { + is_absolute_path: false, qualifier: None, can_be_stmt, .. + }) => can_be_stmt, + _ => return, + }; + + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return, + }; + + if !ctx.config.snippets.is_empty() { + add_custom_completions(acc, ctx, cap, SnippetScope::Expr); + } + + if can_be_stmt { + snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); + snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); + } +} + +pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { + if !(ctx.expects_item() || ctx.has_block_expr_parent()) + || ctx.previous_token_is(T![unsafe]) + || ctx.path_qual().is_some() + || ctx.has_impl_or_trait_prev_sibling() + { + return; + } + if ctx.has_visibility_prev_sibling() { + return; // technically we could do some of these snippet completions if we were to put the + // attributes before the vis node. + } + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return, + }; + + if !ctx.config.snippets.is_empty() { + add_custom_completions(acc, ctx, cap, SnippetScope::Item); + } + + // Test-related snippets shouldn't be shown in blocks. + if !ctx.has_block_expr_parent() { + let mut item = snippet( + ctx, + cap, + "tmod (Test module)", + "\ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ${1:test_name}() { + $0 + } +}", + ); + item.lookup_by("tmod"); + item.add_to(acc); + + let mut item = snippet( + ctx, + cap, + "tfn (Test function)", + "\ +#[test] +fn ${1:feature}() { + $0 +}", + ); + item.lookup_by("tfn"); + item.add_to(acc); + } + + let item = snippet( + ctx, + cap, + "macro_rules", + "\ +macro_rules! $1 { + ($2) => { + $0 + }; +}", + ); + item.add_to(acc); +} + +fn add_custom_completions( + acc: &mut Completions, + ctx: &CompletionContext, + cap: SnippetCap, + scope: SnippetScope, +) -> Option<()> { + if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() { + return None; + } + ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each( + |(trigger, snip)| { + let imports = match snip.imports(ctx) { + Some(imports) => imports, + None => return, + }; + let body = snip.snippet(); + let mut builder = snippet(ctx, cap, trigger, &body); + builder.documentation(Documentation::new(format!("```rust\n{}\n```", body))); + for import in imports.into_iter() { + builder.add_import(import); + } + builder.set_detail(snip.description.clone()); + builder.add_to(acc); + }, + ); + None +} + +#[cfg(test)] +mod tests { + use crate::{ + tests::{check_edit_with_config, TEST_CONFIG}, + CompletionConfig, Snippet, + }; + + #[test] + fn custom_snippet_completion() { + check_edit_with_config( + CompletionConfig { + snippets: vec![Snippet::new( + &["break".into()], + &[], + &["ControlFlow::Break(())".into()], + "", + &["core::ops::ControlFlow".into()], + crate::SnippetScope::Expr, + ) + .unwrap()], + ..TEST_CONFIG + }, + "break", + r#" +//- minicore: try +fn main() { $0 } +"#, + r#" +use core::ops::ControlFlow; + +fn main() { ControlFlow::Break(()) } +"#, + ); + } +} |