diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs')
-rw-r--r-- | src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs new file mode 100644 index 000000000..66adb4286 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs @@ -0,0 +1,189 @@ +//! This file provides snippet completions, like `pd` => `eprintln!(...)`. + +use hir::Documentation; +use ide_db::{imports::insert_use::ImportScope, SnippetCap}; + +use crate::{ + context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified}, + item::Builder, + CompletionContext, CompletionItem, CompletionItemKind, Completions, SnippetScope, +}; + +pub(crate) fn complete_expr_snippet( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx, + &ExprCtx { in_block_expr, .. }: &ExprCtx, +) { + if !matches!(path_ctx.qualified, Qualified::No) { + return; + } + if !ctx.qualifier_ctx.none() { + 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 in_block_expr { + snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); + snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); + let item = snippet( + ctx, + cap, + "macro_rules", + "\ +macro_rules! $1 { + ($2) => { + $0 + }; +}", + ); + item.add_to(acc); + } +} + +pub(crate) fn complete_item_snippet( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx, + kind: &ItemListKind, +) { + if !matches!(path_ctx.qualified, Qualified::No) { + return; + } + if !ctx.qualifier_ctx.none() { + 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::Item); + } + + // Test-related snippets shouldn't be shown in blocks. + if let ItemListKind::SourceFile | ItemListKind::Module = kind { + 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 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 +} + +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(()) } +"#, + ); + } +} |