]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/completions/postfix.rs
feat: Add very simplistic ident completion for format_args! macro input
[rust.git] / crates / ide_completion / src / completions / postfix.rs
index aaa346eeae9aa0d5cf807130ca7706308fff88b0..e8e0c7ea9f1d1b65d7e66e8a3a206d4bcefe3a09 100644 (file)
@@ -2,20 +2,22 @@
 
 mod format_like;
 
-use ide_db::{helpers::SnippetCap, ty_filter::TryEnum};
+use hir::{Documentation, HasAttrs};
+use ide_db::{
+    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) {
@@ -35,22 +37,53 @@ 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,
+        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(CompletionItemKind::Keyword, ctx.source_range(), "await");
+        item.detail("expr.await");
+        item.add_to(acc);
+    }
+
     let cap = match ctx.config.snippet_cap {
         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),
@@ -58,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),
@@ -69,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),
@@ -79,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),
@@ -90,52 +114,42 @@ 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))
+        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(
+                "for",
+                "for ele in expr {}",
+                &format!("for ele in {} {{\n    $0\n}}", receiver_text),
+            )
             .add_to(acc);
+        }
     }
 
-    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),
@@ -144,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!(
@@ -159,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),
@@ -170,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);
         }
     }
 
@@ -283,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 mut relevance = CompletionRelevance::default();
-        relevance.exact_postfix_snippet_match = true;
-        item.set_relevance(relevance);
+    receiver: &'ctx ast::Expr,
+) -> Option<impl Fn(&str, &str, &str) -> 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
+            item
+        }
+    }
+    Some(build(ctx, cap, delete_range))
+}
+
+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)]
@@ -313,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)
     }
 
@@ -339,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)
@@ -372,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)
@@ -396,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)
@@ -425,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)
@@ -532,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(