Skip to content

Commit ebbcf58

Browse files
committed
Auto merge of #15374 - jmintb:extern_crate, r=Veykril
feat: Implement extern crate completion Hi, this is a draft PR for #13002. I have basic completion working as well as a filter for existing extern crate imports in the same file. This is based on the tests, I have not actually tried this in an editor. Before going further I think this is a good point to stop and get feedback on the structure and approach I have taken so far. Let me know what you think :) I will make sure to add more tests, rebase commits and align with the code style guidelines before submitting a final version. A few specific questions : 1. Is there a better way to check for matching suggestions? right now I just test if an extern crate name starts with the current user input. 2. Am I creating the `CompletionItem` correctly? I noticed that `use_.rs` invokes a builder where as I do not. 3. When checking for existing extern crate imports the current implementation only looks at the current source file, is that sufficient?
2 parents 2fbe69d + d070aea commit ebbcf58

File tree

6 files changed

+105
-0
lines changed

6 files changed

+105
-0
lines changed

crates/hir-def/src/resolver.rs

+19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use triomphe::Arc;
1212
use crate::{
1313
body::scope::{ExprScopes, ScopeId},
1414
builtin_type::BuiltinType,
15+
data::ExternCrateDeclData,
1516
db::DefDatabase,
1617
generics::{GenericParams, TypeOrConstParamData},
1718
hir::{BindingId, ExprId, LabelId},
@@ -428,6 +429,7 @@ impl Resolver {
428429
def_map[module_id].scope.entries().for_each(|(name, def)| {
429430
res.add_per_ns(name, def);
430431
});
432+
431433
def_map[module_id].scope.legacy_macros().for_each(|(name, macs)| {
432434
macs.iter().for_each(|&mac| {
433435
res.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac)));
@@ -451,6 +453,23 @@ impl Resolver {
451453
res.map
452454
}
453455

456+
pub fn extern_crate_decls_in_scope<'a>(
457+
&'a self,
458+
db: &'a dyn DefDatabase,
459+
) -> impl Iterator<Item = Name> + 'a {
460+
self.module_scope.def_map[self.module_scope.module_id]
461+
.scope
462+
.extern_crate_decls()
463+
.map(|id| ExternCrateDeclData::extern_crate_decl_data_query(db, id).name.clone())
464+
}
465+
466+
pub fn extern_crates_in_scope<'a>(&'a self) -> impl Iterator<Item = (Name, ModuleId)> + 'a {
467+
self.module_scope
468+
.def_map
469+
.extern_prelude()
470+
.map(|(name, module_id)| (name.clone(), module_id))
471+
}
472+
454473
pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
455474
// FIXME(trait_alias): Trait alias brings aliased traits in scope! Note that supertraits of
456475
// aliased traits are NOT brought in scope (unless also aliased).

crates/hir/src/semantics.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,14 @@ impl SemanticsScope<'_> {
16831683
|name, id| cb(name, id.into()),
16841684
)
16851685
}
1686+
1687+
pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ {
1688+
self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id }))
1689+
}
1690+
1691+
pub fn extern_crate_decls(&self) -> impl Iterator<Item = Name> + '_ {
1692+
self.resolver.extern_crate_decls_in_scope(self.db.upcast())
1693+
}
16861694
}
16871695

16881696
#[derive(Debug)]

crates/ide-completion/src/completions.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub(crate) mod r#type;
2020
pub(crate) mod use_;
2121
pub(crate) mod vis;
2222
pub(crate) mod env_vars;
23+
pub(crate) mod extern_crate;
2324

2425
use std::iter;
2526

@@ -737,6 +738,7 @@ pub(super) fn complete_name_ref(
737738
}
738739
}
739740
}
741+
NameRefKind::ExternCrate => extern_crate::complete_extern_crate(acc, ctx),
740742
NameRefKind::DotAccess(dot_access) => {
741743
flyimport::import_on_the_fly_dot(acc, ctx, dot_access);
742744
dot::complete_dot(acc, ctx, dot_access);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//! Completion for extern crates
2+
3+
use hir::{HasAttrs, Name};
4+
use ide_db::SymbolKind;
5+
6+
use crate::{context::CompletionContext, CompletionItem, CompletionItemKind};
7+
8+
use super::Completions;
9+
10+
pub(crate) fn complete_extern_crate(acc: &mut Completions, ctx: &CompletionContext<'_>) {
11+
let imported_extern_crates: Vec<Name> = ctx.scope.extern_crate_decls().collect();
12+
13+
for (name, module) in ctx.scope.extern_crates() {
14+
if imported_extern_crates.contains(&name) {
15+
continue;
16+
}
17+
18+
let mut item = CompletionItem::new(
19+
CompletionItemKind::SymbolKind(SymbolKind::Module),
20+
ctx.source_range(),
21+
name.to_smol_str(),
22+
);
23+
item.set_documentation(module.docs(ctx.db));
24+
25+
item.add_to(acc, ctx.db);
26+
}
27+
}
28+
29+
#[cfg(test)]
30+
mod test {
31+
use crate::tests::completion_list_no_kw;
32+
33+
#[test]
34+
fn can_complete_extern_crate() {
35+
let case = r#"
36+
//- /lib.rs crate:other_crate_a
37+
// nothing here
38+
//- /other_crate_b.rs crate:other_crate_b
39+
pub mod good_mod{}
40+
//- /lib.rs crate:crate_c
41+
// nothing here
42+
//- /lib.rs crate:lib deps:other_crate_a,other_crate_b,crate_c extern-prelude:other_crate_a
43+
extern crate oth$0
44+
mod other_mod {}
45+
"#;
46+
47+
let completion_list = completion_list_no_kw(case);
48+
49+
assert_eq!("md other_crate_a\n".to_string(), completion_list);
50+
}
51+
52+
#[test]
53+
fn will_not_complete_existing_import() {
54+
let case = r#"
55+
//- /lib.rs crate:other_crate_a
56+
// nothing here
57+
//- /lib.rs crate:crate_c
58+
// nothing here
59+
//- /lib.rs crate:other_crate_b
60+
//
61+
//- /lib.rs crate:lib deps:other_crate_a,other_crate_b,crate_c extern-prelude:other_crate_a,other_crate_b
62+
extern crate other_crate_b;
63+
extern crate oth$0
64+
mod other_mod {}
65+
"#;
66+
67+
let completion_list = completion_list_no_kw(case);
68+
69+
assert_eq!("md other_crate_a\n".to_string(), completion_list);
70+
}
71+
}

crates/ide-completion/src/context.rs

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ pub(super) enum NameRefKind {
301301
expr: ast::RecordExpr,
302302
},
303303
Pattern(PatternContext),
304+
ExternCrate,
304305
}
305306

306307
/// The identifier we are currently completing.

crates/ide-completion/src/context/analysis.rs

+4
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,10 @@ fn classify_name_ref(
624624
});
625625
return Some(make_res(kind));
626626
},
627+
ast::ExternCrate(_) => {
628+
let kind = NameRefKind::ExternCrate;
629+
return Some(make_res(kind));
630+
},
627631
ast::MethodCallExpr(method) => {
628632
let receiver = find_opt_node_in_file(original_file, method.receiver());
629633
let kind = NameRefKind::DotAccess(DotAccess {

0 commit comments

Comments
 (0)