Skip to content

Commit a0e690a

Browse files
committed
add different completion for fn fields
1 parent c7c582a commit a0e690a

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

crates/ide-completion/src/completions/dot.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ pub(crate) fn complete_dot(
2828

2929
if let DotAccessKind::Method { .. } = dot_access.kind {
3030
cov_mark::hit!(test_no_struct_field_completion_for_method_call);
31+
complete_fn_fields(
32+
acc,
33+
ctx,
34+
receiver_ty,
35+
|acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
36+
|acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
37+
);
3138
} else {
3239
complete_fields(
3340
acc,
@@ -144,6 +151,33 @@ fn complete_methods(
144151
);
145152
}
146153

154+
fn complete_fn_fields(
155+
acc: &mut Completions,
156+
ctx: &CompletionContext<'_>,
157+
receiver: &hir::Type,
158+
mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
159+
mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
160+
) {
161+
let mut seen_names = FxHashSet::default();
162+
for receiver in receiver.autoderef(ctx.db) {
163+
for (field, ty) in receiver.fields(ctx.db) {
164+
if seen_names.insert(field.name(ctx.db)) && (ty.is_fn() || ty.is_closure()) {
165+
named_field(acc, field, ty);
166+
}
167+
}
168+
for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
169+
// Tuples are always the last type in a deref chain, so just check if the name is
170+
// already seen without inserting into the hashset.
171+
if !seen_names.contains(&hir::Name::new_tuple_field(i))
172+
&& (ty.is_fn() || ty.is_closure())
173+
{
174+
// Tuple fields are always public (tuple struct fields are handled above).
175+
tuple_index(acc, i, ty);
176+
}
177+
}
178+
}
179+
}
180+
147181
#[cfg(test)]
148182
mod tests {
149183
use expect_test::{expect, Expect};

crates/ide-completion/src/render.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use ide_db::{
1818
RootDatabase, SnippetCap, SymbolKind,
1919
};
2020
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
21+
use text_edit::TextEdit;
2122

2223
use crate::{
2324
context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
@@ -147,7 +148,23 @@ pub(crate) fn render_field(
147148
.set_documentation(field.docs(db))
148149
.set_deprecated(is_deprecated)
149150
.lookup_by(name);
150-
item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
151+
if ty.is_fn() || ty.is_closure() {
152+
let mut builder = TextEdit::builder();
153+
// Use TextEdit to insert / replace the ranges:
154+
// 1. Insert one character ('(') before start of struct name
155+
// 2. Insert one character (')') before call parens
156+
// 3. Variable character of the actual field name
157+
// 4. Optionally, two character ('()') for fn call
158+
//
159+
// TODO: Find a way to get the above ranges, especially the first two
160+
builder.replace(
161+
ctx.source_range(),
162+
field_with_receiver(db, receiver.as_ref(), &escaped_name).into(),
163+
);
164+
item.text_edit(builder.finish());
165+
} else {
166+
item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
167+
}
151168
if let Some(receiver) = &dot_access.receiver {
152169
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
153170
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {

0 commit comments

Comments
 (0)