]> 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 b74030c17350eb787a29d793e207db0cc3355f2c..e8e0c7ea9f1d1b65d7e66e8a3a206d4bcefe3a09 100644 (file)
@@ -2,9 +2,9 @@
 
 mod format_like;
 
-use hir::Documentation;
+use hir::{Documentation, HasAttrs};
 use ide_db::{
-    helpers::{insert_use::ImportScope, FamousDefs, SnippetCap},
+    helpers::{insert_use::ImportScope, SnippetCap},
     ty_filter::TryEnum,
 };
 use syntax::{
 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) {
@@ -45,8 +43,9 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
 
     // 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);
     }
 
@@ -55,7 +54,26 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
         None => return,
     };
 
-    let postfix_snippet = build_postfix_snippet_builder(ctx, cap, &dot_receiver);
+    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);
@@ -105,7 +123,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
         )
         .add_to(acc);
         postfix_snippet("not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
-    } else if let Some(trait_) = FamousDefs(&ctx.sema, ctx.krate).core_iter_IntoIterator() {
+    } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
         if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
             postfix_snippet(
                 "for",
@@ -123,7 +141,10 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
     // 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 = build_postfix_snippet_builder(ctx, cap, &dot_receiver);
+    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 {
@@ -158,10 +179,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
     }
 
     postfix_snippet("box", "Box::new(expr)", &format!("Box::new({})", receiver_text)).add_to(acc);
-    postfix_snippet("ok", "Ok(expr)", &format!("Ok({})", receiver_text)).add_to(acc);
-    postfix_snippet("err", "Err(expr)", &format!("Err({})", receiver_text)).add_to(acc);
-    postfix_snippet("some", "Some(expr)", &format!("Some({})", receiver_text)).add_to(acc);
-    postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({})", 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);
 
@@ -200,27 +218,41 @@ fn include_references(initial_element: &ast::Expr) -> ast::Expr {
     resulting_element
 }
 
-fn build_postfix_snippet_builder<'a>(
-    ctx: &'a CompletionContext,
+fn build_postfix_snippet_builder<'ctx>(
+    ctx: &'ctx CompletionContext,
     cap: SnippetCap,
-    receiver: &'a ast::Expr,
-) -> impl Fn(&str, &str, &str) -> Builder + 'a {
+    receiver: &'ctx ast::Expr,
+) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
     let receiver_syntax = receiver.syntax();
-    let receiver_range = ctx.sema.original_range(receiver_syntax).range;
+    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());
 
-    move |label, detail, snippet| {
-        let edit = 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);
-        }
+    // 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(
@@ -229,9 +261,8 @@ fn add_custom_postfix_completions(
     postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
     receiver_text: &str,
 ) -> Option<()> {
-    let import_scope =
-        ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
-    ctx.config.postfix_snippets().filter(|(_, snip)| snip.is_expr()).for_each(
+    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,
@@ -255,12 +286,12 @@ mod tests {
     use expect_test::{expect, Expect};
 
     use crate::{
-        tests::{check_edit, check_edit_with_config, filtered_completion_list, TEST_CONFIG},
-        CompletionConfig, CompletionKind, Snippet,
+        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)
     }
 
@@ -281,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)
@@ -314,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)
@@ -338,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)
@@ -367,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)