mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 20:42:04 +00:00
scope-based copmletions on original file
This commit is contained in:
parent
2136e75c0b
commit
ccca5aae43
5 changed files with 223 additions and 322 deletions
|
@ -1,10 +1,10 @@
|
|||
mod completion_item;
|
||||
mod reference_completion;
|
||||
|
||||
mod complete_fn_param;
|
||||
mod complete_keyword;
|
||||
mod complete_snippet;
|
||||
mod complete_path;
|
||||
mod complete_scope;
|
||||
|
||||
use ra_editor::find_node_at_offset;
|
||||
use ra_text_edit::AtomTextEdit;
|
||||
|
@ -33,26 +33,16 @@ pub(crate) fn completions(
|
|||
position: FilePosition,
|
||||
) -> Cancelable<Option<Completions>> {
|
||||
let original_file = db.source_file(position.file_id);
|
||||
// Insert a fake ident to get a valid parse tree
|
||||
let file = {
|
||||
let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
|
||||
original_file.reparse(&edit)
|
||||
};
|
||||
let module = ctry!(source_binder::module_from_position(db, position)?);
|
||||
let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?);
|
||||
|
||||
let mut acc = Completions::default();
|
||||
|
||||
// First, let's try to complete a reference to some declaration.
|
||||
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
|
||||
reference_completion::completions(&mut acc, db, &module, &file, name_ref)?;
|
||||
}
|
||||
|
||||
let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?);
|
||||
complete_fn_param::complete_fn_param(&mut acc, &ctx);
|
||||
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
|
||||
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
|
||||
complete_snippet::complete_item_snippet(&mut acc, &ctx);
|
||||
complete_path::complete_path(&mut acc, &ctx)?;
|
||||
complete_scope::complete_scope(&mut acc, &ctx)?;
|
||||
|
||||
Ok(Some(acc))
|
||||
}
|
||||
|
@ -62,6 +52,7 @@ pub(crate) fn completions(
|
|||
#[derive(Debug)]
|
||||
pub(super) struct SyntaxContext<'a> {
|
||||
db: &'a db::RootDatabase,
|
||||
offset: TextUnit,
|
||||
leaf: SyntaxNodeRef<'a>,
|
||||
module: Option<hir::Module>,
|
||||
enclosing_fn: Option<ast::FnDef<'a>>,
|
||||
|
@ -88,6 +79,7 @@ impl<'a> SyntaxContext<'a> {
|
|||
let mut ctx = SyntaxContext {
|
||||
db,
|
||||
leaf,
|
||||
offset: position.offset,
|
||||
module,
|
||||
enclosing_fn: None,
|
||||
is_param: false,
|
||||
|
|
|
@ -9,8 +9,8 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &SyntaxContext) -> Cance
|
|||
_ => return Ok(()),
|
||||
};
|
||||
let def_id = match module.resolve_path(ctx.db, path)? {
|
||||
None => return Ok(()),
|
||||
Some(it) => it,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let target_module = match def_id.resolve(ctx.db)? {
|
||||
hir::Def::Module(it) => it,
|
||||
|
|
171
crates/ra_analysis/src/completion/complete_scope.rs
Normal file
171
crates/ra_analysis/src/completion/complete_scope.rs
Normal file
|
@ -0,0 +1,171 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
use ra_syntax::TextUnit;
|
||||
|
||||
use crate::{
|
||||
completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext},
|
||||
Cancelable
|
||||
};
|
||||
|
||||
pub(super) fn complete_scope(acc: &mut Completions, ctx: &SyntaxContext) -> Cancelable<()> {
|
||||
if !ctx.is_trivial_path {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(fn_def) = ctx.enclosing_fn {
|
||||
let scopes = hir::FnScopes::new(fn_def);
|
||||
complete_fn(acc, &scopes, ctx.offset);
|
||||
}
|
||||
|
||||
if let Some(module) = &ctx.module {
|
||||
let module_scope = module.scope(ctx.db)?;
|
||||
module_scope
|
||||
.entries()
|
||||
.filter(|(_name, res)| {
|
||||
// Don't expose this item
|
||||
match res.import {
|
||||
None => true,
|
||||
Some(import) => {
|
||||
let range = import.range(ctx.db, module.source().file_id());
|
||||
!range.is_subrange(&ctx.leaf.range())
|
||||
}
|
||||
}
|
||||
})
|
||||
.for_each(|(name, _res)| {
|
||||
CompletionItem::new(name.to_string())
|
||||
.kind(Reference)
|
||||
.add_to(acc)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn complete_fn(acc: &mut Completions, scopes: &hir::FnScopes, offset: TextUnit) {
|
||||
let mut shadowed = FxHashSet::default();
|
||||
scopes
|
||||
.scope_chain_for_offset(offset)
|
||||
.flat_map(|scope| scopes.entries(scope).iter())
|
||||
.filter(|entry| shadowed.insert(entry.name()))
|
||||
.for_each(|entry| {
|
||||
CompletionItem::new(entry.name().to_string())
|
||||
.kind(Reference)
|
||||
.add_to(acc)
|
||||
});
|
||||
if scopes.self_param.is_some() {
|
||||
CompletionItem::new("self").kind(Reference).add_to(acc);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::completion::{CompletionKind, check_completion};
|
||||
|
||||
fn check_reference_completion(code: &str, expected_completions: &str) {
|
||||
check_completion(code, expected_completions, CompletionKind::Reference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_let_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn quux(x: i32) {
|
||||
let y = 92;
|
||||
1 + <|>;
|
||||
let z = ();
|
||||
}
|
||||
",
|
||||
"y;x;quux",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_if_let_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn quux() {
|
||||
if let Some(x) = foo() {
|
||||
let y = 92;
|
||||
};
|
||||
if let Some(a) = bar() {
|
||||
let b = 62;
|
||||
1 + <|>
|
||||
}
|
||||
}
|
||||
",
|
||||
"b;a;quux",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_for_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn quux() {
|
||||
for x in &[1, 2, 3] {
|
||||
<|>
|
||||
}
|
||||
}
|
||||
",
|
||||
"x;quux",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_mod_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
struct Foo;
|
||||
enum Baz {}
|
||||
fn quux() {
|
||||
<|>
|
||||
}
|
||||
",
|
||||
"quux;Foo;Baz",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_mod_scope_nested() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
struct Foo;
|
||||
mod m {
|
||||
struct Bar;
|
||||
fn quux() { <|> }
|
||||
}
|
||||
",
|
||||
"quux;Bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_type() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
struct Foo;
|
||||
fn x() -> <|>
|
||||
",
|
||||
"Foo;x",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_shadowing() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn foo() -> {
|
||||
let bar = 92;
|
||||
{
|
||||
let bar = 62;
|
||||
<|>
|
||||
}
|
||||
}
|
||||
",
|
||||
"bar;foo",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_self() {
|
||||
check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
|
||||
}
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
use ra_syntax::{
|
||||
SourceFileNode, AstNode,
|
||||
ast,
|
||||
SyntaxKind::*,
|
||||
};
|
||||
use hir::{
|
||||
self,
|
||||
FnScopes, Path
|
||||
};
|
||||
|
||||
use crate::{
|
||||
db::RootDatabase,
|
||||
completion::{CompletionItem, Completions, CompletionKind::*},
|
||||
Cancelable
|
||||
};
|
||||
|
||||
pub(super) fn completions(
|
||||
acc: &mut Completions,
|
||||
db: &RootDatabase,
|
||||
module: &hir::Module,
|
||||
_file: &SourceFileNode,
|
||||
name_ref: ast::NameRef,
|
||||
) -> Cancelable<()> {
|
||||
let kind = match classify_name_ref(name_ref) {
|
||||
Some(it) => it,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
match kind {
|
||||
NameRefKind::LocalRef { enclosing_fn } => {
|
||||
if let Some(fn_def) = enclosing_fn {
|
||||
let scopes = FnScopes::new(fn_def);
|
||||
complete_fn(name_ref, &scopes, acc);
|
||||
}
|
||||
|
||||
let module_scope = module.scope(db)?;
|
||||
module_scope
|
||||
.entries()
|
||||
.filter(|(_name, res)| {
|
||||
// Don't expose this item
|
||||
match res.import {
|
||||
None => true,
|
||||
Some(import) => {
|
||||
let range = import.range(db, module.source().file_id());
|
||||
!range.is_subrange(&name_ref.syntax().range())
|
||||
}
|
||||
}
|
||||
})
|
||||
.for_each(|(name, _res)| {
|
||||
CompletionItem::new(name.to_string())
|
||||
.kind(Reference)
|
||||
.add_to(acc)
|
||||
});
|
||||
}
|
||||
NameRefKind::Path(_) => (),
|
||||
NameRefKind::BareIdentInMod => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum NameRefKind<'a> {
|
||||
/// NameRef is a part of single-segment path, for example, a refernece to a
|
||||
/// local variable.
|
||||
LocalRef {
|
||||
enclosing_fn: Option<ast::FnDef<'a>>,
|
||||
},
|
||||
/// NameRef is the last segment in some path
|
||||
Path(Path),
|
||||
/// NameRef is bare identifier at the module's root.
|
||||
/// Used for keyword completion
|
||||
BareIdentInMod,
|
||||
}
|
||||
|
||||
fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
|
||||
let name_range = name_ref.syntax().range();
|
||||
let top_node = name_ref
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.take_while(|it| it.range() == name_range)
|
||||
.last()
|
||||
.unwrap();
|
||||
match top_node.parent().map(|it| it.kind()) {
|
||||
Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let parent = name_ref.syntax().parent()?;
|
||||
if let Some(segment) = ast::PathSegment::cast(parent) {
|
||||
let path = segment.parent_path();
|
||||
if let Some(path) = Path::from_ast(path) {
|
||||
if !path.is_ident() {
|
||||
return Some(NameRefKind::Path(path));
|
||||
}
|
||||
}
|
||||
if path.qualifier().is_none() {
|
||||
let enclosing_fn = name_ref
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::FnDef::cast);
|
||||
return Some(NameRefKind::LocalRef { enclosing_fn });
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) {
|
||||
let mut shadowed = FxHashSet::default();
|
||||
scopes
|
||||
.scope_chain(name_ref.syntax())
|
||||
.flat_map(|scope| scopes.entries(scope).iter())
|
||||
.filter(|entry| shadowed.insert(entry.name()))
|
||||
.for_each(|entry| {
|
||||
CompletionItem::new(entry.name().to_string())
|
||||
.kind(Reference)
|
||||
.add_to(acc)
|
||||
});
|
||||
if scopes.self_param.is_some() {
|
||||
CompletionItem::new("self").kind(Reference).add_to(acc);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::completion::{CompletionKind, check_completion};
|
||||
|
||||
fn check_reference_completion(code: &str, expected_completions: &str) {
|
||||
check_completion(code, expected_completions, CompletionKind::Reference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_let_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn quux(x: i32) {
|
||||
let y = 92;
|
||||
1 + <|>;
|
||||
let z = ();
|
||||
}
|
||||
",
|
||||
"y;x;quux",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_if_let_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn quux() {
|
||||
if let Some(x) = foo() {
|
||||
let y = 92;
|
||||
};
|
||||
if let Some(a) = bar() {
|
||||
let b = 62;
|
||||
1 + <|>
|
||||
}
|
||||
}
|
||||
",
|
||||
"b;a;quux",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_for_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn quux() {
|
||||
for x in &[1, 2, 3] {
|
||||
<|>
|
||||
}
|
||||
}
|
||||
",
|
||||
"x;quux",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_mod_scope() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
struct Foo;
|
||||
enum Baz {}
|
||||
fn quux() {
|
||||
<|>
|
||||
}
|
||||
",
|
||||
"quux;Foo;Baz",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_mod_scope_no_self_use() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
use foo<|>;
|
||||
",
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_self_path() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
use self::m::<|>;
|
||||
|
||||
mod m {
|
||||
struct Bar;
|
||||
}
|
||||
",
|
||||
"Bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_mod_scope_nested() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
struct Foo;
|
||||
mod m {
|
||||
struct Bar;
|
||||
fn quux() { <|> }
|
||||
}
|
||||
",
|
||||
"quux;Bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_type() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
struct Foo;
|
||||
fn x() -> <|>
|
||||
",
|
||||
"Foo;x",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_shadowing() {
|
||||
check_reference_completion(
|
||||
r"
|
||||
fn foo() -> {
|
||||
let bar = 92;
|
||||
{
|
||||
let bar = 62;
|
||||
<|>
|
||||
}
|
||||
}
|
||||
",
|
||||
"bar;foo",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_self() {
|
||||
check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_crate_path() {
|
||||
check_reference_completion(
|
||||
"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
struct Spam;
|
||||
//- /foo.rs
|
||||
use crate::Sp<|>
|
||||
",
|
||||
"Spam;foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_crate_path_with_braces() {
|
||||
check_reference_completion(
|
||||
"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
struct Spam;
|
||||
//- /foo.rs
|
||||
use crate::{Sp<|>};
|
||||
",
|
||||
"Spam;foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_crate_path_in_nested_tree() {
|
||||
check_reference_completion(
|
||||
"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
pub mod bar {
|
||||
pub mod baz {
|
||||
pub struct Spam;
|
||||
}
|
||||
}
|
||||
//- /foo.rs
|
||||
use crate::{bar::{baz::Sp<|>}};
|
||||
",
|
||||
"Spam",
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue