Complete crate:: paths
This commit is contained in:
parent
9a7db8fa00
commit
69d07df201
61
crates/ra_analysis/src/completion.rs
Normal file
61
crates/ra_analysis/src/completion.rs
Normal 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)
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
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 @@ pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
|
||||
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,
|
||||
|
@ -11,6 +11,7 @@
|
||||
mod imp;
|
||||
mod roots;
|
||||
mod symbol_index;
|
||||
mod completion;
|
||||
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
@ -246,8 +247,7 @@ pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
|
||||
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))
|
||||
|
@ -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 @@
|
||||
Cancelable,
|
||||
db::{self, FilesDatabase, SyntaxDatabase},
|
||||
imp::FileResolverImp,
|
||||
descriptors::module::{ModulesDatabase, ModuleTree},
|
||||
symbol_index::SymbolIndex,
|
||||
FileId,
|
||||
};
|
||||
|
@ -264,3 +264,17 @@ fn foo(i : u32) -> u32 {
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
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 {
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
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},
|
||||
|
@ -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
|
||||
|
@ -232,6 +232,36 @@ fn blocks(self) -> AstChildren<'a, Block<'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()
|
||||
}
|
||||
|
@ -531,7 +531,8 @@ Grammar(
|
||||
),
|
||||
"Path": (
|
||||
options: [
|
||||
["segment", "PathSegment"]
|
||||
["segment", "PathSegment"],
|
||||
["qualifier", "Path"],
|
||||
]
|
||||
),
|
||||
"PathSegment": (
|
||||
|
Loading…
Reference in New Issue
Block a user