Complete crate:: paths

This commit is contained in:
Aleksey Kladov 2018-10-24 18:37:25 +03:00
parent 9a7db8fa00
commit 69d07df201
10 changed files with 152 additions and 21 deletions

View file

@ -0,0 +1,61 @@
use ra_editor::{CompletionItem, find_node_at_offset, complete_module_items};
use ra_syntax::{
AtomEdit, File, TextUnit, AstNode,
ast::{self, ModuleItemOwner},
};
use crate::{
FileId, Cancelable,
db::{self, SyntaxDatabase},
descriptors::module::{ModulesDatabase, ModuleTree, ModuleId},
};
pub(crate) fn resolve_based_completion(db: &db::RootDatabase, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> {
let file = db.file_syntax(file_id);
let module_tree = db.module_tree()?;
let file = {
let edit = AtomEdit::insert(offset, "intellijRulezz".to_string());
file.reparse(&edit)
};
let target_file = match find_target_module(&module_tree, file_id, &file, offset) {
None => return Ok(None),
Some(target_module) => {
let file_id = target_module.file_id(&module_tree);
db.file_syntax(file_id)
}
};
let mut res = Vec::new();
complete_module_items(target_file.ast().items(), None, &mut res);
Ok(Some(res))
}
pub(crate) fn find_target_module(module_tree: &ModuleTree, file_id: FileId, file: &File, offset: TextUnit) -> Option<ModuleId> {
let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset)?;
let mut crate_path = crate_path(name_ref)?;
let module_id = module_tree.any_module_for_file(file_id)?;
crate_path.pop();
let mut target_module = module_id.root(&module_tree);
for name in crate_path {
target_module = target_module.child(module_tree, name.text().as_str())?;
}
Some(target_module)
}
fn crate_path(name_ref: ast::NameRef) -> Option<Vec<ast::NameRef>> {
let mut path = name_ref.syntax()
.parent().and_then(ast::PathSegment::cast)?
.parent_path();
let mut res = Vec::new();
loop {
let segment = path.segment()?;
match segment.kind()? {
ast::PathSegmentKind::Name(name) => res.push(name),
ast::PathSegmentKind::CrateKw => break,
ast::PathSegmentKind::SelfKw | ast::PathSegmentKind::SuperKw =>
return None,
}
path = path.qualifier()?;
}
res.reverse();
Some(res)
}

View file

@ -5,7 +5,7 @@ use std::{
sync::Arc,
};
use ra_editor::{self, find_node_at_offset, resolve_local_name, FileSymbol, LineIndex, LocalEdit};
use ra_editor::{self, find_node_at_offset, resolve_local_name, FileSymbol, LineIndex, LocalEdit, CompletionItem};
use ra_syntax::{
ast::{self, ArgListOwner, Expr, NameOwner},
AstNode, File, SmolStr,
@ -197,6 +197,26 @@ impl AnalysisImpl {
pub fn crate_root(&self, crate_id: CrateId) -> FileId {
self.data.crate_graph.crate_roots[&crate_id]
}
pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> {
let mut res = Vec::new();
let mut has_completions = false;
let file = self.file_syntax(file_id);
if let Some(scope_based) = ra_editor::scope_completion(&file, offset) {
res.extend(scope_based);
has_completions = true;
}
let root = self.root(file_id);
if let Some(scope_based) = crate::completion::resolve_based_completion(root.db(), file_id, offset)? {
res.extend(scope_based);
has_completions = true;
}
let res = if has_completions {
Some(res)
} else {
None
};
Ok(res)
}
pub fn approximately_resolve_symbol(
&self,
file_id: FileId,

View file

@ -11,6 +11,7 @@ mod descriptors;
mod imp;
mod roots;
mod symbol_index;
mod completion;
use std::{fmt::Debug, sync::Arc};
@ -246,8 +247,7 @@ impl Analysis {
Ok(ra_editor::highlight(&file))
}
pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> {
let file = self.imp.file_syntax(file_id);
Ok(ra_editor::scope_completion(&file, offset))
self.imp.completions(file_id, offset)
}
pub fn assists(&self, file_id: FileId, range: TextRange) -> Cancelable<Vec<SourceChange>> {
Ok(self.imp.assists(file_id, range))

View file

@ -1,7 +1,5 @@
use std::{sync::Arc};
use ra_editor::LineIndex;
use ra_syntax::File;
use rustc_hash::FxHashSet;
use rayon::prelude::*;
use salsa::Database;
@ -10,7 +8,6 @@ use crate::{
Cancelable,
db::{self, FilesDatabase, SyntaxDatabase},
imp::FileResolverImp,
descriptors::module::{ModulesDatabase, ModuleTree},
symbol_index::SymbolIndex,
FileId,
};

View file

@ -264,3 +264,17 @@ fn test_find_all_refs_for_param_inside() {
let refs = get_all_refs(code);
assert_eq!(refs.len(), 2);
}
#[test]
fn test_complete_crate_path() {
let snap = analysis(&[
("/lib.rs", "mod foo; struct Spam;"),
("/foo.rs", "use crate::Sp"),
]);
let completions = snap.completions(FileId(2), 13.into()).unwrap().unwrap();
assert_eq_dbg(
r#"[CompletionItem { label: "foo", lookup: None, snippet: None },
CompletionItem { label: "Spam", lookup: None, snippet: None }]"#,
&completions,
);
}

View file

@ -2,7 +2,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use ra_syntax::{
algo::visit::{visitor, visitor_ctx, Visitor, VisitorCtx},
ast::{self, LoopBodyOwner, ModuleItemOwner},
ast::{self, AstChildren, LoopBodyOwner, ModuleItemOwner},
text_utils::is_subrange,
AstNode, File,
SyntaxKind::*,
@ -65,6 +65,21 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
}
}
pub fn complete_module_items(items: AstChildren<ast::ModuleItem>, this_item: Option<ast::NameRef>, acc: &mut Vec<CompletionItem>) {
let scope = ModuleScope::new(items);
acc.extend(
scope
.entries()
.iter()
.filter(|entry| Some(entry.syntax()) != this_item.map(|it| it.syntax()))
.map(|entry| CompletionItem {
label: entry.name().to_string(),
lookup: None,
snippet: None,
}),
);
}
fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
if !is_node::<ast::Path>(name_ref.syntax()) {
return;
@ -77,18 +92,7 @@ fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<Completi
.accept(node)
{
if let Some(items) = items {
let scope = ModuleScope::new(items);
acc.extend(
scope
.entries()
.iter()
.filter(|entry| entry.syntax() != name_ref.syntax())
.map(|entry| CompletionItem {
label: entry.name().to_string(),
lookup: None,
snippet: None,
}),
);
complete_module_items(items, Some(name_ref), acc);
}
break;
} else if !visited_fn {

View file

@ -21,7 +21,7 @@ mod typing;
pub use self::{
code_actions::{add_derive, add_impl, flip_comma, introduce_variable, LocalEdit},
completion::{scope_completion, CompletionItem},
completion::{scope_completion, complete_module_items, CompletionItem},
edit::{Edit, EditBuilder},
extend_selection::extend_selection,
folding_ranges::{folding_ranges, Fold, FoldKind},

View file

@ -1371,6 +1371,10 @@ impl<'a> Path<'a> {
pub fn segment(self) -> Option<PathSegment<'a>> {
super::child_opt(self)
}
pub fn qualifier(self) -> Option<Path<'a>> {
super::child_opt(self)
}
}
// PathExpr

View file

@ -232,6 +232,36 @@ impl<'a> IfExpr<'a> {
}
}
#[derive(Debug, Clone, Copy)]
pub enum PathSegmentKind<'a> {
Name(NameRef<'a>),
SelfKw,
SuperKw,
CrateKw,
}
impl<'a> PathSegment<'a> {
pub fn parent_path(self) -> Path<'a> {
self.syntax().parent().and_then(Path::cast)
.expect("segments are always nested in paths")
}
pub fn kind(self) -> Option<PathSegmentKind<'a>> {
let res = if let Some(name_ref) = self.name_ref() {
PathSegmentKind::Name(name_ref)
} else {
match self.syntax().first_child()?.kind() {
SELF_KW => PathSegmentKind::SelfKw,
SUPER_KW => PathSegmentKind::SuperKw,
CRATE_KW => PathSegmentKind::CrateKw,
_ => return None,
}
};
Some(res)
}
}
fn child_opt<'a, P: AstNode<'a>, C: AstNode<'a>>(parent: P) -> Option<C> {
children(parent).next()
}

View file

@ -531,7 +531,8 @@ Grammar(
),
"Path": (
options: [
["segment", "PathSegment"]
["segment", "PathSegment"],
["qualifier", "Path"],
]
),
"PathSegment": (