X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=crates%2Fide_completion%2Fsrc%2Fcompletions%2Fpostfix.rs;h=e8e0c7ea9f1d1b65d7e66e8a3a206d4bcefe3a09;hb=82fccb971e49d6d8945b7764dd9e9ed883f6148f;hp=7ec1f939c3e7a8d76bb902106b3a9bddb29f596c;hpb=25ff7171c4559284f462ddaadee491e4791d9f0a;p=rust.git diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs index 7ec1f939c3e..e8e0c7ea9f1 100644 --- a/crates/ide_completion/src/completions/postfix.rs +++ b/crates/ide_completion/src/completions/postfix.rs @@ -2,23 +2,22 @@ mod format_like; +use hir::{Documentation, HasAttrs}; use ide_db::{ - helpers::{FamousDefs, SnippetCap}, + helpers::{insert_use::ImportScope, SnippetCap}, ty_filter::TryEnum, }; use syntax::{ ast::{self, AstNode, AstToken}, - SyntaxKind::{BLOCK_EXPR, EXPR_STMT}, + SyntaxKind::{EXPR_STMT, STMT_LIST}, TextRange, TextSize, }; use text_edit::TextEdit; use crate::{ - completions::postfix::format_like::add_format_like_completions, - context::CompletionContext, - item::{Builder, CompletionKind}, - patterns::ImmediateLocation, - CompletionItem, CompletionItemKind, CompletionRelevance, Completions, + completions::postfix::format_like::add_format_like_completions, context::CompletionContext, + item::Builder, patterns::ImmediateLocation, CompletionItem, CompletionItemKind, + CompletionRelevance, Completions, SnippetScope, }; pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { @@ -38,14 +37,15 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal); let receiver_ty = match ctx.sema.type_of_expr(dot_receiver) { - Some(it) => it.ty, + Some(it) => it.original, None => return, }; // Suggest .await syntax for types that implement Future trait if receiver_ty.impls_future(ctx.db) { - let mut item = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await"); - item.kind(CompletionItemKind::Keyword).detail("expr.await"); + let mut item = + CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await"); + item.detail("expr.await"); item.add_to(acc); } @@ -53,14 +53,37 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { Some(it) => it, None => return, }; + + let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) { + Some(it) => it, + None => return, + }; + + if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop() { + if receiver_ty.impls_trait(ctx.db, drop_trait, &[]) { + if let &[hir::AssocItem::Function(drop_fn)] = &*drop_trait.items(ctx.db) { + cov_mark::hit!(postfix_drop_completion); + // FIXME: check that `drop` is in scope, use fully qualified path if it isn't/if shadowed + let mut item = postfix_snippet( + "drop", + "fn drop(&mut self)", + &format!("drop($0{})", receiver_text), + ); + item.set_documentation(drop_fn.docs(ctx.db)); + item.add_to(acc); + } + } + } + + if !ctx.config.snippets.is_empty() { + add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text); + } + let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references()); if let Some(try_enum) = &try_enum { match try_enum { TryEnum::Result => { postfix_snippet( - ctx, - cap, - dot_receiver, "ifl", "if let Ok {}", &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), @@ -68,9 +91,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { .add_to(acc); postfix_snippet( - ctx, - cap, - dot_receiver, "while", "while let Ok {}", &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), @@ -79,9 +99,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { } TryEnum::Option => { postfix_snippet( - ctx, - cap, - dot_receiver, "ifl", "if let Some {}", &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), @@ -89,9 +106,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { .add_to(acc); postfix_snippet( - ctx, - cap, - dot_receiver, "while", "while let Some {}", &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), @@ -100,32 +114,18 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { } } } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { + postfix_snippet("if", "if expr {}", &format!("if {} {{\n $0\n}}", receiver_text)) + .add_to(acc); postfix_snippet( - ctx, - cap, - dot_receiver, - "if", - "if expr {}", - &format!("if {} {{\n $0\n}}", receiver_text), - ) - .add_to(acc); - postfix_snippet( - ctx, - cap, - dot_receiver, "while", "while expr {}", &format!("while {} {{\n $0\n}}", receiver_text), ) .add_to(acc); - postfix_snippet(ctx, cap, dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) - .add_to(acc); - } else if let Some(trait_) = FamousDefs(&ctx.sema, ctx.krate).core_iter_IntoIterator() { + postfix_snippet("not", "!expr", &format!("!{}", receiver_text)).add_to(acc); + } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() { if receiver_ty.impls_trait(ctx.db, trait_, &[]) { postfix_snippet( - ctx, - cap, - dot_receiver, "for", "for ele in expr {}", &format!("for ele in {} {{\n $0\n}}", receiver_text), @@ -134,30 +134,22 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { } } - postfix_snippet(ctx, cap, dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)) - .add_to(acc); - postfix_snippet( - ctx, - cap, - dot_receiver, - "refm", - "&mut expr", - &format!("&mut {}", receiver_text), - ) - .add_to(acc); + postfix_snippet("ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); + postfix_snippet("refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); // The rest of the postfix completions create an expression that moves an argument, // so it's better to consider references now to avoid breaking the compilation let dot_receiver = include_references(dot_receiver); let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal); + let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) { + Some(it) => it, + None => return, + }; match try_enum { Some(try_enum) => match try_enum { TryEnum::Result => { postfix_snippet( - ctx, - cap, - &dot_receiver, "match", "match expr {}", &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text), @@ -166,9 +158,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { } TryEnum::Option => { postfix_snippet( - ctx, - cap, - &dot_receiver, "match", "match expr {}", &format!( @@ -181,9 +170,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { }, None => { postfix_snippet( - ctx, - cap, - &dot_receiver, "match", "match expr {}", &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text), @@ -192,89 +178,16 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { } } - postfix_snippet( - ctx, - cap, - &dot_receiver, - "box", - "Box::new(expr)", - &format!("Box::new({})", receiver_text), - ) - .add_to(acc); - - postfix_snippet(ctx, cap, &dot_receiver, "ok", "Ok(expr)", &format!("Ok({})", receiver_text)) - .add_to(acc); - - postfix_snippet( - ctx, - cap, - &dot_receiver, - "err", - "Err(expr)", - &format!("Err({})", receiver_text), - ) - .add_to(acc); - - postfix_snippet( - ctx, - cap, - &dot_receiver, - "some", - "Some(expr)", - &format!("Some({})", receiver_text), - ) - .add_to(acc); - - postfix_snippet( - ctx, - cap, - &dot_receiver, - "dbg", - "dbg!(expr)", - &format!("dbg!({})", receiver_text), - ) - .add_to(acc); - - postfix_snippet( - ctx, - cap, - &dot_receiver, - "dbgr", - "dbg!(&expr)", - &format!("dbg!(&{})", receiver_text), - ) - .add_to(acc); - - postfix_snippet( - ctx, - cap, - &dot_receiver, - "call", - "function(expr)", - &format!("${{1}}({})", receiver_text), - ) - .add_to(acc); + postfix_snippet("box", "Box::new(expr)", &format!("Box::new({})", receiver_text)).add_to(acc); + postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); // fixme + postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{})", receiver_text)).add_to(acc); + postfix_snippet("call", "function(expr)", &format!("${{1}}({})", receiver_text)).add_to(acc); if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) { - if matches!(parent.kind(), BLOCK_EXPR | EXPR_STMT) { - postfix_snippet( - ctx, - cap, - &dot_receiver, - "let", - "let", - &format!("let $0 = {};", receiver_text), - ) - .add_to(acc); - postfix_snippet( - ctx, - cap, - &dot_receiver, - "letm", - "let mut", - &format!("let mut $0 = {};", receiver_text), - ) - .add_to(acc); + if matches!(parent.kind(), STMT_LIST | EXPR_STMT) { + postfix_snippet("let", "let", &format!("let $0 = {};", receiver_text)).add_to(acc); + postfix_snippet("letm", "let mut", &format!("let mut $0 = {};", receiver_text)) + .add_to(acc); } } @@ -305,29 +218,67 @@ fn include_references(initial_element: &ast::Expr) -> ast::Expr { resulting_element } -fn postfix_snippet( - ctx: &CompletionContext, +fn build_postfix_snippet_builder<'ctx>( + ctx: &'ctx CompletionContext, cap: SnippetCap, - receiver: &ast::Expr, - label: &str, - detail: &str, - snippet: &str, -) -> Builder { - let edit = { - let receiver_syntax = receiver.syntax(); - let receiver_range = ctx.sema.original_range(receiver_syntax).range; - let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end()); - TextEdit::replace(delete_range, snippet.to_string()) - }; - let mut item = CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label); - item.detail(detail).kind(CompletionItemKind::Snippet).snippet_edit(cap, edit); - if ctx.original_token.text() == label { - let relevance = - CompletionRelevance { exact_postfix_snippet_match: true, ..Default::default() }; - item.set_relevance(relevance); + receiver: &'ctx ast::Expr, +) -> Option Builder + 'ctx> { + let receiver_syntax = receiver.syntax(); + let receiver_range = ctx.sema.original_range_opt(receiver_syntax)?.range; + if ctx.source_range().end() < receiver_range.start() { + // This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping. + return None; } + let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end()); + + // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that + // can't be annotated for the closure, hence fix it by constructing it without the Option first + fn build<'ctx>( + ctx: &'ctx CompletionContext, + cap: SnippetCap, + delete_range: TextRange, + ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx { + move |label, detail, snippet| { + let edit = TextEdit::replace(delete_range, snippet.to_string()); + let mut item = + CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label); + item.detail(detail).snippet_edit(cap, edit); + if ctx.original_token.text() == label { + let relevance = + CompletionRelevance { exact_postfix_snippet_match: true, ..Default::default() }; + item.set_relevance(relevance); + } + + item + } + } + Some(build(ctx, cap, delete_range)) +} - item +fn add_custom_postfix_completions( + acc: &mut Completions, + ctx: &CompletionContext, + postfix_snippet: impl Fn(&str, &str, &str) -> Builder, + receiver_text: &str, +) -> Option<()> { + let import_scope = ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?; + ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each( + |(trigger, snippet)| { + let imports = match snippet.imports(ctx, &import_scope) { + Some(imports) => imports, + None => return, + }; + let body = snippet.postfix_snippet(&receiver_text); + let mut builder = + postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body); + builder.documentation(Documentation::new(format!("```rust\n{}\n```", body))); + for import in imports.into_iter() { + builder.add_import(import); + } + builder.add_to(acc); + }, + ); + None } #[cfg(test)] @@ -335,12 +286,12 @@ mod tests { use expect_test::{expect, Expect}; use crate::{ - tests::{check_edit, filtered_completion_list}, - CompletionKind, + tests::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG}, + CompletionConfig, Snippet, }; fn check(ra_fixture: &str, expect: Expect) { - let actual = filtered_completion_list(ra_fixture, CompletionKind::Postfix); + let actual = completion_list(ra_fixture); expect.assert_eq(&actual) } @@ -361,9 +312,6 @@ fn main() { sn refm &mut expr sn match match expr {} sn box Box::new(expr) - sn ok Ok(expr) - sn err Err(expr) - sn some Some(expr) sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn call function(expr) @@ -394,9 +342,6 @@ fn main() { sn refm &mut expr sn match match expr {} sn box Box::new(expr) - sn ok Ok(expr) - sn err Err(expr) - sn some Some(expr) sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn call function(expr) @@ -418,9 +363,6 @@ fn main() { sn refm &mut expr sn match match expr {} sn box Box::new(expr) - sn ok Ok(expr) - sn err Err(expr) - sn some Some(expr) sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn call function(expr) @@ -447,9 +389,6 @@ fn main() { sn refm &mut expr sn match match expr {} sn box Box::new(expr) - sn ok Ok(expr) - sn err Err(expr) - sn some Some(expr) sn dbg dbg!(expr) sn dbgr dbg!(&expr) sn call function(expr) @@ -554,6 +493,34 @@ fn main() { ) } + #[test] + fn custom_postfix_completion() { + check_edit_with_config( + CompletionConfig { + snippets: vec![Snippet::new( + &[], + &["break".into()], + &["ControlFlow::Break(${receiver})".into()], + "", + &["core::ops::ControlFlow".into()], + crate::SnippetScope::Expr, + ) + .unwrap()], + ..TEST_CONFIG + }, + "break", + r#" +//- minicore: try +fn main() { 42.$0 } +"#, + r#" +use core::ops::ControlFlow; + +fn main() { ControlFlow::Break(42) } +"#, + ); + } + #[test] fn postfix_completion_for_format_like_strings() { check_edit(