From 767bff89ededdff875b042bb37397b972e3a82f1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 1 May 2020 03:46:17 +0300 Subject: Complete standard derives --- crates/ra_ide/src/completion/complete_attribute.rs | 255 +++++++++++++++++++-- crates/ra_ide/src/completion/completion_context.rs | 6 +- 2 files changed, 245 insertions(+), 16 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf952798..8934b45fe 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs @@ -5,23 +5,26 @@ use super::completion_context::CompletionContext; use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; +use ast::AttrInput; use ra_syntax::{ - ast::{Attr, AttrKind}, - AstNode, + ast::{self, AttrKind}, + AstNode, SyntaxKind, }; +use rustc_hash::FxHashSet; -pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_attribute { - return; - } +pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let attribute = ctx.attribute_under_caret.as_ref()?; - let is_inner = ctx - .original_token - .ancestors() - .find_map(Attr::cast) - .map(|attr| attr.kind() == AttrKind::Inner) - .unwrap_or(false); + match (attribute.path(), attribute.input()) { + (Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => { + complete_derive(acc, ctx, token_tree) + } + _ => complete_attribute_start(acc, ctx, attribute), + } + Some(()) +} +fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { for attr_completion in ATTRIBUTES { let mut item = CompletionItem::new( CompletionKind::Attribute, @@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) _ => {} } - if is_inner || !attr_completion.should_be_inner { + if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner { acc.add(item); } } @@ -126,6 +129,68 @@ const ATTRIBUTES: &[AttrCompletion] = &[ }, ]; +fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { + // TODO kb autodetect derive macros + // https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Find.20all.20possible.20derive.20macro.20values.3F/near/195955580 + + if let Ok(existing_derives) = parse_derive_input(derive_input) { + for derive_completion in DERIVE_COMPLETIONS + .into_iter() + .filter(|completion| !existing_derives.contains(completion.label)) + { + let mut label = derive_completion.label.to_owned(); + for dependency in derive_completion + .dependencies + .into_iter() + .filter(|&&dependency| !existing_derives.contains(dependency)) + { + label.push_str(", "); + label.push_str(dependency); + } + let item = CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) + .kind(CompletionItemKind::Attribute); + acc.add(item); + } + } +} + +fn parse_derive_input(derive_input: ast::TokenTree) -> Result, ()> { + match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { + (Some(left_paren), Some(right_paren)) + if left_paren.kind() == SyntaxKind::L_PAREN + && right_paren.kind() == SyntaxKind::R_PAREN => + { + Ok(derive_input + .syntax() + .children_with_tokens() + .filter_map(|child| child.into_token()) + .skip_while(|child| child != &left_paren) + .take_while(|child| child != &right_paren) + .filter(|child| child.kind() == SyntaxKind::IDENT) + .map(|child| child.to_string()) + .collect()) + } + _ => Err(()), + } +} + +struct DeriveCompletion { + label: &'static str, + dependencies: &'static [&'static str], +} + +const DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ + DeriveCompletion { label: "Clone", dependencies: &[] }, + DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, + DeriveCompletion { label: "Debug", dependencies: &[] }, + DeriveCompletion { label: "Default", dependencies: &[] }, + DeriveCompletion { label: "Hash", dependencies: &[] }, + DeriveCompletion { label: "PartialEq", dependencies: &[] }, + DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, + DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, + DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, +]; + #[cfg(test)] mod tests { use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; @@ -135,6 +200,170 @@ mod tests { do_completion(code, CompletionKind::Attribute) } + #[test] + fn empty_derive_completion() { + assert_debug_snapshot!( + do_attr_completion( + r" + #[derive(<|>)] + struct Test {} + ", + ), + @r###" + [ + CompletionItem { + label: "Clone", + source_range: 30..30, + delete: 30..30, + insert: "Clone", + kind: Attribute, + }, + CompletionItem { + label: "Copy, Clone", + source_range: 30..30, + delete: 30..30, + insert: "Copy, Clone", + kind: Attribute, + }, + CompletionItem { + label: "Debug", + source_range: 30..30, + delete: 30..30, + insert: "Debug", + kind: Attribute, + }, + CompletionItem { + label: "Default", + source_range: 30..30, + delete: 30..30, + insert: "Default", + kind: Attribute, + }, + CompletionItem { + label: "Eq, PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "Eq, PartialEq", + kind: Attribute, + }, + CompletionItem { + label: "Hash", + source_range: 30..30, + delete: 30..30, + insert: "Hash", + kind: Attribute, + }, + CompletionItem { + label: "Ord, PartialOrd, Eq, PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "Ord, PartialOrd, Eq, PartialEq", + kind: Attribute, + }, + CompletionItem { + label: "PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "PartialEq", + kind: Attribute, + }, + CompletionItem { + label: "PartialOrd, PartialEq", + source_range: 30..30, + delete: 30..30, + insert: "PartialOrd, PartialEq", + kind: Attribute, + }, + ] + "### + ); + } + + #[test] + fn no_completion_for_incorrect_derive() { + assert_debug_snapshot!( + do_attr_completion( + r" + #[derive{<|>)] + struct Test {} + ", + ), + @"[]" + ); + } + + #[test] + fn derive_with_input_completion() { + assert_debug_snapshot!( + do_attr_completion( + r" + #[derive(Whatever, PartialEq, <|>)] + struct Test {} + ", + ), + @r###" + [ + CompletionItem { + label: "Clone", + source_range: 51..51, + delete: 51..51, + insert: "Clone", + kind: Attribute, + }, + CompletionItem { + label: "Copy, Clone", + source_range: 51..51, + delete: 51..51, + insert: "Copy, Clone", + kind: Attribute, + }, + CompletionItem { + label: "Debug", + source_range: 51..51, + delete: 51..51, + insert: "Debug", + kind: Attribute, + }, + CompletionItem { + label: "Default", + source_range: 51..51, + delete: 51..51, + insert: "Default", + kind: Attribute, + }, + CompletionItem { + label: "Eq", + source_range: 51..51, + delete: 51..51, + insert: "Eq", + kind: Attribute, + }, + CompletionItem { + label: "Hash", + source_range: 51..51, + delete: 51..51, + insert: "Hash", + kind: Attribute, + }, + CompletionItem { + label: "Ord, PartialOrd, Eq", + source_range: 51..51, + delete: 51..51, + insert: "Ord, PartialOrd, Eq", + kind: Attribute, + }, + CompletionItem { + label: "PartialOrd", + source_range: 51..51, + delete: 51..51, + insert: "PartialOrd", + kind: Attribute, + }, + ] + "### + ); + } + #[test] fn test_attribute_completion() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c529752d4..dd87bd119 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_macro_call: bool, pub(super) is_path_type: bool, pub(super) has_type_args: bool, - pub(super) is_attribute: bool, + pub(super) attribute_under_caret: Option, } impl<'a> CompletionContext<'a> { @@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> { is_path_type: false, has_type_args: false, dot_receiver_is_ambiguous_float_literal: false, - is_attribute: false, + attribute_under_caret: None, }; let mut original_file = original_file.syntax().clone(); @@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> { Some(ty) }) .flatten(); + self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); // First, let's try to complete a reference to some declaration. if let Some(name_ref) = find_node_at_offset::(&file_with_fake_ident, offset) { @@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> { .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) .is_some(); self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); - self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some(); self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); self.has_type_args = segment.type_arg_list().is_some(); -- cgit v1.2.3 From fee74851b02fb69c32291f3051438901faaf667f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 2 May 2020 22:24:27 +0300 Subject: Propose custom derives in completion --- crates/ra_ide/src/completion/complete_attribute.rs | 40 +++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8934b45fe..346ba9e7a 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs @@ -130,11 +130,8 @@ const ATTRIBUTES: &[AttrCompletion] = &[ ]; fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { - // TODO kb autodetect derive macros - // https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Find.20all.20possible.20derive.20macro.20values.3F/near/195955580 - if let Ok(existing_derives) = parse_derive_input(derive_input) { - for derive_completion in DERIVE_COMPLETIONS + for derive_completion in DEFAULT_DERIVE_COMPLETIONS .into_iter() .filter(|completion| !existing_derives.contains(completion.label)) { @@ -147,9 +144,21 @@ fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: label.push_str(", "); label.push_str(dependency); } - let item = CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) - .kind(CompletionItemKind::Attribute); - acc.add(item); + acc.add( + CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) + .kind(CompletionItemKind::Attribute), + ); + } + + for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { + acc.add( + CompletionItem::new( + CompletionKind::Attribute, + ctx.source_range(), + custom_derive_name, + ) + .kind(CompletionItemKind::Attribute), + ); } } } @@ -174,12 +183,27 @@ fn parse_derive_input(derive_input: ast::TokenTree) -> Result, } } +fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet { + let mut result = FxHashSet::default(); + ctx.scope().process_all_names(&mut |name, scope_def| { + if let hir::ScopeDef::MacroDef(mac) = scope_def { + if mac.is_derive_macro() { + let name_string = name.to_string(); + result.insert(name_string); + } + } + }); + result +} + struct DeriveCompletion { label: &'static str, dependencies: &'static [&'static str], } -const DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ +/// Standard Rust derives and the information about their dependencies +/// (the dependencies are needed so that the main derive don't break the compilation when added) +const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ DeriveCompletion { label: "Clone", dependencies: &[] }, DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, DeriveCompletion { label: "Debug", dependencies: &[] }, -- cgit v1.2.3 From 9f0ed7ed92a978da73d19fe9f23b3953ca0a1de4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 2 May 2020 22:33:23 +0300 Subject: Separate macros completion from other --- crates/ra_ide/src/completion.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 4ca0fdf4f..a0e06faa2 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -65,21 +65,23 @@ pub(crate) fn completions( let ctx = CompletionContext::new(db, position, config)?; let mut acc = Completions::default(); - - complete_fn_param::complete_fn_param(&mut acc, &ctx); - complete_keyword::complete_expr_keyword(&mut acc, &ctx); - complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); - complete_snippet::complete_expr_snippet(&mut acc, &ctx); - complete_snippet::complete_item_snippet(&mut acc, &ctx); - complete_qualified_path::complete_qualified_path(&mut acc, &ctx); - complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); - complete_dot::complete_dot(&mut acc, &ctx); - complete_record::complete_record(&mut acc, &ctx); - complete_pattern::complete_pattern(&mut acc, &ctx); - complete_postfix::complete_postfix(&mut acc, &ctx); - complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); - complete_trait_impl::complete_trait_impl(&mut acc, &ctx); - complete_attribute::complete_attribute(&mut acc, &ctx); + if ctx.attribute_under_caret.is_some() { + complete_attribute::complete_attribute(&mut acc, &ctx); + } else { + complete_fn_param::complete_fn_param(&mut acc, &ctx); + complete_keyword::complete_expr_keyword(&mut acc, &ctx); + complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); + complete_snippet::complete_expr_snippet(&mut acc, &ctx); + complete_snippet::complete_item_snippet(&mut acc, &ctx); + complete_qualified_path::complete_qualified_path(&mut acc, &ctx); + complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); + complete_dot::complete_dot(&mut acc, &ctx); + complete_record::complete_record(&mut acc, &ctx); + complete_pattern::complete_pattern(&mut acc, &ctx); + complete_postfix::complete_postfix(&mut acc, &ctx); + complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); + complete_trait_impl::complete_trait_impl(&mut acc, &ctx); + } Some(acc) } -- cgit v1.2.3 From 2fd054f276e6fd75237b476622d03eef2f18430a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 2 May 2020 23:45:44 +0300 Subject: Fix derive argument parsing --- crates/ra_ide/src/completion/complete_attribute.rs | 66 +++++++++++++--------- 1 file changed, 40 insertions(+), 26 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 346ba9e7a..20e6edc17 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs @@ -169,15 +169,30 @@ fn parse_derive_input(derive_input: ast::TokenTree) -> Result, if left_paren.kind() == SyntaxKind::L_PAREN && right_paren.kind() == SyntaxKind::R_PAREN => { - Ok(derive_input + let mut input_derives = FxHashSet::default(); + let mut current_derive = String::new(); + for token in derive_input .syntax() .children_with_tokens() - .filter_map(|child| child.into_token()) - .skip_while(|child| child != &left_paren) - .take_while(|child| child != &right_paren) - .filter(|child| child.kind() == SyntaxKind::IDENT) - .map(|child| child.to_string()) - .collect()) + .filter_map(|token| token.into_token()) + .skip_while(|token| token != &left_paren) + .skip(1) + .take_while(|token| token != &right_paren) + { + if SyntaxKind::COMMA == token.kind() { + if !current_derive.is_empty() { + input_derives.insert(current_derive); + current_derive = String::new(); + } + } else { + current_derive.push_str(token.to_string().trim()); + } + } + + if !current_derive.is_empty() { + input_derives.insert(current_derive); + } + Ok(input_derives) } _ => Err(()), } @@ -188,8 +203,7 @@ fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet { ctx.scope().process_all_names(&mut |name, scope_def| { if let hir::ScopeDef::MacroDef(mac) = scope_def { if mac.is_derive_macro() { - let name_string = name.to_string(); - result.insert(name_string); + result.insert(name.to_string()); } } }); @@ -321,7 +335,7 @@ mod tests { assert_debug_snapshot!( do_attr_completion( r" - #[derive(Whatever, PartialEq, <|>)] + #[derive(serde::Serialize, PartialEq, <|>)] struct Test {} ", ), @@ -329,57 +343,57 @@ mod tests { [ CompletionItem { label: "Clone", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Clone", kind: Attribute, }, CompletionItem { label: "Copy, Clone", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Copy, Clone", kind: Attribute, }, CompletionItem { label: "Debug", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Debug", kind: Attribute, }, CompletionItem { label: "Default", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Default", kind: Attribute, }, CompletionItem { label: "Eq", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Eq", kind: Attribute, }, CompletionItem { label: "Hash", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Hash", kind: Attribute, }, CompletionItem { label: "Ord, PartialOrd, Eq", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "Ord, PartialOrd, Eq", kind: Attribute, }, CompletionItem { label: "PartialOrd", - source_range: 51..51, - delete: 51..51, + source_range: 59..59, + delete: 59..59, insert: "PartialOrd", kind: Attribute, }, -- cgit v1.2.3