From 06076f95a7ca764696b055eb754e163f884eefaa Mon Sep 17 00:00:00 2001 From: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Date: Wed, 15 Apr 2020 22:10:57 +0200 Subject: feat: improve dot completions in a struct literal expression Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> --- crates/ra_ide/src/completion.rs | 2 +- crates/ra_ide/src/completion/complete_dot.rs | 141 ++++++++++++++++++++- crates/ra_ide/src/completion/completion_context.rs | 10 ++ crates/ra_ide/src/completion/completion_item.rs | 74 ++++++----- 4 files changed, 196 insertions(+), 31 deletions(-) diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 3be8b1903..185450508 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -95,7 +95,7 @@ pub(crate) fn completions( complete_trait_impl::complete_trait_impl(&mut acc, &ctx); // Reorder completion items if there is a sort_option - acc.sort(); + acc.sort(&ctx); Some(acc) } diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs index cb899d8ff..2e228b638 100644 --- a/crates/ra_ide/src/completion/complete_dot.rs +++ b/crates/ra_ide/src/completion/complete_dot.rs @@ -50,7 +50,9 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty let fields = receiver.fields(ctx.db); // If we use this implementation we can delete call_info in the CompletionContext - if let Some(call_info) = call_info(ctx.db, ctx.file_position) { + if let Some(record_field) = &ctx.record_field_syntax { + acc.with_sort_option(SortOption::RecordField(record_field.clone())); + } else if let Some(call_info) = call_info(ctx.db, ctx.file_position) { acc.with_sort_option(SortOption::CallFn(call_info)); } @@ -240,6 +242,143 @@ mod tests { ); } + #[test] + fn test_struct_field_completion_in_record_lit() { + assert_debug_snapshot!( + do_ref_completion_without_sort( + r" + struct A { another_field: i64, another_good_type: u32, the_field: u32 } + struct B { my_string: String, my_vec: Vec, the_field: u32 } + fn foo(a: A) { + let b = B { + the_field: a.<|> + }; + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_field", + source_range: [270; 270), + delete: [270; 270), + insert: "the_field", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_good_type", + source_range: [270; 270), + delete: [270; 270), + insert: "another_good_type", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_field", + source_range: [270; 270), + delete: [270; 270), + insert: "another_field", + kind: Field, + detail: "i64", + }, + ] + "### + ); + } + + #[test] + fn test_struct_field_completion_in_record_lit_and_fn_call() { + assert_debug_snapshot!( + do_ref_completion_without_sort( + r" + struct A { another_field: i64, another_good_type: u32, the_field: u32 } + struct B { my_string: String, my_vec: Vec, the_field: u32 } + fn test(the_field: i64) -> i64 { the_field } + fn foo(a: A) { + let b = B { + the_field: test(a.<|>) + }; + } + ", + ), + @r###" + [ + CompletionItem { + label: "another_field", + source_range: [336; 336), + delete: [336; 336), + insert: "another_field", + kind: Field, + detail: "i64", + }, + CompletionItem { + label: "another_good_type", + source_range: [336; 336), + delete: [336; 336), + insert: "another_good_type", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "the_field", + source_range: [336; 336), + delete: [336; 336), + insert: "the_field", + kind: Field, + detail: "u32", + }, + ] + "### + ); + } + + #[test] + fn test_struct_field_completion_in_fn_call_and_record_lit() { + assert_debug_snapshot!( + do_ref_completion_without_sort( + r" + struct A { another_field: i64, another_good_type: u32, the_field: u32 } + struct B { my_string: String, my_vec: Vec, the_field: u32 } + fn test(the_field: i64) -> i64 { the_field } + fn foo(a: A) { + test(B { + the_field: a.<|> + }); + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_field", + source_range: [328; 328), + delete: [328; 328), + insert: "the_field", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_good_type", + source_range: [328; 328), + delete: [328; 328), + insert: "another_good_type", + kind: Field, + detail: "u32", + }, + CompletionItem { + label: "another_field", + source_range: [328; 328), + delete: [328; 328), + insert: "another_field", + kind: Field, + detail: "i64", + }, + ] + "### + ); + } + #[test] fn test_struct_field_completion_self() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index da054f7a2..dd9cc7daa 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -32,6 +32,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) use_item_syntax: Option, pub(super) record_lit_syntax: Option, pub(super) record_pat_syntax: Option, + pub(super) record_field_syntax: Option, pub(super) impl_def: Option, pub(super) call_info: Option, pub(super) is_param: bool, @@ -98,6 +99,7 @@ impl<'a> CompletionContext<'a> { use_item_syntax: None, record_lit_syntax: None, record_pat_syntax: None, + record_field_syntax: None, impl_def: None, is_param: false, is_pat_binding_or_const: false, @@ -274,6 +276,14 @@ impl<'a> CompletionContext<'a> { .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) .find_map(ast::FnDef::cast); + self.record_field_syntax = self + .sema + .ancestors_with_macros(self.token.parent()) + .take_while(|it| { + it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR + }) + .find_map(ast::RecordField::cast); + let parent = match name_ref.syntax().parent() { Some(it) => it, None => return, diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index f8e6e53f1..c9c3fdc0e 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs @@ -2,9 +2,10 @@ use std::{cmp::Ordering, fmt}; +use super::completion_context::CompletionContext; use crate::CallInfo; -use hir::Documentation; -use ra_syntax::TextRange; +use hir::{Documentation, HirDisplay}; +use ra_syntax::{ast::RecordField, TextRange}; use ra_text_edit::TextEdit; /// `CompletionItem` describes a single completion variant in the editor pop-up. @@ -301,7 +302,7 @@ impl<'a> Into for Builder { #[derive(Debug)] pub(crate) enum SortOption { CallFn(CallInfo), - // LitStruct, + RecordField(RecordField), } /// Represents an in-progress set of completions being built. @@ -327,40 +328,55 @@ impl Completions { self.sort_option = Some(sort_option); } - pub(crate) fn sort(&mut self) { + pub(crate) fn sort(&mut self, ctx: &CompletionContext) { if self.sort_option.is_none() { return; } - let sort_option = self.sort_option.as_ref().unwrap(); - match sort_option { + let (active_name, active_type) = match self.sort_option.as_ref().unwrap() { SortOption::CallFn(call_info) => { - if let Some(active_parameter_type) = call_info.active_parameter_type() { - let active_parameter_name = call_info.active_parameter_name().unwrap(); - - self.buf.sort_by(|a, b| { - // For the same type - if let Some(a_parameter_type) = &a.detail { - if &active_parameter_type == a_parameter_type { - // If same type + same name then go top position - if active_parameter_name != a.label { - if let Some(b_parameter_type) = &b.detail { - if &active_parameter_type == b_parameter_type { - return Ordering::Equal; - } - } - } - Ordering::Less - } else { - Ordering::Greater + if call_info.active_parameter_type().is_none() + || call_info.active_parameter_name().is_none() + { + return; + } + ( + call_info.active_parameter_name().unwrap(), + call_info.active_parameter_type().unwrap(), + ) + } + SortOption::RecordField(record_field) => { + if let Some((struct_field, _)) = ctx.sema.resolve_record_field(record_field) { + ( + struct_field.name(ctx.db).to_string(), + struct_field.signature_ty(ctx.db).display(ctx.db).to_string(), + ) + } else { + return; + } + } + }; + + self.buf.sort_by(|a, b| { + // For the same type + if let Some(a_parameter_type) = &a.detail { + if &active_type == a_parameter_type { + // If same type + same name then go top position + if active_name != a.label { + if let Some(b_parameter_type) = &b.detail { + if &active_type == b_parameter_type { + return Ordering::Equal; } - } else { - Ordering::Greater } - }); + } + Ordering::Less + } else { + Ordering::Greater } - } // _ => unimplemented!("sort options not already implemented"), - } + } else { + Ordering::Greater + } + }); } } -- cgit v1.2.3