about summary refs log tree commit diff
diff options
context:
space:
mode:
authordfireBird <me@dfirebird.dev>2023-10-10 08:30:49 +0530
committerdfireBird <me@dfirebird.dev>2023-11-28 22:21:57 +0530
commita0e690a7e999ac1a3260bc4cf41b8e3219b74e4f (patch)
tree6ca9414205068f3c917a2cb9e4b09a5a6e6abb45
parentc7c582afb57bb802715262d7f1ba73b8a86c1c5a (diff)
downloadrust-a0e690a7e999ac1a3260bc4cf41b8e3219b74e4f.tar.gz
rust-a0e690a7e999ac1a3260bc4cf41b8e3219b74e4f.zip
add different completion for fn fields
-rw-r--r--crates/ide-completion/src/completions/dot.rs34
-rw-r--r--crates/ide-completion/src/render.rs19
2 files changed, 52 insertions, 1 deletions
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 5bcc867fe18..12591449cdb 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -28,6 +28,13 @@ pub(crate) fn complete_dot(
 
     if let DotAccessKind::Method { .. } = dot_access.kind {
         cov_mark::hit!(test_no_struct_field_completion_for_method_call);
+        complete_fn_fields(
+            acc,
+            ctx,
+            receiver_ty,
+            |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
+            |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
+        );
     } else {
         complete_fields(
             acc,
@@ -144,6 +151,33 @@ fn complete_methods(
     );
 }
 
+fn complete_fn_fields(
+    acc: &mut Completions,
+    ctx: &CompletionContext<'_>,
+    receiver: &hir::Type,
+    mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
+    mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
+) {
+    let mut seen_names = FxHashSet::default();
+    for receiver in receiver.autoderef(ctx.db) {
+        for (field, ty) in receiver.fields(ctx.db) {
+            if seen_names.insert(field.name(ctx.db)) && (ty.is_fn() || ty.is_closure()) {
+                named_field(acc, field, ty);
+            }
+        }
+        for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
+            // Tuples are always the last type in a deref chain, so just check if the name is
+            // already seen without inserting into the hashset.
+            if !seen_names.contains(&hir::Name::new_tuple_field(i))
+                && (ty.is_fn() || ty.is_closure())
+            {
+                // Tuple fields are always public (tuple struct fields are handled above).
+                tuple_index(acc, i, ty);
+            }
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 00a9081985b..f05cc78ac6e 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -18,6 +18,7 @@ use ide_db::{
     RootDatabase, SnippetCap, SymbolKind,
 };
 use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
+use text_edit::TextEdit;
 
 use crate::{
     context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
@@ -147,7 +148,23 @@ pub(crate) fn render_field(
         .set_documentation(field.docs(db))
         .set_deprecated(is_deprecated)
         .lookup_by(name);
-    item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
+    if ty.is_fn() || ty.is_closure() {
+        let mut builder = TextEdit::builder();
+        // Use TextEdit to insert / replace the ranges:
+        // 1. Insert one character ('(') before start of struct name
+        // 2. Insert one character (')') before call parens
+        // 3. Variable character of the actual field name
+        // 4. Optionally, two character ('()') for fn call
+        //
+        // TODO: Find a way to get the above ranges, especially the first two
+        builder.replace(
+            ctx.source_range(),
+            field_with_receiver(db, receiver.as_ref(), &escaped_name).into(),
+        );
+        item.text_edit(builder.finish());
+    } else {
+        item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
+    }
     if let Some(receiver) = &dot_access.receiver {
         if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
             if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {