From 914421495835380b96e0016763fda6eff31a8179 Mon Sep 17 00:00:00 2001 From: Andrea Pretto Date: Mon, 15 Apr 2019 16:11:32 +0200 Subject: complete_import: add new import resolver infrastructure with some hardcoded importable name. Changes complete_scope to support that. --- crates/ra_assists/src/lib.rs | 2 +- crates/ra_hir/src/lib.rs | 2 +- crates/ra_hir/src/resolve.rs | 65 ++++++++++++++++++++++ crates/ra_hir/src/source_binder.rs | 19 ++++++- crates/ra_ide_api/src/completion/complete_scope.rs | 55 ++++++++++++++++-- .../src/completion/completion_context.rs | 23 +++++++- 6 files changed, 155 insertions(+), 11 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index ded401b63..173c004cd 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -98,7 +98,7 @@ mod inline_local_variable; mod replace_if_let_with_match; mod split_import; mod remove_dbg; -mod auto_import; +pub mod auto_import; mod add_missing_impl_members; fn all_assists() -> &'static [fn(AssistCtx) -> Option] { diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 4411715de..f156e3f07 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -52,7 +52,7 @@ use crate::{ db::{HirDatabase, DefDatabase}, name::{AsName, KnownName}, source_id::{FileAstId, AstId}, - resolve::Resolver, + resolve::Resolver, resolve::ImportResolver, }; pub use self::{ diff --git a/crates/ra_hir/src/resolve.rs b/crates/ra_hir/src/resolve.rs index f2c85eb66..0f866e6c2 100644 --- a/crates/ra_hir/src/resolve.rs +++ b/crates/ra_hir/src/resolve.rs @@ -3,6 +3,8 @@ use std::sync::Arc; use rustc_hash::FxHashMap; +use ra_syntax::SmolStr; + use crate::{ ModuleDef, code_model_api::Crate, @@ -12,8 +14,12 @@ use crate::{ generics::GenericParams, expr::{scope::{ExprScopes, ScopeId}, PatId}, impl_block::ImplBlock, +<<<<<<< HEAD path::Path, Trait +======= + path::Path, Trait, +>>>>>>> complete_import: add new import resolver infrastructure with some hardcoded importable name. }; #[derive(Debug, Clone, Default)] @@ -21,6 +27,12 @@ pub(crate) struct Resolver { scopes: Vec, } +#[derive(Debug, Clone, Default)] +pub(crate) struct ImportResolver { + // todo: use fst crate or something like that + dummy_names: Vec<(SmolStr, Vec)>, +} + // FIXME how to store these best #[derive(Debug, Clone)] pub(crate) struct ModuleItemMap { @@ -309,3 +321,56 @@ impl Scope { } } } + +impl ImportResolver { + pub(crate) fn new() -> Self { + let dummy_names = vec![ + (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]), + (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]), + (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]), + (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]), + ( + SmolStr::new("Debug"), + vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")], + ), + ( + SmolStr::new("Display"), + vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")], + ), + ( + SmolStr::new("Hash"), + vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")], + ), + ( + SmolStr::new("Hasher"), + vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")], + ), + ( + SmolStr::new("Iterator"), + vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")], + ), + ]; + + ImportResolver { dummy_names } + } + + // Returns a map of importable items filtered by name. + // The map associates item name with its full path. + // todo: should return Resolutions + pub(crate) fn all_names( + &self, + _db: &impl HirDatabase, + name: &Name, + ) -> FxHashMap> { + let name = name.to_smolstr(); + if name.len() > 1 { + self.dummy_names + .iter() + .filter(|(n, _)| n.as_str().contains(name.as_str())) + .cloned() + .collect() + } else { + FxHashMap::default() + } + } +} diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index f1bb13bc6..a6f0ab289 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -14,14 +14,19 @@ use ra_syntax::{ ast::{self, AstNode, NameOwner}, algo::find_node_at_offset, SyntaxKind::*, + SmolStr, }; use crate::{ HirDatabase, Function, Struct, Enum, Const, Static, Either, DefWithBody, PerNs, Name, +<<<<<<< HEAD AsName, Module, HirFileId, Crate, Trait, Resolver, Ty, +======= + AsName, Module, HirFileId, Crate, Trait, Resolver, ImportResolver, +>>>>>>> complete_import: add new import resolver infrastructure with some hardcoded importable name. expr::{BodySourceMap, scope::{ScopeId, ExprScopes}}, ids::LocationCtx, - expr, AstId + expr, AstId, }; /// Locates the module by `FileId`. Picks topmost module in the file. @@ -170,6 +175,7 @@ fn def_with_body_from_child_node( #[derive(Debug)] pub struct SourceAnalyzer { resolver: Resolver, + import_resolver: ImportResolver, body_source_map: Option>, infer: Option>, scopes: Option>, @@ -217,6 +223,7 @@ impl SourceAnalyzer { offset: Option, ) -> SourceAnalyzer { let def_with_body = def_with_body_from_child_node(db, file_id, node); + let import_resolver = ImportResolver::new(); if let Some(def) = def_with_body { let source_map = def.body_source_map(db); let scopes = db.expr_scopes(def); @@ -227,6 +234,7 @@ impl SourceAnalyzer { let resolver = expr::resolver_for_scope(def.body(db), db, scope); SourceAnalyzer { resolver, + import_resolver, body_source_map: Some(source_map), infer: Some(def.infer(db)), scopes: Some(scopes), @@ -237,6 +245,7 @@ impl SourceAnalyzer { .ancestors() .find_map(|node| try_get_resolver_for_node(db, file_id, node)) .unwrap_or_default(), + import_resolver, body_source_map: None, infer: None, scopes: None, @@ -323,6 +332,14 @@ impl SourceAnalyzer { self.resolver.all_names(db) } + pub fn all_import_names( + &self, + db: &impl HirDatabase, + name: &Name, + ) -> FxHashMap> { + self.import_resolver.all_names(db, name) + } + pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec { // FIXME: at least, this should work with any DefWithBody, but ideally // this should be hir-based altogether diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index fd256fc3b..63d475823 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs @@ -1,12 +1,57 @@ -use crate::completion::{Completions, CompletionContext}; +use ra_text_edit::TextEditBuilder; +use ra_syntax::SmolStr; +use ra_assists::auto_import; +use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionContext}; pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path { - return; + if ctx.is_trivial_path { + let names = ctx.analyzer.all_names(ctx.db); + names.into_iter().for_each(|(name, res)| acc.add_resolution(ctx, name.to_string(), &res)); } - let names = ctx.analyzer.all_names(ctx.db); - names.into_iter().for_each(|(name, res)| acc.add_resolution(ctx, name.to_string(), &res)); + if let Some(name) = ctx.path_ident.as_ref() { + let import_names = ctx.analyzer.all_import_names(ctx.db, name); + import_names.into_iter().for_each(|(name, path)| { + let edit = { + let mut builder = TextEditBuilder::default(); + builder.replace(ctx.source_range(), name.to_string()); + auto_import::auto_import_text_edit( + ctx.token.parent(), + ctx.token.parent(), + &path, + &mut builder, + ); + builder.finish() + }; + CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + build_import_label(&name, &path), + ) + .text_edit(edit) + .add_to(acc) + }); + } +} + +fn build_import_label(name: &str, path: &Vec) -> String { + let mut buf = String::with_capacity(64); + buf.push_str(name); + buf.push_str(" ("); + fmt_import_path(path, &mut buf); + buf.push_str(")"); + buf +} + +fn fmt_import_path(path: &Vec, buf: &mut String) { + let mut segments = path.iter(); + if let Some(s) = segments.next() { + buf.push_str(&s); + } + for s in segments { + buf.push_str("::"); + buf.push_str(&s); + } } #[cfg(test)] diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs index 359f2cffa..ca8f7900d 100644 --- a/crates/ra_ide_api/src/completion/completion_context.rs +++ b/crates/ra_ide_api/src/completion/completion_context.rs @@ -5,10 +5,10 @@ use ra_syntax::{ algo::{find_token_at_offset, find_covering_element, find_node_at_offset}, SyntaxKind::*, }; -use hir::source_binder; -use crate::{db, FilePosition}; +use hir::{ source_binder, Name }; +use crate::{db, FilePosition}; /// `CompletionContext` is created early during completion to figure out, where /// exactly is the cursor, syntax-wise. #[derive(Debug)] @@ -27,8 +27,10 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_pat_binding: bool, /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. pub(super) is_trivial_path: bool, - /// If not a trivial, path, the prefix (qualifier). + /// If not a trivial path, the prefix (qualifier). pub(super) path_prefix: Option, + /// If a trivial path, the ident. + pub(super) path_ident: Option, pub(super) after_if: bool, /// `true` if we are a statement or a last expr in the block. pub(super) can_be_stmt: bool, @@ -63,6 +65,7 @@ impl<'a> CompletionContext<'a> { is_pat_binding: false, is_trivial_path: false, path_prefix: None, + path_ident: None, after_if: false, can_be_stmt: false, is_new_item: false, @@ -83,6 +86,18 @@ impl<'a> CompletionContext<'a> { } fn fill(&mut self, original_file: &'a SourceFile, offset: TextUnit) { + // We heed the original NameRef before the "intellijRulezz" hack + if let Some(name_ref) = find_node_at_offset::(original_file.syntax(), offset) + { + if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { + if let Some(path) = hir::Path::from_ast(path) { + if let Some(ident) = path.as_ident() { + self.path_ident = Some(ident.clone()); + } + } + } + } + // Insert a fake ident to get a valid parse tree. We will use this file // to determine context, though the original_file will be used for // actual completion. @@ -151,6 +166,7 @@ impl<'a> CompletionContext<'a> { Some(it) => it, None => return, }; + if let Some(segment) = ast::PathSegment::cast(parent) { let path = segment.parent_path(); self.is_call = path @@ -167,6 +183,7 @@ impl<'a> CompletionContext<'a> { return; } } + if path.qualifier().is_none() { self.is_trivial_path = true; -- cgit v1.2.3