]> git.lizzy.rs Git - rust.git/commitdiff
Complete standard derives
authorKirill Bulatov <mail4score@gmail.com>
Fri, 1 May 2020 00:46:17 +0000 (03:46 +0300)
committerKirill Bulatov <mail4score@gmail.com>
Sat, 2 May 2020 18:41:02 +0000 (21:41 +0300)
crates/ra_ide/src/completion/complete_attribute.rs
crates/ra_ide/src/completion/completion_context.rs

index 8bf9527980e2ef67552373faeb346c3bcd53bd40..8934b45fe5d13e909bcbbb45b5e09257fc33f4bc 100644 (file)
@@ -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 @@ struct 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<FxHashSet<String>, ()> {
+    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 @@ fn do_attr_completion(code: &str) -> Vec<CompletionItem> {
         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!(
index c529752d42d9ad22955dafd7e142ed1ed2ee7a08..dd87bd119e1973317e53ff2d57046767a544c2a1 100644 (file)
@@ -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<ast::Attr>,
 }
 
 impl<'a> CompletionContext<'a> {
@@ -116,7 +116,7 @@ pub(super) fn new(
             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 @@ fn fill(
                 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::<ast::NameRef>(&file_with_fake_ident, offset) {
@@ -318,7 +319,6 @@ fn classify_name_ref(
                 .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();