Skip to content

Commit 767bff8

Browse files
Complete standard derives
1 parent c4b32d1 commit 767bff8

File tree

2 files changed

+245
-16
lines changed

2 files changed

+245
-16
lines changed

crates/ra_ide/src/completion/complete_attribute.rs

Lines changed: 242 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,26 @@
55
66
use super::completion_context::CompletionContext;
77
use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
8+
use ast::AttrInput;
89
use ra_syntax::{
9-
ast::{Attr, AttrKind},
10-
AstNode,
10+
ast::{self, AttrKind},
11+
AstNode, SyntaxKind,
1112
};
13+
use rustc_hash::FxHashSet;
1214

13-
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
14-
if !ctx.is_attribute {
15-
return;
16-
}
15+
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16+
let attribute = ctx.attribute_under_caret.as_ref()?;
1717

18-
let is_inner = ctx
19-
.original_token
20-
.ancestors()
21-
.find_map(Attr::cast)
22-
.map(|attr| attr.kind() == AttrKind::Inner)
23-
.unwrap_or(false);
18+
match (attribute.path(), attribute.input()) {
19+
(Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => {
20+
complete_derive(acc, ctx, token_tree)
21+
}
22+
_ => complete_attribute_start(acc, ctx, attribute),
23+
}
24+
Some(())
25+
}
2426

27+
fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
2528
for attr_completion in ATTRIBUTES {
2629
let mut item = CompletionItem::new(
2730
CompletionKind::Attribute,
@@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
3740
_ => {}
3841
}
3942

40-
if is_inner || !attr_completion.should_be_inner {
43+
if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner {
4144
acc.add(item);
4245
}
4346
}
@@ -126,6 +129,68 @@ const ATTRIBUTES: &[AttrCompletion] = &[
126129
},
127130
];
128131

132+
fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
133+
// TODO kb autodetect derive macros
134+
// https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Find.20all.20possible.20derive.20macro.20values.3F/near/195955580
135+
136+
if let Ok(existing_derives) = parse_derive_input(derive_input) {
137+
for derive_completion in DERIVE_COMPLETIONS
138+
.into_iter()
139+
.filter(|completion| !existing_derives.contains(completion.label))
140+
{
141+
let mut label = derive_completion.label.to_owned();
142+
for dependency in derive_completion
143+
.dependencies
144+
.into_iter()
145+
.filter(|&&dependency| !existing_derives.contains(dependency))
146+
{
147+
label.push_str(", ");
148+
label.push_str(dependency);
149+
}
150+
let item = CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
151+
.kind(CompletionItemKind::Attribute);
152+
acc.add(item);
153+
}
154+
}
155+
}
156+
157+
fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
158+
match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
159+
(Some(left_paren), Some(right_paren))
160+
if left_paren.kind() == SyntaxKind::L_PAREN
161+
&& right_paren.kind() == SyntaxKind::R_PAREN =>
162+
{
163+
Ok(derive_input
164+
.syntax()
165+
.children_with_tokens()
166+
.filter_map(|child| child.into_token())
167+
.skip_while(|child| child != &left_paren)
168+
.take_while(|child| child != &right_paren)
169+
.filter(|child| child.kind() == SyntaxKind::IDENT)
170+
.map(|child| child.to_string())
171+
.collect())
172+
}
173+
_ => Err(()),
174+
}
175+
}
176+
177+
struct DeriveCompletion {
178+
label: &'static str,
179+
dependencies: &'static [&'static str],
180+
}
181+
182+
const DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
183+
DeriveCompletion { label: "Clone", dependencies: &[] },
184+
DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
185+
DeriveCompletion { label: "Debug", dependencies: &[] },
186+
DeriveCompletion { label: "Default", dependencies: &[] },
187+
DeriveCompletion { label: "Hash", dependencies: &[] },
188+
DeriveCompletion { label: "PartialEq", dependencies: &[] },
189+
DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
190+
DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
191+
DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
192+
];
193+
129194
#[cfg(test)]
130195
mod tests {
131196
use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
@@ -135,6 +200,170 @@ mod tests {
135200
do_completion(code, CompletionKind::Attribute)
136201
}
137202

203+
#[test]
204+
fn empty_derive_completion() {
205+
assert_debug_snapshot!(
206+
do_attr_completion(
207+
r"
208+
#[derive(<|>)]
209+
struct Test {}
210+
",
211+
),
212+
@r###"
213+
[
214+
CompletionItem {
215+
label: "Clone",
216+
source_range: 30..30,
217+
delete: 30..30,
218+
insert: "Clone",
219+
kind: Attribute,
220+
},
221+
CompletionItem {
222+
label: "Copy, Clone",
223+
source_range: 30..30,
224+
delete: 30..30,
225+
insert: "Copy, Clone",
226+
kind: Attribute,
227+
},
228+
CompletionItem {
229+
label: "Debug",
230+
source_range: 30..30,
231+
delete: 30..30,
232+
insert: "Debug",
233+
kind: Attribute,
234+
},
235+
CompletionItem {
236+
label: "Default",
237+
source_range: 30..30,
238+
delete: 30..30,
239+
insert: "Default",
240+
kind: Attribute,
241+
},
242+
CompletionItem {
243+
label: "Eq, PartialEq",
244+
source_range: 30..30,
245+
delete: 30..30,
246+
insert: "Eq, PartialEq",
247+
kind: Attribute,
248+
},
249+
CompletionItem {
250+
label: "Hash",
251+
source_range: 30..30,
252+
delete: 30..30,
253+
insert: "Hash",
254+
kind: Attribute,
255+
},
256+
CompletionItem {
257+
label: "Ord, PartialOrd, Eq, PartialEq",
258+
source_range: 30..30,
259+
delete: 30..30,
260+
insert: "Ord, PartialOrd, Eq, PartialEq",
261+
kind: Attribute,
262+
},
263+
CompletionItem {
264+
label: "PartialEq",
265+
source_range: 30..30,
266+
delete: 30..30,
267+
insert: "PartialEq",
268+
kind: Attribute,
269+
},
270+
CompletionItem {
271+
label: "PartialOrd, PartialEq",
272+
source_range: 30..30,
273+
delete: 30..30,
274+
insert: "PartialOrd, PartialEq",
275+
kind: Attribute,
276+
},
277+
]
278+
"###
279+
);
280+
}
281+
282+
#[test]
283+
fn no_completion_for_incorrect_derive() {
284+
assert_debug_snapshot!(
285+
do_attr_completion(
286+
r"
287+
#[derive{<|>)]
288+
struct Test {}
289+
",
290+
),
291+
@"[]"
292+
);
293+
}
294+
295+
#[test]
296+
fn derive_with_input_completion() {
297+
assert_debug_snapshot!(
298+
do_attr_completion(
299+
r"
300+
#[derive(Whatever, PartialEq, <|>)]
301+
struct Test {}
302+
",
303+
),
304+
@r###"
305+
[
306+
CompletionItem {
307+
label: "Clone",
308+
source_range: 51..51,
309+
delete: 51..51,
310+
insert: "Clone",
311+
kind: Attribute,
312+
},
313+
CompletionItem {
314+
label: "Copy, Clone",
315+
source_range: 51..51,
316+
delete: 51..51,
317+
insert: "Copy, Clone",
318+
kind: Attribute,
319+
},
320+
CompletionItem {
321+
label: "Debug",
322+
source_range: 51..51,
323+
delete: 51..51,
324+
insert: "Debug",
325+
kind: Attribute,
326+
},
327+
CompletionItem {
328+
label: "Default",
329+
source_range: 51..51,
330+
delete: 51..51,
331+
insert: "Default",
332+
kind: Attribute,
333+
},
334+
CompletionItem {
335+
label: "Eq",
336+
source_range: 51..51,
337+
delete: 51..51,
338+
insert: "Eq",
339+
kind: Attribute,
340+
},
341+
CompletionItem {
342+
label: "Hash",
343+
source_range: 51..51,
344+
delete: 51..51,
345+
insert: "Hash",
346+
kind: Attribute,
347+
},
348+
CompletionItem {
349+
label: "Ord, PartialOrd, Eq",
350+
source_range: 51..51,
351+
delete: 51..51,
352+
insert: "Ord, PartialOrd, Eq",
353+
kind: Attribute,
354+
},
355+
CompletionItem {
356+
label: "PartialOrd",
357+
source_range: 51..51,
358+
delete: 51..51,
359+
insert: "PartialOrd",
360+
kind: Attribute,
361+
},
362+
]
363+
"###
364+
);
365+
}
366+
138367
#[test]
139368
fn test_attribute_completion() {
140369
assert_debug_snapshot!(

crates/ra_ide/src/completion/completion_context.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> {
5858
pub(super) is_macro_call: bool,
5959
pub(super) is_path_type: bool,
6060
pub(super) has_type_args: bool,
61-
pub(super) is_attribute: bool,
61+
pub(super) attribute_under_caret: Option<ast::Attr>,
6262
}
6363

6464
impl<'a> CompletionContext<'a> {
@@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> {
116116
is_path_type: false,
117117
has_type_args: false,
118118
dot_receiver_is_ambiguous_float_literal: false,
119-
is_attribute: false,
119+
attribute_under_caret: None,
120120
};
121121

122122
let mut original_file = original_file.syntax().clone();
@@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> {
200200
Some(ty)
201201
})
202202
.flatten();
203+
self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
203204

204205
// First, let's try to complete a reference to some declaration.
205206
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
@@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> {
318319
.and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
319320
.is_some();
320321
self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
321-
self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some();
322322

323323
self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
324324
self.has_type_args = segment.type_arg_list().is_some();

0 commit comments

Comments
 (0)