complete items from module scope

This commit is contained in:
Aleksey Kladov 2018-08-28 19:23:55 +03:00
parent d34588bf83
commit 537ea620bb
6 changed files with 100 additions and 18 deletions

View file

@ -8,7 +8,7 @@ use libsyntax2::{
use { use {
AtomEdit, find_node_at_offset, AtomEdit, find_node_at_offset,
scope::FnScopes, scope::{FnScopes, ModuleScope},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -24,18 +24,27 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
file.incremental_reparse(&edit)? file.incremental_reparse(&edit)?
}; };
let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?; let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?;
let fn_def = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next()?; let mut res = Vec::new();
if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() {
let scopes = FnScopes::new(fn_def); let scopes = FnScopes::new(fn_def);
Some(complete(name_ref, &scopes)) complete_fn(name_ref, &scopes, &mut res);
}
if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() {
let scope = ModuleScope::new(root);
res.extend(
scope.entries().iter()
.map(|entry| CompletionItem { name: entry.name().to_string() })
)
}
Some(res)
} }
fn complete(name_ref: ast::NameRef, scopes: &FnScopes) -> Vec<CompletionItem> { fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
acc.extend(
scopes.scope_chain(name_ref.syntax()) scopes.scope_chain(name_ref.syntax())
.flat_map(|scope| scopes.entries(scope).iter()) .flat_map(|scope| scopes.entries(scope).iter())
.map(|entry| CompletionItem { .map(|entry| CompletionItem { name: entry.name().to_string() })
name: entry.name().to_string() )
})
.collect()
} }
#[cfg(test)] #[cfg(test)]
@ -59,7 +68,8 @@ mod tests {
let z = (); let z = ();
} }
", r#"[CompletionItem { name: "y" }, ", r#"[CompletionItem { name: "y" },
CompletionItem { name: "x" }]"#); CompletionItem { name: "x" },
CompletionItem { name: "quux" }]"#);
} }
#[test] #[test]
@ -75,7 +85,8 @@ mod tests {
} }
} }
", r#"[CompletionItem { name: "b" }, ", r#"[CompletionItem { name: "b" },
CompletionItem { name: "a" }]"#); CompletionItem { name: "a" },
CompletionItem { name: "quux" }]"#);
} }
#[test] #[test]
@ -86,6 +97,20 @@ mod tests {
<|> <|>
} }
} }
", r#"[CompletionItem { name: "x" }]"#); ", r#"[CompletionItem { name: "x" },
CompletionItem { name: "quux" }]"#);
}
#[test]
fn test_completion_mod_scope() {
do_check(r"
struct Foo;
enum Baz {}
fn quux() {
<|>
}
", r#"[CompletionItem { name: "Foo" },
CompletionItem { name: "Baz" },
CompletionItem { name: "quux" }]"#);
} }
} }

View file

@ -18,7 +18,7 @@ mod test_utils;
use libsyntax2::{ use libsyntax2::{
File, TextUnit, TextRange, SyntaxNodeRef, File, TextUnit, TextRange, SyntaxNodeRef,
ast::{AstNode, NameOwner}, ast::{self, AstNode, NameOwner},
algo::{walk, find_leaf_at_offset, ancestors}, algo::{walk, find_leaf_at_offset, ancestors},
SyntaxKind::{self, *}, SyntaxKind::{self, *},
}; };
@ -126,8 +126,8 @@ pub fn syntax_tree(file: &File) -> String {
} }
pub fn runnables(file: &File) -> Vec<Runnable> { pub fn runnables(file: &File) -> Vec<Runnable> {
file.ast() walk::preorder(file.syntax())
.functions() .filter_map(ast::FnDef::cast)
.filter_map(|f| { .filter_map(|f| {
let name = f.name()?.text(); let name = f.name()?.text();
let kind = if name == "main" { let kind = if name == "main" {

View file

@ -1,3 +1,8 @@
mod fn_scope; mod fn_scope;
mod mod_scope;
pub use self::{
fn_scope::FnScopes,
mod_scope::ModuleScope,
};
pub use self::fn_scope::FnScopes;

View file

@ -0,0 +1,47 @@
use libsyntax2::{
AstNode, SyntaxNode, SmolStr, ast
};
pub struct ModuleScope {
entries: Vec<Entry>,
}
impl ModuleScope {
pub fn new(m: ast::Root) -> ModuleScope {
let entries = m.items().filter_map(|item| {
match item {
ast::ModuleItem::StructDef(item) => Entry::new(item),
ast::ModuleItem::EnumDef(item) => Entry::new(item),
ast::ModuleItem::FnDef(item) => Entry::new(item),
ast::ModuleItem::TraitDef(item) => Entry::new(item),
ast::ModuleItem::ExternCrateItem(_) |
ast::ModuleItem::ImplItem(_) |
ast::ModuleItem::UseItem(_) => None
}
}).collect();
ModuleScope { entries }
}
pub fn entries(&self) -> &[Entry] {
self.entries.as_slice()
}
}
pub struct Entry {
name: SyntaxNode,
}
impl Entry {
fn new<'a>(item: impl ast::NameOwner<'a>) -> Option<Entry> {
let name = item.name()?;
Some(Entry { name: name.syntax().owned() })
}
pub fn name(&self) -> SmolStr {
self.ast().text()
}
fn ast(&self) -> ast::Name {
ast::Name::cast(self.name.borrowed()).unwrap()
}
}

View file

@ -1441,6 +1441,10 @@ impl<'a> AstNode<'a> for Root<'a> {
} }
impl<'a> Root<'a> { impl<'a> Root<'a> {
pub fn items(self) -> impl Iterator<Item = ModuleItem<'a>> + 'a {
super::children(self)
}
pub fn functions(self) -> impl Iterator<Item = FnDef<'a>> + 'a { pub fn functions(self) -> impl Iterator<Item = FnDef<'a>> + 'a {
super::children(self) super::children(self)
} }

View file

@ -238,6 +238,7 @@ Grammar(
ast: { ast: {
"Root": ( "Root": (
collections: [ collections: [
["items", "ModuleItem"],
["functions", "FnDef"], ["functions", "FnDef"],
["modules", "Module"], ["modules", "Module"],
] ]