diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs new file mode 100644 index 00000000000..a0fd6828d62 --- /dev/null +++ b/crates/ra_analysis/src/completion.rs @@ -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>> { + 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 { + 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> { + 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) +} diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index f142b6c43a8..f3e5b28878c 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs @@ -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> { 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>> { + 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, diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index 67a239a5c62..7078e2d31f3 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs @@ -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> { Ok(ra_editor::highlight(&file)) } pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Cancelable>> { - 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> { Ok(self.imp.assists(file_id, range)) diff --git a/crates/ra_analysis/src/roots.rs b/crates/ra_analysis/src/roots.rs index aa02437200b..1e9e613ac2d 100644 --- a/crates/ra_analysis/src/roots.rs +++ b/crates/ra_analysis/src/roots.rs @@ -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, }; diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index 7ae3d0eebfc..52fae71aeb0 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs @@ -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, + ); +} diff --git a/crates/ra_editor/src/completion.rs b/crates/ra_editor/src/completion.rs index 8502b337d1a..0a36752554b 100644 --- a/crates/ra_editor/src/completion.rs +++ b/crates/ra_editor/src/completion.rs @@ -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, this_item: Option, acc: &mut Vec) { + 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) { if !is_node::(name_ref.syntax()) { return; @@ -77,18 +92,7 @@ fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec Path<'a> { pub fn segment(self) -> Option> { super::child_opt(self) } + + pub fn qualifier(self) -> Option> { + super::child_opt(self) + } } // PathExpr diff --git a/crates/ra_syntax/src/ast/mod.rs b/crates/ra_syntax/src/ast/mod.rs index 900426a8a91..c033263a1b6 100644 --- a/crates/ra_syntax/src/ast/mod.rs +++ b/crates/ra_syntax/src/ast/mod.rs @@ -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> { + 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 { children(parent).next() } diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 0830e02f21b..c1c215e0d6b 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -531,7 +531,8 @@ Grammar( ), "Path": ( options: [ - ["segment", "PathSegment"] + ["segment", "PathSegment"], + ["qualifier", "Path"], ] ), "PathSegment": (