diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 4f522ee76134..147563ef103d 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -36,9 +36,9 @@ use crate::{ const_::render_const, function::{render_fn, render_method}, literal::{render_struct_literal, render_variant_lit}, - macro_::render_macro, + macro_::{render_macro, render_macro_pat}, pattern::{render_struct_pat, render_variant_pat}, - render_field, render_path_resolution, render_resolution_simple, render_tuple_field, + render_field, render_path_resolution, render_pattern_resolution, render_tuple_field, type_alias::{render_type_alias, render_type_alias_with_eq}, union_literal::render_union_literal, RenderContext, @@ -134,10 +134,14 @@ impl Completions { item.add_to(self); } - pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) { + pub(crate) fn add_crate_roots( + &mut self, + ctx: &CompletionContext, + path_ctx: &PathCompletionCtx, + ) { ctx.process_all_names(&mut |name, res| match res { ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => { - self.add_module(ctx, m, name); + self.add_module(ctx, path_ctx, m, name); } _ => (), }); @@ -160,25 +164,36 @@ impl Completions { ); } - pub(crate) fn add_resolution_simple( + pub(crate) fn add_pattern_resolution( &mut self, ctx: &CompletionContext, + pattern_ctx: &PatternContext, local_name: hir::Name, resolution: hir::ScopeDef, ) { if ctx.is_scope_def_hidden(resolution) { + cov_mark::hit!(qualified_path_doc_hidden); return; } - self.add(render_resolution_simple(RenderContext::new(ctx), local_name, resolution).build()); + self.add( + render_pattern_resolution(RenderContext::new(ctx), pattern_ctx, local_name, resolution) + .build(), + ); } pub(crate) fn add_module( &mut self, ctx: &CompletionContext, + path_ctx: &PathCompletionCtx, module: hir::Module, local_name: hir::Name, ) { - self.add_resolution_simple(ctx, local_name, hir::ScopeDef::ModuleDef(module.into())); + self.add_path_resolution( + ctx, + path_ctx, + local_name, + hir::ScopeDef::ModuleDef(module.into()), + ); } pub(crate) fn add_macro( @@ -204,6 +219,29 @@ impl Completions { ); } + pub(crate) fn add_macro_pat( + &mut self, + ctx: &CompletionContext, + pattern_ctx: &PatternContext, + mac: hir::Macro, + local_name: hir::Name, + ) { + let is_private_editable = match ctx.is_visible(&mac) { + Visible::Yes => false, + Visible::Editable => true, + Visible::No => return, + }; + self.add( + render_macro_pat( + RenderContext::new(ctx).private_editable(is_private_editable), + pattern_ctx, + local_name, + mac, + ) + .build(), + ); + } + pub(crate) fn add_function( &mut self, ctx: &CompletionContext, @@ -341,6 +379,7 @@ impl Completions { pub(crate) fn add_field( &mut self, ctx: &CompletionContext, + dot_access: &DotAccess, receiver: Option, field: hir::Field, ty: &hir::Type, @@ -352,6 +391,7 @@ impl Completions { }; let item = render_field( RenderContext::new(ctx).private_editable(is_private_editable), + dot_access, receiver, field, ty, diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index 1c4f9a3113d1..154c096f4d59 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -95,7 +95,7 @@ pub(crate) fn complete_attribute_path( acc.add_macro(ctx, path_ctx, m, name) } hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { - acc.add_module(ctx, m, name) + acc.add_module(ctx, path_ctx, m, name) } _ => (), } @@ -103,14 +103,16 @@ pub(crate) fn complete_attribute_path( return; } // fresh use tree with leading colon2, only show crate roots - Qualified::Absolute => acc.add_crate_roots(ctx), + Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx), // only show modules in a fresh UseTree Qualified::No => { ctx.process_all_names(&mut |name, def| match def { hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => { acc.add_macro(ctx, path_ctx, m, name) } - hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => acc.add_module(ctx, m, name), + hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { + acc.add_module(ctx, path_ctx, m, name) + } _ => (), }); acc.add_nameref_keywords_with_colon(ctx); diff --git a/crates/ide-completion/src/completions/attribute/derive.rs b/crates/ide-completion/src/completions/attribute/derive.rs index 21298b6ca5dd..48eb76029ff7 100644 --- a/crates/ide-completion/src/completions/attribute/derive.rs +++ b/crates/ide-completion/src/completions/attribute/derive.rs @@ -35,12 +35,14 @@ pub(crate) fn complete_derive_path( { acc.add_macro(ctx, path_ctx, mac, name) } - ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => acc.add_module(ctx, m, name), + ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { + acc.add_module(ctx, path_ctx, m, name) + } _ => (), } } } - Qualified::Absolute => acc.add_crate_roots(ctx), + Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx), // only show modules in a fresh UseTree Qualified::No => { ctx.process_all_names(&mut |name, def| { @@ -51,7 +53,7 @@ pub(crate) fn complete_derive_path( mac } ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { - return acc.add_module(ctx, m, name); + return acc.add_module(ctx, path_ctx, m, name); } _ => return, }; diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index b58a9f39f2ef..bf0bce2198c1 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -29,7 +29,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext, dot_a acc, ctx, &receiver_ty, - |acc, field, ty| acc.add_field(ctx, None, field, &ty), + |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty), |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty), ); } @@ -64,7 +64,19 @@ pub(crate) fn complete_undotted_self( acc, ctx, &ty, - |acc, field, ty| acc.add_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), + |acc, field, ty| { + acc.add_field( + ctx, + &DotAccess { + receiver: None, + receiver_ty: None, + kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }, + }, + Some(hir::known::SELF_PARAM), + field, + &ty, + ) + }, |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), ); complete_methods(ctx, &ty, |func| { diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 9c003be6af46..6b36801205af 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -152,7 +152,7 @@ pub(crate) fn complete_expr_path( _ => (), } } - Qualified::Absolute => acc.add_crate_roots(ctx), + Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx), Qualified::No => { acc.add_nameref_keywords_with_colon(ctx); if let Some(adt) = diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 129910465c22..fa8c0eb77ace 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -12,7 +12,7 @@ use crate::{ CompletionContext, DotAccess, PathCompletionCtx, PathKind, PatternContext, Qualified, TypeLocation, }, - render::{render_resolution_with_import, RenderContext}, + render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext}, }; use super::Completions; @@ -149,30 +149,22 @@ pub(crate) fn import_on_the_fly_path( pub(crate) fn import_on_the_fly_pat( acc: &mut Completions, ctx: &CompletionContext, - pat_ctx: &PatternContext, + pattern_ctx: &PatternContext, ) -> Option<()> { if !ctx.config.enable_imports_on_the_fly { return None; } - if let PatternContext { record_pat: Some(_), .. } = pat_ctx { + if let PatternContext { record_pat: Some(_), .. } = pattern_ctx { return None; } let potential_import_name = import_name(ctx); let import_assets = import_assets_for_path(ctx, &potential_import_name, None)?; - import_on_the_fly( + import_on_the_fly_pat2( acc, ctx, - &PathCompletionCtx { - has_call_parens: false, - has_macro_bang: false, - qualified: Qualified::No, - parent: None, - kind: crate::context::PathKind::Pat { pat_ctx: pat_ctx.clone() }, - has_type_args: false, - use_tree_parent: false, - }, + pattern_ctx, import_assets, ctx.original_token.parent()?, potential_import_name, @@ -287,6 +279,50 @@ fn import_on_the_fly( Some(()) } +fn import_on_the_fly_pat2( + acc: &mut Completions, + ctx: &CompletionContext, + pattern_ctx: &PatternContext, + import_assets: ImportAssets, + position: SyntaxNode, + potential_import_name: String, +) -> Option<()> { + let _p = profile::span("import_on_the_fly_pat").detail(|| potential_import_name.clone()); + + if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() { + return None; + } + + let ns_filter = |import: &LocatedImport| match import.original_item { + ItemInNs::Macros(mac) => mac.is_fn_like(ctx.db), + ItemInNs::Types(_) => true, + ItemInNs::Values(def) => matches!(def, hir::ModuleDef::Const(_)), + }; + let user_input_lowercased = potential_import_name.to_lowercase(); + + acc.add_all( + import_assets + .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) + .into_iter() + .filter(ns_filter) + .filter(|import| { + !ctx.is_item_hidden(&import.item_to_import) + && !ctx.is_item_hidden(&import.original_item) + }) + .sorted_by_key(|located_import| { + compute_fuzzy_completion_order_key( + &located_import.import_path, + &user_input_lowercased, + ) + }) + .filter_map(|import| { + render_resolution_with_import_pat(RenderContext::new(ctx), pattern_ctx, import) + }) + .map(|builder| builder.build()), + ); + Some(()) +} + fn import_on_the_fly_method( acc: &mut Completions, ctx: &CompletionContext, @@ -295,7 +331,7 @@ fn import_on_the_fly_method( position: SyntaxNode, potential_import_name: String, ) -> Option<()> { - let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.clone()); + let _p = profile::span("import_on_the_fly_method").detail(|| potential_import_name.clone()); if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() { return None; diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index 329d08a9e764..4a32e0ebf58b 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -45,7 +45,7 @@ pub(crate) fn complete_item_list( acc.add_macro(ctx, path_ctx, m, name) } hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { - acc.add_module(ctx, m, name) + acc.add_module(ctx, path_ctx, m, name) } _ => (), } @@ -55,13 +55,15 @@ pub(crate) fn complete_item_list( acc.add_keyword(ctx, "super::"); } } - Qualified::Absolute => acc.add_crate_roots(ctx), + Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx), Qualified::No if ctx.qualifier_ctx.none() => { ctx.process_all_names(&mut |name, def| match def { hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => { acc.add_macro(ctx, path_ctx, m, name) } - hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => acc.add_module(ctx, m, name), + hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { + acc.add_module(ctx, path_ctx, m, name) + } _ => (), }); acc.add_nameref_keywords_with_colon(ctx); diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs index 91d535654105..4ea80a50777c 100644 --- a/crates/ide-completion/src/completions/pattern.rs +++ b/crates/ide-completion/src/completions/pattern.rs @@ -82,20 +82,7 @@ pub(crate) fn complete_pattern( hir::ModuleDef::Const(..) => refutable, hir::ModuleDef::Module(..) => true, hir::ModuleDef::Macro(mac) if mac.is_fn_like(ctx.db) => { - return acc.add_macro( - ctx, - &PathCompletionCtx { - has_call_parens: false, - has_macro_bang: false, - qualified: Qualified::No, - parent: None, - kind: crate::context::PathKind::Pat { pat_ctx: pattern_ctx.clone() }, - has_type_args: false, - use_tree_parent: false, - }, - mac, - name, - ) + return acc.add_macro_pat(ctx, pattern_ctx, mac, name); } _ => false, }, @@ -116,7 +103,7 @@ pub(crate) fn complete_pattern( | ScopeDef::Unknown => false, }; if add_simple_path { - acc.add_resolution_simple(ctx, name, res); + acc.add_pattern_resolution(ctx, pattern_ctx, name, res); } }); } @@ -205,7 +192,7 @@ pub(crate) fn complete_pattern_path( } } // qualifier can only be none here if we are in a TuplePat or RecordPat in which case special characters have to follow the path - Qualified::Absolute => acc.add_crate_roots(ctx), + Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx), Qualified::No => { ctx.process_all_names(&mut |name, res| { // FIXME: properly filter here diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs index 9f834e1ca158..8cef3a70182b 100644 --- a/crates/ide-completion/src/completions/record.rs +++ b/crates/ide-completion/src/completions/record.rs @@ -3,7 +3,7 @@ use ide_db::SymbolKind; use syntax::ast::{self, Expr}; use crate::{ - context::{ExprCtx, PathCompletionCtx, PatternContext, Qualified}, + context::{DotAccess, DotAccessKind, ExprCtx, PathCompletionCtx, PatternContext, Qualified}, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, Completions, }; @@ -107,7 +107,17 @@ fn complete_fields( missing_fields: Vec<(hir::Field, hir::Type)>, ) { for (field, ty) in missing_fields { - acc.add_field(ctx, None, field, &ty); + acc.add_field( + ctx, + &DotAccess { + receiver: None, + receiver_ty: None, + kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }, + }, + None, + field, + &ty, + ); } } diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index dea0c701b84e..c5b65d36ae8f 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -141,7 +141,7 @@ pub(crate) fn complete_type_path( _ => (), } } - Qualified::Absolute => acc.add_crate_roots(ctx), + Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx), Qualified::No => { acc.add_nameref_keywords_with_colon(ctx); if let TypeLocation::TypeBound = location { diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index 2c039d50189c..c98590f1361c 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -89,7 +89,7 @@ pub(crate) fn complete_use_path( // fresh use tree with leading colon2, only show crate roots Qualified::Absolute => { cov_mark::hit!(use_tree_crate_roots_only); - acc.add_crate_roots(ctx); + acc.add_crate_roots(ctx, path_ctx); } // only show modules and non-std enum in a fresh UseTree Qualified::No => { @@ -97,7 +97,7 @@ pub(crate) fn complete_use_path( ctx.process_all_names(&mut |name, res| { match res { ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => { - acc.add_module(ctx, module, name); + acc.add_module(ctx, path_ctx, module, name); } ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => { // exclude prelude enum diff --git a/crates/ide-completion/src/completions/vis.rs b/crates/ide-completion/src/completions/vis.rs index 30de0e94f7a5..6621aafe4bd6 100644 --- a/crates/ide-completion/src/completions/vis.rs +++ b/crates/ide-completion/src/completions/vis.rs @@ -8,7 +8,7 @@ use crate::{ pub(crate) fn complete_vis_path( acc: &mut Completions, ctx: &CompletionContext, - PathCompletionCtx { qualified, .. }: &PathCompletionCtx, + path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx, &has_in_token: &bool, ) { match qualified { @@ -23,7 +23,7 @@ pub(crate) fn complete_vis_path( if let Some(next) = next_towards_current { if let Some(name) = next.name(ctx.db) { cov_mark::hit!(visibility_qualified); - acc.add_module(ctx, next, name); + acc.add_module(ctx, path_ctx, next, name); } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 441f7ad70bf2..8ea03358aebc 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -61,6 +61,8 @@ pub(crate) struct PathCompletionCtx { pub(super) qualified: Qualified, /// The parent of the path we are completing. pub(super) parent: Option, + /// The path of which we are completing the segment + pub(super) path: ast::Path, pub(super) kind: PathKind, /// Whether the path segment has type args or not. pub(super) has_type_args: bool, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 7e6c842b1e05..e13950d56a96 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -556,6 +556,7 @@ impl<'a> CompletionContext<'a> { has_macro_bang: false, qualified: Qualified::No, parent: path.parent_path(), + path: path.clone(), kind: PathKind::Item { kind: ItemListKind::SourceFile }, has_type_args: false, use_tree_parent: false, diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index 4774fe9db737..2b10dccb8008 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -6,7 +6,7 @@ use hir::{Documentation, Mutability}; use ide_db::{imports::import_assets::LocatedImport, SnippetCap, SymbolKind}; use smallvec::SmallVec; use stdx::{impl_from, never}; -use syntax::{SmolStr, TextRange}; +use syntax::{SmolStr, TextRange, TextSize}; use text_edit::TextEdit; use crate::{ @@ -68,7 +68,7 @@ pub struct CompletionItem { /// Indicates that a reference or mutable reference to this variable is a /// possible match. - ref_match: Option, + ref_match: Option<(Mutability, TextSize)>, /// The import data to add to completion's edits. import_to_add: SmallVec<[LocatedImport; 1]>, @@ -104,8 +104,8 @@ impl fmt::Debug for CompletionItem { s.field("relevance", &self.relevance); } - if let Some(mutability) = &self.ref_match { - s.field("ref_match", &format!("&{}", mutability.as_keyword_for_ref())); + if let Some((mutability, offset)) = &self.ref_match { + s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref())); } if self.trigger_call_info { s.field("trigger_call_info", &true); @@ -395,14 +395,14 @@ impl CompletionItem { self.trigger_call_info } - pub fn ref_match(&self) -> Option<(Mutability, CompletionRelevance)> { + pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> { // Relevance of the ref match should be the same as the original // match, but with exact type match set because self.ref_match // is only set if there is an exact type match. let mut relevance = self.relevance; relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact); - self.ref_match.map(|mutability| (mutability, relevance)) + self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance)) } pub fn imports_to_add(&self) -> &[LocatedImport] { @@ -428,7 +428,7 @@ pub(crate) struct Builder { deprecated: bool, trigger_call_info: bool, relevance: CompletionRelevance, - ref_match: Option, + ref_match: Option<(Mutability, TextSize)>, } impl Builder { @@ -548,8 +548,8 @@ impl Builder { self.imports_to_add.push(import_to_add); self } - pub(crate) fn ref_match(&mut self, mutability: Mutability) -> &mut Builder { - self.ref_match = Some(mutability); + pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder { + self.ref_match = Some((mutability, offset)); self } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 9c339a13e7b3..6571e673527d 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -14,12 +14,16 @@ use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef}; use ide_db::{ helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, }; -use syntax::{SmolStr, SyntaxKind, TextRange}; +use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; use crate::{ - context::{PathCompletionCtx, PathKind}, + context::{DotAccess, PathCompletionCtx, PathKind, PatternContext}, item::{Builder, CompletionRelevanceTypeMatch}, - render::{function::render_fn, literal::render_variant_lit, macro_::render_macro}, + render::{ + function::render_fn, + literal::render_variant_lit, + macro_::{render_macro, render_macro_pat}, + }, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, }; /// Interface for data and methods required for items rendering. @@ -106,6 +110,7 @@ impl<'a> RenderContext<'a> { pub(crate) fn render_field( ctx: RenderContext<'_>, + dot_access: &DotAccess, receiver: Option, field: hir::Field, ty: &hir::Type, @@ -130,10 +135,10 @@ pub(crate) fn render_field( if is_keyword && !matches!(name.as_str(), "self" | "crate" | "super" | "Self") { item.insert_text(format!("r#{}", name)); } - if let Some(_ref_match) = compute_ref_match(ctx.completion, ty) { - // FIXME - // For now we don't properly calculate the edits for ref match - // completions on struct fields, so we've disabled them. See #8058. + if let Some(receiver) = &dot_access.receiver { + if let Some(ref_match) = compute_ref_match(ctx.completion, ty) { + item.ref_match(ref_match, receiver.syntax().text_range().start()); + } } item.build() } @@ -153,21 +158,29 @@ pub(crate) fn render_tuple_field( item.build() } +pub(crate) fn render_type_inference(ty_string: String, ctx: &CompletionContext) -> CompletionItem { + let mut builder = + CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string); + builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() }); + builder.build() +} + pub(crate) fn render_path_resolution( ctx: RenderContext<'_>, path_ctx: &PathCompletionCtx, local_name: hir::Name, resolution: ScopeDef, ) -> Builder { - render_resolution_(ctx, path_ctx, local_name, None, resolution) + render_resolution_path(ctx, path_ctx, local_name, None, resolution) } -pub(crate) fn render_resolution_simple( +pub(crate) fn render_pattern_resolution( ctx: RenderContext<'_>, + pattern_ctx: &PatternContext, local_name: hir::Name, resolution: ScopeDef, ) -> Builder { - render_resolution_simple_(ctx, local_name, None, resolution) + render_resolution_pat(ctx, pattern_ctx, local_name, None, resolution) } pub(crate) fn render_resolution_with_import( @@ -176,23 +189,56 @@ pub(crate) fn render_resolution_with_import( import_edit: LocatedImport, ) -> Option { let resolution = ScopeDef::from(import_edit.original_item); - let local_name = match resolution { + let local_name = scope_def_to_name(resolution, &ctx, &import_edit)?; + + Some(render_resolution_path(ctx, path_ctx, local_name, Some(import_edit), resolution)) +} + +pub(crate) fn render_resolution_with_import_pat( + ctx: RenderContext<'_>, + pattern_ctx: &PatternContext, + import_edit: LocatedImport, +) -> Option { + let resolution = ScopeDef::from(import_edit.original_item); + let local_name = scope_def_to_name(resolution, &ctx, &import_edit)?; + Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution)) +} + +fn scope_def_to_name( + resolution: ScopeDef, + ctx: &RenderContext, + import_edit: &LocatedImport, +) -> Option { + Some(match resolution { ScopeDef::ModuleDef(hir::ModuleDef::Function(f)) => f.name(ctx.completion.db), ScopeDef::ModuleDef(hir::ModuleDef::Const(c)) => c.name(ctx.completion.db)?, ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db), _ => item_name(ctx.db(), import_edit.original_item)?, - }; - Some(render_resolution_(ctx, path_ctx, local_name, Some(import_edit), resolution)) + }) } -pub(crate) fn render_type_inference(ty_string: String, ctx: &CompletionContext) -> CompletionItem { - let mut builder = - CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string); - builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() }); - builder.build() +fn render_resolution_pat( + ctx: RenderContext<'_>, + pattern_ctx: &PatternContext, + local_name: hir::Name, + import_to_add: Option, + resolution: ScopeDef, +) -> Builder { + let _p = profile::span("render_resolution"); + use hir::ModuleDef::*; + + match resolution { + ScopeDef::ModuleDef(Macro(mac)) => { + let ctx = ctx.import_to_add(import_to_add); + return render_macro_pat(ctx, pattern_ctx, local_name, mac); + } + _ => (), + } + + render_resolution_simple_(ctx, local_name, import_to_add, resolution) } -fn render_resolution_( +fn render_resolution_path( ctx: RenderContext<'_>, path_ctx: &PathCompletionCtx, local_name: hir::Name, @@ -221,19 +267,12 @@ fn render_resolution_( } _ => (), } - render_resolution_simple_type(ctx, path_ctx, local_name, import_to_add, resolution) -} -fn render_resolution_simple_type( - ctx: RenderContext<'_>, - path_ctx: &PathCompletionCtx, - local_name: hir::Name, - import_to_add: Option, - resolution: ScopeDef, -) -> Builder { + let completion = ctx.completion; let cap = ctx.snippet_cap(); - let db = ctx.completion.db; - let config = ctx.completion.config; + let db = completion.db; + let config = completion.config; + let name = local_name.to_smol_str(); let mut item = render_resolution_simple_(ctx, local_name, import_to_add, resolution); // Add `<>` for generic types @@ -250,6 +289,7 @@ fn render_resolution_simple_type( } _ => false, }; + if has_non_default_type_params { cov_mark::hit!(inserts_angle_brackets_for_generics); item.lookup_by(name.clone()) @@ -259,6 +299,23 @@ fn render_resolution_simple_type( } } } + if let ScopeDef::Local(local) = resolution { + let ty = local.ty(db); + if !ty.is_unknown() { + item.detail(ty.display(db).to_string()); + } + + item.set_relevance(CompletionRelevance { + type_match: compute_type_match(completion, &ty), + exact_name_match: compute_exact_name_match(completion, &name), + is_local: true, + ..CompletionRelevance::default() + }); + + if let Some(ref_match) = compute_ref_match(completion, &ty) { + item.ref_match(ref_match, path_ctx.path.syntax().text_range().start()); + } + }; item } @@ -269,11 +326,25 @@ fn render_resolution_simple_( resolution: ScopeDef, ) -> Builder { let _p = profile::span("render_resolution"); - use hir::ModuleDef::*; let db = ctx.db(); let ctx = ctx.import_to_add(import_to_add); - let kind = match resolution { + let kind = res_to_kind(resolution); + + let mut item = CompletionItem::new(kind, ctx.source_range(), local_name.to_smol_str()); + item.set_relevance(ctx.completion_relevance()) + .set_documentation(scope_def_docs(db, resolution)) + .set_deprecated(scope_def_is_deprecated(&ctx, resolution)); + + if let Some(import_to_add) = ctx.import_to_add { + item.add_import(import_to_add); + } + item +} + +fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind { + use hir::ModuleDef::*; + match resolution { ScopeDef::Unknown => CompletionItemKind::UnresolvedReference, ScopeDef::ModuleDef(Function(_)) => CompletionItemKind::SymbolKind(SymbolKind::Function), ScopeDef::ModuleDef(Variant(_)) => CompletionItemKind::SymbolKind(SymbolKind::Variant), @@ -299,36 +370,7 @@ fn render_resolution_simple_( ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { CompletionItemKind::SymbolKind(SymbolKind::SelfParam) } - }; - - let local_name = local_name.to_smol_str(); - let mut item = CompletionItem::new(kind, ctx.source_range(), local_name.clone()); - item.set_relevance(ctx.completion_relevance()); - if let ScopeDef::Local(local) = resolution { - let ty = local.ty(db); - if !ty.is_unknown() { - item.detail(ty.display(db).to_string()); - } - - item.set_relevance(CompletionRelevance { - type_match: compute_type_match(ctx.completion, &ty), - exact_name_match: compute_exact_name_match(ctx.completion, &local_name), - is_local: true, - ..CompletionRelevance::default() - }); - - if let Some(ref_match) = compute_ref_match(ctx.completion, &ty) { - item.ref_match(ref_match); - } - }; - - item.set_documentation(scope_def_docs(db, resolution)) - .set_deprecated(scope_def_is_deprecated(&ctx, resolution)); - - if let Some(import_to_add) = ctx.import_to_add { - item.add_import(import_to_add); } - item } fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option { @@ -455,7 +497,7 @@ mod tests { let relevance = display_relevance(it.relevance()); items.push(format!("{} {} {}\n", tag, it.label(), relevance)); - if let Some((mutability, relevance)) = it.ref_match() { + if let Some((mutability, _offset, relevance)) = it.ref_match() { let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label()); let relevance = display_relevance(relevance); @@ -1494,9 +1536,6 @@ impl Foo { fn baz(&self) -> u32 { 0 } } fn foo(f: Foo) { let _: &u32 = f.b$0 } "#, &[CompletionItemKind::Method, CompletionItemKind::SymbolKind(SymbolKind::Field)], - // FIXME - // Ideally we'd also suggest &f.bar and &f.baz() as exact - // type matches. See #8058. expect![[r#" [ CompletionItem { @@ -1507,6 +1546,7 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 } kind: Method, lookup: "baz", detail: "fn(&self) -> u32", + ref_match: "&@96", }, CompletionItem { label: "bar", @@ -1517,6 +1557,7 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 } Field, ), detail: "u32", + ref_match: "&@96", }, ] "#]], @@ -1525,7 +1566,6 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 } #[test] fn qualified_path_ref() { - // disabled right now because it doesn't render correctly, #8058 check_kinds( r#" struct S; @@ -1554,6 +1594,7 @@ fn main() { ), lookup: "foo", detail: "fn() -> S", + ref_match: "&@92", }, ] "#]], diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 3666ee40e2f8..37486e4d93e9 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -4,20 +4,19 @@ use hir::{db::HirDatabase, AsAssocItem, HirDisplay}; use ide_db::{SnippetCap, SymbolKind}; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; -use syntax::SmolStr; +use syntax::{AstNode, SmolStr}; use crate::{ - context::{ - CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind, Qualified, - }, + context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind}, item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext}, CallableSnippets, }; -enum FuncKind { - Function, - Method(Option), +#[derive(Debug)] +enum FuncKind<'ctx> { + Function(&'ctx PathCompletionCtx), + Method(&'ctx DotAccess, Option), } pub(crate) fn render_fn( @@ -27,29 +26,7 @@ pub(crate) fn render_fn( func: hir::Function, ) -> Builder { let _p = profile::span("render_fn"); - let func_kind = FuncKind::Function; - let params = match ctx.completion.config.snippet_cap { - Some(_) => { - if !matches!( - path_ctx, - PathCompletionCtx { kind: PathKind::Expr { .. }, has_call_parens: true, .. } - | PathCompletionCtx { kind: PathKind::Use | PathKind::Type { .. }, .. } - ) { - params(ctx.completion, func, &func_kind, false) - } else { - None - } - } - _ => None, - }; - render( - ctx, - local_name, - func, - func_kind, - params, - matches!(path_ctx.qualified, Qualified::With { .. }), - ) + render(ctx, local_name, func, FuncKind::Function(path_ctx)) } pub(crate) fn render_method( @@ -60,16 +37,7 @@ pub(crate) fn render_method( func: hir::Function, ) -> Builder { let _p = profile::span("render_method"); - let func_kind = FuncKind::Method(receiver); - let params = match ctx.completion.config.snippet_cap { - Some(_) => match dot_access { - DotAccess { kind: DotAccessKind::Method { has_parens: true }, .. } => None, - _ => params(ctx.completion, func, &func_kind, true), - }, - _ => None, - }; - - render(ctx, local_name, func, func_kind, params, false) + render(ctx, local_name, func, FuncKind::Method(dot_access, receiver)) } fn render( @@ -77,15 +45,13 @@ fn render( local_name: Option, func: hir::Function, func_kind: FuncKind, - params: Option<(Option, Vec)>, - qualified_path: bool, ) -> Builder { let db = completion.db; let name = local_name.unwrap_or_else(|| func.name(db)); let call = match &func_kind { - FuncKind::Method(Some(receiver)) => format!("{}.{}", receiver, &name).into(), + FuncKind::Method(_, Some(receiver)) => format!("{}.{}", receiver, &name).into(), _ => name.to_smol_str(), }; let mut item = CompletionItem::new( @@ -111,11 +77,14 @@ fn render( }); if let Some(ref_match) = compute_ref_match(completion, &ret_type) { - // FIXME For now we don't properly calculate the edits for ref match - // completions on methods or qualified paths, so we've disabled them. - // See #8058. - if matches!(func_kind, FuncKind::Function) && !qualified_path { - item.ref_match(ref_match); + match func_kind { + FuncKind::Function(path_ctx) => { + item.ref_match(ref_match, path_ctx.path.syntax().text_range().start()); + } + FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => { + item.ref_match(ref_match, receiver.syntax().text_range().start()); + } + _ => (), } } @@ -124,12 +93,34 @@ fn render( .detail(detail(db, func)) .lookup_by(name.to_smol_str()); - match completion.config.snippet_cap.zip(params) { - Some((cap, (self_param, params))) => { - add_call_parens(&mut item, completion, cap, call, self_param, params); + match ctx.completion.config.snippet_cap { + Some(cap) => { + let complete_params = match func_kind { + FuncKind::Function(PathCompletionCtx { + kind: PathKind::Expr { .. }, + has_call_parens: false, + .. + }) => Some(false), + FuncKind::Method( + DotAccess { + kind: + DotAccessKind::Method { has_parens: false } | DotAccessKind::Field { .. }, + .. + }, + _, + ) => Some(true), + _ => None, + }; + if let Some(has_dot_receiver) = complete_params { + if let Some((self_param, params)) = + params(ctx.completion, func, &func_kind, has_dot_receiver) + { + add_call_parens(&mut item, completion, cap, call, self_param, params); + } + } } _ => (), - } + }; match ctx.import_to_add { Some(import_to_add) => { @@ -291,7 +282,7 @@ fn params( } } - let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(Some(_))) { + let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) { None } else { func.self_param(ctx.db) diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs index b89030990e44..7b0555d5a4ce 100644 --- a/crates/ide-completion/src/render/literal.rs +++ b/crates/ide-completion/src/render/literal.rs @@ -2,6 +2,7 @@ use hir::{db::HirDatabase, Documentation, HasAttrs, StructKind}; use ide_db::SymbolKind; +use syntax::AstNode; use crate::{ context::{CompletionContext, PathCompletionCtx, PathKind}, @@ -117,7 +118,7 @@ fn render( ..ctx.completion_relevance() }); if let Some(ref_match) = compute_ref_match(completion, &ty) { - item.ref_match(ref_match); + item.ref_match(ref_match, path_ctx.path.syntax().text_range().start()); } if let Some(import_to_add) = ctx.import_to_add { diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs index 6da7bb3193ba..ac2091eca981 100644 --- a/crates/ide-completion/src/render/macro_.rs +++ b/crates/ide-completion/src/render/macro_.rs @@ -5,24 +5,37 @@ use ide_db::SymbolKind; use syntax::SmolStr; use crate::{ - context::{PathCompletionCtx, PathKind}, + context::{PathCompletionCtx, PathKind, PatternContext}, item::{Builder, CompletionItem}, render::RenderContext, }; pub(crate) fn render_macro( ctx: RenderContext<'_>, - path_ctx: &PathCompletionCtx, + PathCompletionCtx { kind, has_macro_bang, has_call_parens, .. }: &PathCompletionCtx, + + name: hir::Name, + macro_: hir::Macro, +) -> Builder { + let _p = profile::span("render_macro"); + render(ctx, *kind == PathKind::Use, *has_macro_bang, *has_call_parens, name, macro_) +} + +pub(crate) fn render_macro_pat( + ctx: RenderContext<'_>, + _pattern_ctx: &PatternContext, name: hir::Name, macro_: hir::Macro, ) -> Builder { let _p = profile::span("render_macro"); - render(ctx, path_ctx, name, macro_) + render(ctx, false, false, false, name, macro_) } fn render( ctx @ RenderContext { completion, .. }: RenderContext<'_>, - PathCompletionCtx { kind, has_macro_bang, has_call_parens, .. }: &PathCompletionCtx, + is_use_path: bool, + has_macro_bang: bool, + has_call_parens: bool, name: hir::Name, macro_: hir::Macro, ) -> Builder { @@ -39,7 +52,7 @@ fn render( let is_fn_like = macro_.is_fn_like(completion.db); let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") }; - let needs_bang = is_fn_like && *kind != PathKind::Use && !has_macro_bang; + let needs_bang = is_fn_like && !is_use_path && !has_macro_bang; let mut item = CompletionItem::new( SymbolKind::from(macro_.kind(completion.db)), diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs index da9e5e20288d..8953f1b2fdf7 100644 --- a/crates/ide-completion/src/tests/pattern.rs +++ b/crates/ide-completion/src/tests/pattern.rs @@ -399,6 +399,7 @@ fn foo() { #[test] fn completes_no_delims_if_existing() { + // FIXME: We should not complete functions here check_empty( r#" struct Bar(u32); @@ -409,7 +410,7 @@ fn foo() { } "#, expect![[r#" - fn foo() fn() + fn foo fn() st Bar bt u32 kw crate:: @@ -427,7 +428,7 @@ fn foo() { } "#, expect![[r#" - fn foo() fn() + fn foo fn() st Foo bt u32 kw crate:: diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 3d702fe8dc23..cdd152ccf566 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -224,6 +224,7 @@ fn completion_item( max_relevance: u32, item: CompletionItem, ) { + let insert_replace_support = config.insert_replace_support().then(|| tdpp.position); let mut additional_text_edits = Vec::new(); // LSP does not allow arbitrary edits in completion, so we have to do a @@ -233,7 +234,6 @@ fn completion_item( let source_range = item.source_range(); for indel in item.text_edit().iter() { if indel.delete.contains_range(source_range) { - let insert_replace_support = config.insert_replace_support().then(|| tdpp.position); text_edit = Some(if indel.delete == source_range { self::completion_text_edit(line_index, insert_replace_support, indel.clone()) } else { @@ -254,6 +254,14 @@ fn completion_item( text_edit.unwrap() }; + let insert_text_format = item.is_snippet().then(|| lsp_types::InsertTextFormat::SNIPPET); + let tags = item.deprecated().then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]); + let command = if item.trigger_call_info() && config.client_commands().trigger_parameter_hints { + Some(command::trigger_parameter_hints()) + } else { + None + }; + let mut lsp_item = lsp_types::CompletionItem { label: item.label().to_string(), detail: item.detail().map(|it| it.to_string()), @@ -263,22 +271,14 @@ fn completion_item( additional_text_edits: Some(additional_text_edits), documentation: item.documentation().map(documentation), deprecated: Some(item.deprecated()), + tags, + command, + insert_text_format, ..Default::default() }; set_score(&mut lsp_item, max_relevance, item.relevance()); - if item.deprecated() { - lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::DEPRECATED]) - } - - if item.trigger_call_info() && config.client_commands().trigger_parameter_hints { - lsp_item.command = Some(command::trigger_parameter_hints()); - } - - if item.is_snippet() { - lsp_item.insert_text_format = Some(lsp_types::InsertTextFormat::SNIPPET); - } if config.completion().enable_imports_on_the_fly { if let imports @ [_, ..] = item.imports_to_add() { let imports: Vec<_> = imports @@ -299,18 +299,17 @@ fn completion_item( } } - if let Some((mutability, relevance)) = item.ref_match() { + if let Some((mutability, offset, relevance)) = item.ref_match() { let mut lsp_item_with_ref = lsp_item.clone(); set_score(&mut lsp_item_with_ref, max_relevance, relevance); lsp_item_with_ref.label = format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label); - if let Some(it) = &mut lsp_item_with_ref.text_edit { - let new_text = match it { - lsp_types::CompletionTextEdit::Edit(it) => &mut it.new_text, - lsp_types::CompletionTextEdit::InsertAndReplace(it) => &mut it.new_text, - }; - *new_text = format!("&{}{}", mutability.as_keyword_for_ref(), new_text); - } + lsp_item_with_ref.additional_text_edits.get_or_insert_with(Default::default).push( + self::text_edit( + line_index, + Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())), + ), + ); acc.push(lsp_item_with_ref); };