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_ide_api/src/completion/complete_scope.rs | 55 ++++++++++++++++++++-- .../src/completion/completion_context.rs | 23 +++++++-- 2 files changed, 70 insertions(+), 8 deletions(-) (limited to 'crates/ra_ide_api') 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 From 200032852be0c66b978c875a8edf0eca1f08b901 Mon Sep 17 00:00:00 2001 From: Andrea Pretto Date: Sun, 21 Apr 2019 23:55:47 +0200 Subject: complete_import: prevent panic when the anchor is the completion source range (fix rebase mess) Please enter the commit message for your changes. Lines starting --- crates/ra_ide_api/src/completion/complete_scope.rs | 24 +++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index 63d475823..5bd5376c9 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs @@ -23,13 +23,23 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { ); builder.finish() }; - CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - build_import_label(&name, &path), - ) - .text_edit(edit) - .add_to(acc) + + // Hack: copied this check form conv.rs beacause auto import can produce edits + // that invalidate assert in conv_with. + if edit + .as_atoms() + .iter() + .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) + .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) + { + CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + build_import_label(&name, &path), + ) + .text_edit(edit) + .add_to(acc); + } }); } } -- cgit v1.2.3 From e01052d1f0b8bf70a418a10538528923c5f400d5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 15:56:28 +0300 Subject: move auto-imoprter into IDE auto-import is purely an IDE concern, so it should be done outside of HIR --- crates/ra_ide_api/src/completion/complete_scope.rs | 55 +++++++++++++++++++++- .../src/completion/completion_context.rs | 12 ----- 2 files changed, 54 insertions(+), 13 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index 5bd5376c9..a2523c5ef 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs @@ -1,6 +1,8 @@ +use rustc_hash::FxHashMap; 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) { @@ -10,7 +12,8 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { } if let Some(name) = ctx.path_ident.as_ref() { - let import_names = ctx.analyzer.all_import_names(ctx.db, name); + let import_resolver = ImportResolver::new(); + let import_names = import_resolver.all_names(&name.to_string()); import_names.into_iter().for_each(|(name, path)| { let edit = { let mut builder = TextEditBuilder::default(); @@ -64,6 +67,56 @@ fn fmt_import_path(path: &Vec, buf: &mut String) { } } +#[derive(Debug, Clone, Default)] +pub(crate) struct ImportResolver { + // todo: use fst crate or something like that + dummy_names: Vec<(SmolStr, Vec)>, +} + +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, name: &str) -> FxHashMap> { + if name.len() > 1 { + self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect() + } else { + FxHashMap::default() + } + } +} + #[cfg(test)] mod tests { use crate::completion::{CompletionKind, check_completion}; diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs index ca8f7900d..0d630fdf6 100644 --- a/crates/ra_ide_api/src/completion/completion_context.rs +++ b/crates/ra_ide_api/src/completion/completion_context.rs @@ -86,18 +86,6 @@ 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. -- cgit v1.2.3 From c27a3da5e86b1fcbfb562e0723fb97d72268fa72 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 22 Apr 2019 16:04:56 +0300 Subject: remove path_ident from CompletionContext We really shouldn't be looking at the identifier at point. Instead, all filtering and sorting should be implemented at the layer above. This layer should probably be home for auto-import completions as well, but, since that is not yet implemented, let's just stick this into complete_scope. --- crates/ra_ide_api/src/completion/complete_scope.rs | 70 +++++++++++----------- .../src/completion/completion_context.rs | 7 +-- 2 files changed, 38 insertions(+), 39 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index a2523c5ef..2473e58b4 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashMap; use ra_text_edit::TextEditBuilder; -use ra_syntax::SmolStr; +use ra_syntax::{SmolStr, ast, AstNode}; use ra_assists::auto_import; use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionContext}; @@ -9,41 +9,43 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { 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)); - } - if let Some(name) = ctx.path_ident.as_ref() { - let import_resolver = ImportResolver::new(); - let import_names = import_resolver.all_names(&name.to_string()); - 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() - }; + // auto-import + // We fetch ident from the original file, because we need to pre-filter auto-imports + if ast::NameRef::cast(ctx.token.parent()).is_some() { + let import_resolver = ImportResolver::new(); + let import_names = import_resolver.all_names(ctx.token.text()); + 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() + }; - // Hack: copied this check form conv.rs beacause auto import can produce edits - // that invalidate assert in conv_with. - if edit - .as_atoms() - .iter() - .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) - .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) - { - CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - build_import_label(&name, &path), - ) - .text_edit(edit) - .add_to(acc); - } - }); + // Hack: copied this check form conv.rs beacause auto import can produce edits + // that invalidate assert in conv_with. + if edit + .as_atoms() + .iter() + .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) + .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) + { + CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + build_import_label(&name, &path), + ) + .text_edit(edit) + .add_to(acc); + } + }); + } } } diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs index 0d630fdf6..a8c8cc7b0 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, Name }; +use hir::source_binder; use crate::{db, FilePosition}; + /// `CompletionContext` is created early during completion to figure out, where /// exactly is the cursor, syntax-wise. #[derive(Debug)] @@ -29,8 +29,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) is_trivial_path: bool, /// 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, @@ -65,7 +63,6 @@ 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, -- cgit v1.2.3