mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +00:00
rename completion -> ide_completion
We don't have completion-related PRs in flight, so lets do it
This commit is contained in:
parent
6334ce866a
commit
3db64a400c
37 changed files with 33 additions and 32 deletions
224
crates/ide_completion/src/completions.rs
Normal file
224
crates/ide_completion/src/completions.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
//! This module defines an accumulator for completions which are going to be presented to user.
|
||||
|
||||
pub(crate) mod attribute;
|
||||
pub(crate) mod dot;
|
||||
pub(crate) mod record;
|
||||
pub(crate) mod pattern;
|
||||
pub(crate) mod fn_param;
|
||||
pub(crate) mod keyword;
|
||||
pub(crate) mod snippet;
|
||||
pub(crate) mod qualified_path;
|
||||
pub(crate) mod unqualified_path;
|
||||
pub(crate) mod postfix;
|
||||
pub(crate) mod macro_in_item_position;
|
||||
pub(crate) mod trait_impl;
|
||||
pub(crate) mod mod_;
|
||||
pub(crate) mod flyimport;
|
||||
|
||||
use std::iter;
|
||||
|
||||
use hir::{known, ModPath, ScopeDef, Type};
|
||||
|
||||
use crate::{
|
||||
item::Builder,
|
||||
render::{
|
||||
const_::render_const,
|
||||
enum_variant::render_variant,
|
||||
function::render_fn,
|
||||
macro_::render_macro,
|
||||
pattern::{render_struct_pat, render_variant_pat},
|
||||
render_field, render_resolution, render_tuple_field,
|
||||
type_alias::render_type_alias,
|
||||
RenderContext,
|
||||
},
|
||||
CompletionContext, CompletionItem,
|
||||
};
|
||||
|
||||
/// Represents an in-progress set of completions being built.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Completions {
|
||||
buf: Vec<CompletionItem>,
|
||||
}
|
||||
|
||||
impl Into<Vec<CompletionItem>> for Completions {
|
||||
fn into(self) -> Vec<CompletionItem> {
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Convenience method, which allows to add a freshly created completion into accumulator
|
||||
/// without binding it to the variable.
|
||||
pub(crate) fn add_to(self, acc: &mut Completions) {
|
||||
acc.add(self.build())
|
||||
}
|
||||
}
|
||||
|
||||
impl Completions {
|
||||
pub(crate) fn add(&mut self, item: CompletionItem) {
|
||||
self.buf.push(item.into())
|
||||
}
|
||||
|
||||
pub(crate) fn add_all<I>(&mut self, items: I)
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<CompletionItem>,
|
||||
{
|
||||
items.into_iter().for_each(|item| self.add(item.into()))
|
||||
}
|
||||
|
||||
pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) {
|
||||
let item = render_field(RenderContext::new(ctx), field, ty);
|
||||
self.add(item);
|
||||
}
|
||||
|
||||
pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
|
||||
let item = render_tuple_field(RenderContext::new(ctx), field, ty);
|
||||
self.add(item);
|
||||
}
|
||||
|
||||
pub(crate) fn add_resolution(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
local_name: String,
|
||||
resolution: &ScopeDef,
|
||||
) {
|
||||
if let Some(item) = render_resolution(RenderContext::new(ctx), local_name, resolution) {
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_macro(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
name: Option<String>,
|
||||
macro_: hir::MacroDef,
|
||||
) {
|
||||
let name = match name {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
if let Some(item) = render_macro(RenderContext::new(ctx), None, name, macro_) {
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_function(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
func: hir::Function,
|
||||
local_name: Option<String>,
|
||||
) {
|
||||
if let Some(item) = render_fn(RenderContext::new(ctx), None, local_name, func) {
|
||||
self.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_variant_pat(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
variant: hir::Variant,
|
||||
local_name: Option<hir::Name>,
|
||||
) {
|
||||
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name, None) {
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_qualified_variant_pat(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
variant: hir::Variant,
|
||||
path: ModPath,
|
||||
) {
|
||||
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, None, Some(path)) {
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_struct_pat(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
strukt: hir::Struct,
|
||||
local_name: Option<hir::Name>,
|
||||
) {
|
||||
if let Some(item) = render_struct_pat(RenderContext::new(ctx), strukt, local_name) {
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
|
||||
if let Some(item) = render_const(RenderContext::new(ctx), constant) {
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) {
|
||||
if let Some(item) = render_type_alias(RenderContext::new(ctx), type_alias) {
|
||||
self.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_qualified_enum_variant(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
variant: hir::Variant,
|
||||
path: ModPath,
|
||||
) {
|
||||
let item = render_variant(RenderContext::new(ctx), None, None, variant, Some(path));
|
||||
self.add(item);
|
||||
}
|
||||
|
||||
pub(crate) fn add_enum_variant(
|
||||
&mut self,
|
||||
ctx: &CompletionContext,
|
||||
variant: hir::Variant,
|
||||
local_name: Option<String>,
|
||||
) {
|
||||
let item = render_variant(RenderContext::new(ctx), None, local_name, variant, None);
|
||||
self.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_enum_variants(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
ty: &hir::Type,
|
||||
cb: impl Fn(&mut Completions, &CompletionContext, hir::Variant, hir::ModPath),
|
||||
) {
|
||||
if let Some(hir::Adt::Enum(enum_data)) =
|
||||
iter::successors(Some(ty.clone()), |ty| ty.remove_ref()).last().and_then(|ty| ty.as_adt())
|
||||
{
|
||||
let variants = enum_data.variants(ctx.db);
|
||||
|
||||
let module = if let Some(module) = ctx.scope.module() {
|
||||
// Compute path from the completion site if available.
|
||||
module
|
||||
} else {
|
||||
// Otherwise fall back to the enum's definition site.
|
||||
enum_data.module(ctx.db)
|
||||
};
|
||||
|
||||
if let Some(impl_) = ctx.impl_def.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) {
|
||||
if impl_.target_ty(ctx.db) == *ty {
|
||||
for &variant in &variants {
|
||||
let self_path = hir::ModPath::from_segments(
|
||||
hir::PathKind::Plain,
|
||||
iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))),
|
||||
);
|
||||
cb(acc, ctx, variant, self_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for variant in variants {
|
||||
if let Some(path) = module.find_use_path(ctx.db, hir::ModuleDef::from(variant)) {
|
||||
// Variants with trivial paths are already added by the existing completion logic,
|
||||
// so we should avoid adding these twice
|
||||
if path.segments().len() > 1 {
|
||||
cb(acc, ctx, variant, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
557
crates/ide_completion/src/completions/attribute.rs
Normal file
557
crates/ide_completion/src/completions/attribute.rs
Normal file
|
@ -0,0 +1,557 @@
|
|||
//! Completion for attributes
|
||||
//!
|
||||
//! This module uses a bit of static metadata to provide completions
|
||||
//! for built-in attributes.
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{ast, AstNode, T};
|
||||
|
||||
use crate::{
|
||||
context::CompletionContext,
|
||||
generated_lint_completions::{CLIPPY_LINTS, FEATURES},
|
||||
item::{CompletionItem, CompletionItemKind, CompletionKind},
|
||||
Completions,
|
||||
};
|
||||
|
||||
pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||
if ctx.mod_declaration_under_caret.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let attribute = ctx.attribute_under_caret.as_ref()?;
|
||||
match (attribute.path(), attribute.token_tree()) {
|
||||
(Some(path), Some(token_tree)) => {
|
||||
let path = path.syntax().text();
|
||||
if path == "derive" {
|
||||
complete_derive(acc, ctx, token_tree)
|
||||
} else if path == "feature" {
|
||||
complete_lint(acc, ctx, token_tree, FEATURES)
|
||||
} else if path == "allow" || path == "warn" || path == "deny" || path == "forbid" {
|
||||
complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
|
||||
complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
|
||||
}
|
||||
}
|
||||
(_, Some(_token_tree)) => {}
|
||||
_ => complete_attribute_start(acc, ctx, attribute),
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
|
||||
for attr_completion in ATTRIBUTES {
|
||||
let mut item = CompletionItem::new(
|
||||
CompletionKind::Attribute,
|
||||
ctx.source_range(),
|
||||
attr_completion.label,
|
||||
)
|
||||
.kind(CompletionItemKind::Attribute);
|
||||
|
||||
if let Some(lookup) = attr_completion.lookup {
|
||||
item = item.lookup_by(lookup);
|
||||
}
|
||||
|
||||
if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
|
||||
item = item.insert_snippet(cap, snippet);
|
||||
}
|
||||
|
||||
if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
|
||||
acc.add(item.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AttrCompletion {
|
||||
label: &'static str,
|
||||
lookup: Option<&'static str>,
|
||||
snippet: Option<&'static str>,
|
||||
prefer_inner: bool,
|
||||
}
|
||||
|
||||
impl AttrCompletion {
|
||||
const fn prefer_inner(self) -> AttrCompletion {
|
||||
AttrCompletion { prefer_inner: true, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
const fn attr(
|
||||
label: &'static str,
|
||||
lookup: Option<&'static str>,
|
||||
snippet: Option<&'static str>,
|
||||
) -> AttrCompletion {
|
||||
AttrCompletion { label, lookup, snippet, prefer_inner: false }
|
||||
}
|
||||
|
||||
/// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
|
||||
const ATTRIBUTES: &[AttrCompletion] = &[
|
||||
attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
|
||||
attr("automatically_derived", None, None),
|
||||
attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
|
||||
attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
|
||||
attr("cold", None, None),
|
||||
attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#))
|
||||
.prefer_inner(),
|
||||
attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
|
||||
attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
|
||||
attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
|
||||
attr(
|
||||
r#"export_name = "…""#,
|
||||
Some("export_name"),
|
||||
Some(r#"export_name = "${0:exported_symbol_name}""#),
|
||||
),
|
||||
attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
|
||||
attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
|
||||
attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
|
||||
attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
|
||||
// FIXME: resolve through macro resolution?
|
||||
attr("global_allocator", None, None).prefer_inner(),
|
||||
attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
|
||||
attr("inline", Some("inline"), Some("inline")),
|
||||
attr("link", None, None),
|
||||
attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
|
||||
attr(
|
||||
r#"link_section = "…""#,
|
||||
Some("link_section"),
|
||||
Some(r#"link_section = "${0:section_name}""#),
|
||||
),
|
||||
attr("macro_export", None, None),
|
||||
attr("macro_use", None, None),
|
||||
attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)),
|
||||
attr("no_link", None, None).prefer_inner(),
|
||||
attr("no_implicit_prelude", None, None).prefer_inner(),
|
||||
attr("no_main", None, None).prefer_inner(),
|
||||
attr("no_mangle", None, None),
|
||||
attr("no_std", None, None).prefer_inner(),
|
||||
attr("non_exhaustive", None, None),
|
||||
attr("panic_handler", None, None).prefer_inner(),
|
||||
attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)),
|
||||
attr("proc_macro", None, None),
|
||||
attr("proc_macro_attribute", None, None),
|
||||
attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
|
||||
attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
|
||||
.prefer_inner(),
|
||||
attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
|
||||
attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
|
||||
attr(
|
||||
r#"target_feature = "…""#,
|
||||
Some("target_feature"),
|
||||
Some(r#"target_feature = "${0:feature}""#),
|
||||
),
|
||||
attr("test", None, None),
|
||||
attr("track_caller", None, None),
|
||||
attr("type_length_limit = …", Some("type_length_limit"), Some("type_length_limit = ${0:128}"))
|
||||
.prefer_inner(),
|
||||
attr("used", None, None),
|
||||
attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
|
||||
attr(
|
||||
r#"windows_subsystem = "…""#,
|
||||
Some("windows_subsystem"),
|
||||
Some(r#"windows_subsystem = "${0:subsystem}""#),
|
||||
)
|
||||
.prefer_inner(),
|
||||
];
|
||||
|
||||
fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
|
||||
if let Ok(existing_derives) = parse_comma_sep_input(derive_input) {
|
||||
for derive_completion in DEFAULT_DERIVE_COMPLETIONS
|
||||
.iter()
|
||||
.filter(|completion| !existing_derives.contains(completion.label))
|
||||
{
|
||||
let mut components = vec![derive_completion.label];
|
||||
components.extend(
|
||||
derive_completion
|
||||
.dependencies
|
||||
.iter()
|
||||
.filter(|&&dependency| !existing_derives.contains(dependency)),
|
||||
);
|
||||
let lookup = components.join(", ");
|
||||
let label = components.iter().rev().join(", ");
|
||||
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
|
||||
.lookup_by(lookup)
|
||||
.kind(CompletionItemKind::Attribute)
|
||||
.add_to(acc)
|
||||
}
|
||||
|
||||
for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
|
||||
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), custom_derive_name)
|
||||
.kind(CompletionItemKind::Attribute)
|
||||
.add_to(acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_lint(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
derive_input: ast::TokenTree,
|
||||
lints_completions: &[LintCompletion],
|
||||
) {
|
||||
if let Ok(existing_lints) = parse_comma_sep_input(derive_input) {
|
||||
for lint_completion in lints_completions
|
||||
.into_iter()
|
||||
.filter(|completion| !existing_lints.contains(completion.label))
|
||||
{
|
||||
CompletionItem::new(
|
||||
CompletionKind::Attribute,
|
||||
ctx.source_range(),
|
||||
lint_completion.label,
|
||||
)
|
||||
.kind(CompletionItemKind::Attribute)
|
||||
.detail(lint_completion.description)
|
||||
.add_to(acc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
|
||||
match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
|
||||
(Some(left_paren), Some(right_paren))
|
||||
if left_paren.kind() == T!['('] && right_paren.kind() == T![')'] =>
|
||||
{
|
||||
let mut input_derives = FxHashSet::default();
|
||||
let mut current_derive = String::new();
|
||||
for token in derive_input
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.filter_map(|token| token.into_token())
|
||||
.skip_while(|token| token != &left_paren)
|
||||
.skip(1)
|
||||
.take_while(|token| token != &right_paren)
|
||||
{
|
||||
if T![,] == token.kind() {
|
||||
if !current_derive.is_empty() {
|
||||
input_derives.insert(current_derive);
|
||||
current_derive = String::new();
|
||||
}
|
||||
} else {
|
||||
current_derive.push_str(token.text().trim());
|
||||
}
|
||||
}
|
||||
|
||||
if !current_derive.is_empty() {
|
||||
input_derives.insert(current_derive);
|
||||
}
|
||||
Ok(input_derives)
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
|
||||
let mut result = FxHashSet::default();
|
||||
ctx.scope.process_all_names(&mut |name, scope_def| {
|
||||
if let hir::ScopeDef::MacroDef(mac) = scope_def {
|
||||
if mac.is_derive_macro() {
|
||||
result.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
struct DeriveCompletion {
|
||||
label: &'static str,
|
||||
dependencies: &'static [&'static str],
|
||||
}
|
||||
|
||||
/// Standard Rust derives and the information about their dependencies
|
||||
/// (the dependencies are needed so that the main derive don't break the compilation when added)
|
||||
const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
|
||||
DeriveCompletion { label: "Clone", dependencies: &[] },
|
||||
DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
|
||||
DeriveCompletion { label: "Debug", dependencies: &[] },
|
||||
DeriveCompletion { label: "Default", dependencies: &[] },
|
||||
DeriveCompletion { label: "Hash", dependencies: &[] },
|
||||
DeriveCompletion { label: "PartialEq", dependencies: &[] },
|
||||
DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
|
||||
DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
|
||||
DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
|
||||
];
|
||||
|
||||
pub(crate) struct LintCompletion {
|
||||
pub(crate) label: &'static str,
|
||||
pub(crate) description: &'static str,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[
|
||||
LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# },
|
||||
LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# },
|
||||
LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# },
|
||||
LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# },
|
||||
LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# },
|
||||
LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# },
|
||||
LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# },
|
||||
LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# },
|
||||
LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# },
|
||||
LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# },
|
||||
LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# },
|
||||
LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# },
|
||||
LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# },
|
||||
LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# },
|
||||
LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# },
|
||||
LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# },
|
||||
LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# },
|
||||
LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# },
|
||||
LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# },
|
||||
LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# },
|
||||
LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# },
|
||||
LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# },
|
||||
LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# },
|
||||
LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# },
|
||||
LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# },
|
||||
LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# },
|
||||
LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# },
|
||||
LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# },
|
||||
LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# },
|
||||
LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# },
|
||||
LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# },
|
||||
LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# },
|
||||
LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# },
|
||||
LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# },
|
||||
LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# },
|
||||
LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# },
|
||||
LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# },
|
||||
LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# },
|
||||
LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# },
|
||||
LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# },
|
||||
LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# },
|
||||
LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# },
|
||||
LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# },
|
||||
LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# },
|
||||
LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# },
|
||||
LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# },
|
||||
LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# },
|
||||
LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# },
|
||||
LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# },
|
||||
LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# },
|
||||
LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# },
|
||||
LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# },
|
||||
LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# },
|
||||
LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# },
|
||||
LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# },
|
||||
LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# },
|
||||
LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# },
|
||||
LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# },
|
||||
LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# },
|
||||
LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# },
|
||||
LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# },
|
||||
LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# },
|
||||
LintCompletion { label: "path_statements", description: r#"path statements with no effect"# },
|
||||
LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# },
|
||||
LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# },
|
||||
LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# },
|
||||
LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# },
|
||||
LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# },
|
||||
LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# },
|
||||
LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# },
|
||||
LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# },
|
||||
LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# },
|
||||
LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# },
|
||||
LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# },
|
||||
LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# },
|
||||
LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# },
|
||||
LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# },
|
||||
LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# },
|
||||
LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# },
|
||||
LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# },
|
||||
LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# },
|
||||
LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# },
|
||||
LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# },
|
||||
LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# },
|
||||
LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# },
|
||||
LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# },
|
||||
LintCompletion { label: "unused_imports", description: r#"imports that are never used"# },
|
||||
LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# },
|
||||
LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# },
|
||||
LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# },
|
||||
LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# },
|
||||
LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# },
|
||||
LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# },
|
||||
LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# },
|
||||
LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# },
|
||||
LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# },
|
||||
LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# },
|
||||
LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# },
|
||||
LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# },
|
||||
LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# },
|
||||
LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# },
|
||||
LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# },
|
||||
LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# },
|
||||
LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# },
|
||||
LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# },
|
||||
LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# },
|
||||
LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# },
|
||||
LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# },
|
||||
LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# },
|
||||
LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# },
|
||||
LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# },
|
||||
LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# },
|
||||
LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# },
|
||||
LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# },
|
||||
LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# },
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{test_utils::completion_list, CompletionKind};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Attribute);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_derive_completion() {
|
||||
check(
|
||||
r#"
|
||||
#[derive($0)]
|
||||
struct Test {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
at Clone
|
||||
at Clone, Copy
|
||||
at Debug
|
||||
at Default
|
||||
at Hash
|
||||
at PartialEq
|
||||
at PartialEq, Eq
|
||||
at PartialEq, PartialOrd
|
||||
at PartialEq, Eq, PartialOrd, Ord
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completion_for_incorrect_derive() {
|
||||
check(
|
||||
r#"
|
||||
#[derive{$0)]
|
||||
struct Test {}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_with_input_completion() {
|
||||
check(
|
||||
r#"
|
||||
#[derive(serde::Serialize, PartialEq, $0)]
|
||||
struct Test {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
at Clone
|
||||
at Clone, Copy
|
||||
at Debug
|
||||
at Default
|
||||
at Hash
|
||||
at Eq
|
||||
at PartialOrd
|
||||
at Eq, PartialOrd, Ord
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attribute_completion() {
|
||||
check(
|
||||
r#"#[$0]"#,
|
||||
expect![[r#"
|
||||
at allow(…)
|
||||
at automatically_derived
|
||||
at cfg_attr(…)
|
||||
at cfg(…)
|
||||
at cold
|
||||
at deny(…)
|
||||
at deprecated
|
||||
at derive(…)
|
||||
at export_name = "…"
|
||||
at doc(alias = "…")
|
||||
at doc = "…"
|
||||
at forbid(…)
|
||||
at ignore = "…"
|
||||
at inline
|
||||
at link
|
||||
at link_name = "…"
|
||||
at link_section = "…"
|
||||
at macro_export
|
||||
at macro_use
|
||||
at must_use
|
||||
at no_mangle
|
||||
at non_exhaustive
|
||||
at path = "…"
|
||||
at proc_macro
|
||||
at proc_macro_attribute
|
||||
at proc_macro_derive(…)
|
||||
at repr(…)
|
||||
at should_panic
|
||||
at target_feature = "…"
|
||||
at test
|
||||
at track_caller
|
||||
at used
|
||||
at warn(…)
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attribute_completion_inside_nested_attr() {
|
||||
check(r#"#[cfg($0)]"#, expect![[]])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inner_attribute_completion() {
|
||||
check(
|
||||
r"#![$0]",
|
||||
expect![[r#"
|
||||
at allow(…)
|
||||
at automatically_derived
|
||||
at cfg_attr(…)
|
||||
at cfg(…)
|
||||
at cold
|
||||
at crate_name = ""
|
||||
at deny(…)
|
||||
at deprecated
|
||||
at derive(…)
|
||||
at export_name = "…"
|
||||
at doc(alias = "…")
|
||||
at doc = "…"
|
||||
at feature(…)
|
||||
at forbid(…)
|
||||
at global_allocator
|
||||
at ignore = "…"
|
||||
at inline
|
||||
at link
|
||||
at link_name = "…"
|
||||
at link_section = "…"
|
||||
at macro_export
|
||||
at macro_use
|
||||
at must_use
|
||||
at no_link
|
||||
at no_implicit_prelude
|
||||
at no_main
|
||||
at no_mangle
|
||||
at no_std
|
||||
at non_exhaustive
|
||||
at panic_handler
|
||||
at path = "…"
|
||||
at proc_macro
|
||||
at proc_macro_attribute
|
||||
at proc_macro_derive(…)
|
||||
at recursion_limit = …
|
||||
at repr(…)
|
||||
at should_panic
|
||||
at target_feature = "…"
|
||||
at test
|
||||
at track_caller
|
||||
at type_length_limit = …
|
||||
at used
|
||||
at warn(…)
|
||||
at windows_subsystem = "…"
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
431
crates/ide_completion/src/completions/dot.rs
Normal file
431
crates/ide_completion/src/completions/dot.rs
Normal file
|
@ -0,0 +1,431 @@
|
|||
//! Completes references after dot (fields and method calls).
|
||||
|
||||
use hir::{HasVisibility, Type};
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{context::CompletionContext, Completions};
|
||||
|
||||
/// Complete dot accesses, i.e. fields or methods.
|
||||
pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let dot_receiver = match &ctx.dot_receiver {
|
||||
Some(expr) => expr,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
|
||||
Some(ty) => ty,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if ctx.is_call {
|
||||
mark::hit!(test_no_struct_field_completion_for_method_call);
|
||||
} else {
|
||||
complete_fields(acc, ctx, &receiver_ty);
|
||||
}
|
||||
complete_methods(acc, ctx, &receiver_ty);
|
||||
}
|
||||
|
||||
fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
|
||||
for receiver in receiver.autoderef(ctx.db) {
|
||||
for (field, ty) in receiver.fields(ctx.db) {
|
||||
if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
|
||||
// Skip private field. FIXME: If the definition location of the
|
||||
// field is editable, we should show the completion
|
||||
continue;
|
||||
}
|
||||
acc.add_field(ctx, field, &ty);
|
||||
}
|
||||
for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
|
||||
// FIXME: Handle visibility
|
||||
acc.add_tuple_field(ctx, i, &ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
|
||||
if let Some(krate) = ctx.krate {
|
||||
let mut seen_methods = FxHashSet::default();
|
||||
let traits_in_scope = ctx.scope.traits_in_scope();
|
||||
receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
|
||||
if func.self_param(ctx.db).is_some()
|
||||
&& ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m))
|
||||
&& seen_methods.insert(func.name(ctx.db))
|
||||
{
|
||||
acc.add_function(ctx, func, None);
|
||||
}
|
||||
None::<()>
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{test_utils::completion_list, CompletionKind};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_and_method_completion() {
|
||||
check(
|
||||
r#"
|
||||
struct S { foo: u32 }
|
||||
impl S {
|
||||
fn bar(&self) {}
|
||||
}
|
||||
fn foo(s: S) { s.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd foo u32
|
||||
me bar() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_self() {
|
||||
check(
|
||||
r#"
|
||||
struct S { the_field: (u32,) }
|
||||
impl S {
|
||||
fn foo(self) { self.$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field (u32,)
|
||||
me foo() -> ()
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_autoderef() {
|
||||
check(
|
||||
r#"
|
||||
struct A { the_field: (u32, i32) }
|
||||
impl A {
|
||||
fn foo(&self) { self.$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field (u32, i32)
|
||||
me foo() -> ()
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_struct_field_completion_for_method_call() {
|
||||
mark::check!(test_no_struct_field_completion_for_method_call);
|
||||
check(
|
||||
r#"
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) { a.$0() }
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_visibility_filtering() {
|
||||
check(
|
||||
r#"
|
||||
mod inner {
|
||||
pub struct A {
|
||||
private_field: u32,
|
||||
pub pub_field: u32,
|
||||
pub(crate) crate_field: u32,
|
||||
pub(crate) super_field: u32,
|
||||
}
|
||||
}
|
||||
fn foo(a: inner::A) { a.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd pub_field u32
|
||||
fd crate_field u32
|
||||
fd super_field u32
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
struct A {}
|
||||
mod m {
|
||||
impl super::A {
|
||||
fn private_method(&self) {}
|
||||
pub(crate) fn the_method(&self) {}
|
||||
}
|
||||
}
|
||||
fn foo(a: A) { a.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
me the_method() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_union_field_completion() {
|
||||
check(
|
||||
r#"
|
||||
union U { field: u8, other: u16 }
|
||||
fn foo(u: U) { u.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd field u8
|
||||
fd other u16
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_completion_only_fitting_impls() {
|
||||
check(
|
||||
r#"
|
||||
struct A<T> {}
|
||||
impl A<u32> {
|
||||
fn the_method(&self) {}
|
||||
}
|
||||
impl A<i32> {
|
||||
fn the_other_method(&self) {}
|
||||
}
|
||||
fn foo(a: A<u32>) { a.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
me the_method() -> ()
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trait_method_completion() {
|
||||
check(
|
||||
r#"
|
||||
struct A {}
|
||||
trait Trait { fn the_method(&self); }
|
||||
impl Trait for A {}
|
||||
fn foo(a: A) { a.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
me the_method() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trait_method_completion_deduplicated() {
|
||||
check(
|
||||
r"
|
||||
struct A {}
|
||||
trait Trait { fn the_method(&self); }
|
||||
impl<T> Trait for T {}
|
||||
fn foo(a: &A) { a.$0 }
|
||||
",
|
||||
expect![[r#"
|
||||
me the_method() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_trait_method_from_other_module() {
|
||||
check(
|
||||
r"
|
||||
struct A {}
|
||||
mod m {
|
||||
pub trait Trait { fn the_method(&self); }
|
||||
}
|
||||
use m::Trait;
|
||||
impl Trait for A {}
|
||||
fn foo(a: A) { a.$0 }
|
||||
",
|
||||
expect![[r#"
|
||||
me the_method() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_non_self_method() {
|
||||
check(
|
||||
r#"
|
||||
struct A {}
|
||||
impl A {
|
||||
fn the_method() {}
|
||||
}
|
||||
fn foo(a: A) {
|
||||
a.$0
|
||||
}
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_field_completion() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
let b = (0, 3.14);
|
||||
b.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd 0 i32
|
||||
fd 1 f64
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_field_inference() {
|
||||
check(
|
||||
r#"
|
||||
pub struct S;
|
||||
impl S { pub fn blah(&self) {} }
|
||||
|
||||
struct T(S);
|
||||
|
||||
impl T {
|
||||
fn foo(&self) {
|
||||
// FIXME: This doesn't work without the trailing `a` as `0.` is a float
|
||||
self.0.a$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me blah() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_works_in_consts() {
|
||||
check(
|
||||
r#"
|
||||
struct A { the_field: u32 }
|
||||
const X: u32 = {
|
||||
A { the_field: 92 }.$0
|
||||
};
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_in_simple_macro_1() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
m!(a.x$0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_in_simple_macro_2() {
|
||||
// this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
m!(a.$0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_in_simple_macro_recursive_1() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
m!(m!(m!(a.x$0)))
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_expansion_resilient() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! d {
|
||||
() => {};
|
||||
($val:expr) => {
|
||||
match $val { tmp => { tmp } }
|
||||
};
|
||||
// Trailing comma with single argument is ignored
|
||||
($val:expr,) => { $crate::d!($val) };
|
||||
($($val:expr),+ $(,)?) => {
|
||||
($($crate::d!($val)),+,)
|
||||
};
|
||||
}
|
||||
struct A { the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
d!(a.$0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_completion_issue_3547() {
|
||||
check(
|
||||
r#"
|
||||
struct HashSet<T> {}
|
||||
impl<T> HashSet<T> {
|
||||
pub fn the_method(&self) {}
|
||||
}
|
||||
fn foo() {
|
||||
let s: HashSet<_>;
|
||||
s.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me the_method() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_method_call_when_receiver_is_a_macro_call() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl S { fn foo(&self) {} }
|
||||
macro_rules! make_s { () => { S }; }
|
||||
fn main() { make_s!().f$0; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
me foo() -> ()
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
688
crates/ide_completion/src/completions/flyimport.rs
Normal file
688
crates/ide_completion/src/completions/flyimport.rs
Normal file
|
@ -0,0 +1,688 @@
|
|||
//! Feature: completion with imports-on-the-fly
|
||||
//!
|
||||
//! When completing names in the current scope, proposes additional imports from other modules or crates,
|
||||
//! if they can be qualified in the scope and their name contains all symbols from the completion input
|
||||
//! (case-insensitive, in any order or places).
|
||||
//!
|
||||
//! ```
|
||||
//! fn main() {
|
||||
//! pda$0
|
||||
//! }
|
||||
//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
|
||||
//! ```
|
||||
//! ->
|
||||
//! ```
|
||||
//! use std::marker::PhantomData;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! PhantomData
|
||||
//! }
|
||||
//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
|
||||
//! ```
|
||||
//!
|
||||
//! Also completes associated items, that require trait imports.
|
||||
//!
|
||||
//! .Fuzzy search details
|
||||
//!
|
||||
//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
|
||||
//! (i.e. in `HashMap` in the `std::collections::HashMap` path).
|
||||
//! For the same reasons, avoids searching for any path imports for inputs with their length less that 2 symbols
|
||||
//! (but shows all associated items for any input length).
|
||||
//!
|
||||
//! .Import configuration
|
||||
//!
|
||||
//! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
|
||||
//! Mimics the corresponding behavior of the `Auto Import` feature.
|
||||
//!
|
||||
//! .LSP and performance implications
|
||||
//!
|
||||
//! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
|
||||
//! (case sensitive) resolve client capability in its client capabilities.
|
||||
//! This way the server is able to defer the costly computations, doing them for a selected completion item only.
|
||||
//! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
|
||||
//! which might be slow ergo the feature is automatically disabled.
|
||||
//!
|
||||
//! .Feature toggle
|
||||
//!
|
||||
//! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
|
||||
//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
|
||||
//! capability enabled.
|
||||
|
||||
use hir::{AsAssocItem, ModPath, ScopeDef};
|
||||
use ide_db::helpers::{
|
||||
import_assets::{ImportAssets, ImportCandidate},
|
||||
insert_use::ImportScope,
|
||||
};
|
||||
use syntax::{AstNode, SyntaxNode, T};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
context::CompletionContext,
|
||||
render::{render_resolution_with_import, RenderContext},
|
||||
ImportEdit,
|
||||
};
|
||||
|
||||
use super::Completions;
|
||||
|
||||
pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||
if !ctx.config.enable_imports_on_the_fly {
|
||||
return None;
|
||||
}
|
||||
if ctx.use_item_syntax.is_some()
|
||||
|| ctx.attribute_under_caret.is_some()
|
||||
|| ctx.mod_declaration_under_caret.is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let potential_import_name = {
|
||||
let token_kind = ctx.token.kind();
|
||||
if matches!(token_kind, T![.] | T![::]) {
|
||||
String::new()
|
||||
} else {
|
||||
ctx.token.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
|
||||
|
||||
let user_input_lowercased = potential_import_name.to_lowercase();
|
||||
let import_assets = import_assets(ctx, potential_import_name)?;
|
||||
let import_scope = ImportScope::find_insert_use_container(
|
||||
position_for_import(ctx, Some(import_assets.import_candidate()))?,
|
||||
&ctx.sema,
|
||||
)?;
|
||||
let mut all_mod_paths = import_assets
|
||||
.search_for_relative_paths(&ctx.sema)
|
||||
.into_iter()
|
||||
.map(|(mod_path, item_in_ns)| {
|
||||
let scope_item = match item_in_ns {
|
||||
hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
|
||||
hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
|
||||
hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
|
||||
};
|
||||
(mod_path, scope_item)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
|
||||
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
|
||||
});
|
||||
|
||||
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
|
||||
let import_for_trait_assoc_item = match definition {
|
||||
ScopeDef::ModuleDef(module_def) => module_def
|
||||
.as_assoc_item(ctx.db)
|
||||
.and_then(|assoc| assoc.containing_trait(ctx.db))
|
||||
.is_some(),
|
||||
_ => false,
|
||||
};
|
||||
let import_edit = ImportEdit {
|
||||
import_path,
|
||||
import_scope: import_scope.clone(),
|
||||
import_for_trait_assoc_item,
|
||||
};
|
||||
render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
|
||||
}));
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(crate) fn position_for_import<'a>(
|
||||
ctx: &'a CompletionContext,
|
||||
import_candidate: Option<&ImportCandidate>,
|
||||
) -> Option<&'a SyntaxNode> {
|
||||
Some(match import_candidate {
|
||||
Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(),
|
||||
Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(),
|
||||
Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver.as_ref()?.syntax(),
|
||||
None => ctx
|
||||
.name_ref_syntax
|
||||
.as_ref()
|
||||
.map(|name_ref| name_ref.syntax())
|
||||
.or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax()))
|
||||
.or_else(|| ctx.dot_receiver.as_ref().map(|expr| expr.syntax()))?,
|
||||
})
|
||||
}
|
||||
|
||||
fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
|
||||
let current_module = ctx.scope.module()?;
|
||||
if let Some(dot_receiver) = &ctx.dot_receiver {
|
||||
ImportAssets::for_fuzzy_method_call(
|
||||
current_module,
|
||||
ctx.sema.type_of_expr(dot_receiver)?,
|
||||
fuzzy_name,
|
||||
)
|
||||
} else {
|
||||
let fuzzy_name_length = fuzzy_name.len();
|
||||
let assets_for_path = ImportAssets::for_fuzzy_path(
|
||||
current_module,
|
||||
ctx.path_qual.clone(),
|
||||
fuzzy_name,
|
||||
&ctx.sema,
|
||||
);
|
||||
|
||||
if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
|
||||
&& fuzzy_name_length < 2
|
||||
{
|
||||
mark::hit!(ignore_short_input_for_path);
|
||||
None
|
||||
} else {
|
||||
assets_for_path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_fuzzy_completion_order_key(
|
||||
proposed_mod_path: &ModPath,
|
||||
user_input_lowercased: &str,
|
||||
) -> usize {
|
||||
mark::hit!(certain_fuzzy_order_test);
|
||||
let proposed_import_name = match proposed_mod_path.segments().last() {
|
||||
Some(name) => name.to_string().to_lowercase(),
|
||||
None => return usize::MAX,
|
||||
};
|
||||
match proposed_import_name.match_indices(user_input_lowercased).next() {
|
||||
Some((first_matching_index, _)) => first_matching_index,
|
||||
None => usize::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
item::CompletionKind,
|
||||
test_utils::{check_edit, completion_list},
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Magic);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_fuzzy_completion() {
|
||||
check_edit(
|
||||
"stdin",
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod io {
|
||||
pub fn stdin() {}
|
||||
};
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
stdi$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use dep::io::stdin;
|
||||
|
||||
fn main() {
|
||||
stdin()$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_fuzzy_completion() {
|
||||
check_edit(
|
||||
"macro_with_curlies!",
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
/// Please call me as macro_with_curlies! {}
|
||||
#[macro_export]
|
||||
macro_rules! macro_with_curlies {
|
||||
() => {}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
curli$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use dep::macro_with_curlies;
|
||||
|
||||
fn main() {
|
||||
macro_with_curlies! {$0}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_fuzzy_completion() {
|
||||
check_edit(
|
||||
"ThirdStruct",
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub struct FirstStruct;
|
||||
pub mod some_module {
|
||||
pub struct SecondStruct;
|
||||
pub struct ThirdStruct;
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
use dep::{FirstStruct, some_module::SecondStruct};
|
||||
|
||||
fn main() {
|
||||
this$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
|
||||
|
||||
fn main() {
|
||||
ThirdStruct
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_paths_are_ignored() {
|
||||
mark::check!(ignore_short_input_for_path);
|
||||
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub struct FirstStruct;
|
||||
pub mod some_module {
|
||||
pub struct SecondStruct;
|
||||
pub struct ThirdStruct;
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
use dep::{FirstStruct, some_module::SecondStruct};
|
||||
|
||||
fn main() {
|
||||
t$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzy_completions_come_in_specific_order() {
|
||||
mark::check!(certain_fuzzy_order_test);
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub struct FirstStruct;
|
||||
pub mod some_module {
|
||||
// already imported, omitted
|
||||
pub struct SecondStruct;
|
||||
// does not contain all letters from the query, omitted
|
||||
pub struct UnrelatedOne;
|
||||
// contains all letters from the query, but not in sequence, displayed last
|
||||
pub struct ThiiiiiirdStruct;
|
||||
// contains all letters from the query, but not in the beginning, displayed second
|
||||
pub struct AfterThirdStruct;
|
||||
// contains all letters from the query in the begginning, displayed first
|
||||
pub struct ThirdStruct;
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
use dep::{FirstStruct, some_module::SecondStruct};
|
||||
|
||||
fn main() {
|
||||
hir$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
st dep::some_module::ThirdStruct
|
||||
st dep::some_module::AfterThirdStruct
|
||||
st dep::some_module::ThiiiiiirdStruct
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_function_fuzzy_completion() {
|
||||
let fixture = r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::wei$0
|
||||
}
|
||||
"#;
|
||||
|
||||
check(
|
||||
fixture,
|
||||
expect![[r#"
|
||||
fn weird_function() (dep::test_mod::TestTrait) -> ()
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"weird_function",
|
||||
fixture,
|
||||
r#"
|
||||
use dep::test_mod::TestTrait;
|
||||
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::weird_function()$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_const_fuzzy_completion() {
|
||||
let fixture = r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::spe$0
|
||||
}
|
||||
"#;
|
||||
|
||||
check(
|
||||
fixture,
|
||||
expect![[r#"
|
||||
ct SPECIAL_CONST (dep::test_mod::TestTrait)
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"SPECIAL_CONST",
|
||||
fixture,
|
||||
r#"
|
||||
use dep::test_mod::TestTrait;
|
||||
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::SPECIAL_CONST
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_method_fuzzy_completion() {
|
||||
let fixture = r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
let test_struct = dep::test_mod::TestStruct {};
|
||||
test_struct.ran$0
|
||||
}
|
||||
"#;
|
||||
|
||||
check(
|
||||
fixture,
|
||||
expect![[r#"
|
||||
me random_method() (dep::test_mod::TestTrait) -> ()
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"random_method",
|
||||
fixture,
|
||||
r#"
|
||||
use dep::test_mod::TestTrait;
|
||||
|
||||
fn main() {
|
||||
let test_struct = dep::test_mod::TestStruct {};
|
||||
test_struct.random_method()$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_trait_type_fuzzy_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::hum$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_propose_names_in_scope() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
use dep::test_mod::TestStruct;
|
||||
fn main() {
|
||||
TestSt$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_propose_traits_in_scope() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
use dep::test_mod::{TestStruct, TestTrait};
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::hum$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blanket_trait_impl_import() {
|
||||
check_edit(
|
||||
"another_function",
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
pub struct TestStruct {}
|
||||
pub trait TestTrait {
|
||||
fn another_function();
|
||||
}
|
||||
impl<T> TestTrait for T {
|
||||
fn another_function() {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::ano$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
use dep::test_mod::TestTrait;
|
||||
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::another_function()$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_input_deprecated_assoc_item_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
#[deprecated]
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
let test_struct = dep::test_mod::TestStruct {};
|
||||
test_struct.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
me random_method() (dep::test_mod::TestTrait) -> () DEPRECATED
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod test_mod {
|
||||
#[deprecated]
|
||||
pub trait TestTrait {
|
||||
const SPECIAL_CONST: u8;
|
||||
type HumbleType;
|
||||
fn weird_function();
|
||||
fn random_method(&self);
|
||||
}
|
||||
pub struct TestStruct {}
|
||||
impl TestTrait for TestStruct {
|
||||
const SPECIAL_CONST: u8 = 42;
|
||||
type HumbleType = ();
|
||||
fn weird_function() {}
|
||||
fn random_method(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
dep::test_mod::TestStruct::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
|
||||
fn weird_function() (dep::test_mod::TestTrait) -> () DEPRECATED
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_use_statements() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod io {
|
||||
pub fn stdin() {}
|
||||
};
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
use stdi$0
|
||||
|
||||
fn main() {}
|
||||
"#,
|
||||
expect![[]],
|
||||
);
|
||||
}
|
||||
}
|
135
crates/ide_completion/src/completions/fn_param.rs
Normal file
135
crates/ide_completion/src/completions/fn_param.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
//! See `complete_fn_param`.
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{
|
||||
ast::{self, ModuleItemOwner},
|
||||
match_ast, AstNode,
|
||||
};
|
||||
|
||||
use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
|
||||
|
||||
/// Complete repeated parameters, both name and type. For example, if all
|
||||
/// functions in a file have a `spam: &mut Spam` parameter, a completion with
|
||||
/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
|
||||
/// suggested.
|
||||
pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !ctx.is_param {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut params = FxHashMap::default();
|
||||
|
||||
let me = ctx.token.ancestors().find_map(ast::Fn::cast);
|
||||
let mut process_fn = |func: ast::Fn| {
|
||||
if Some(&func) == me.as_ref() {
|
||||
return;
|
||||
}
|
||||
func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
|
||||
let text = param.syntax().text().to_string();
|
||||
params.entry(text).or_insert(param);
|
||||
})
|
||||
};
|
||||
|
||||
for node in ctx.token.parent().ancestors() {
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::SourceFile(it) => it.items().filter_map(|item| match item {
|
||||
ast::Item::Fn(it) => Some(it),
|
||||
_ => None,
|
||||
}).for_each(&mut process_fn),
|
||||
ast::ItemList(it) => it.items().filter_map(|item| match item {
|
||||
ast::Item::Fn(it) => Some(it),
|
||||
_ => None,
|
||||
}).for_each(&mut process_fn),
|
||||
ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
|
||||
ast::AssocItem::Fn(it) => Some(it),
|
||||
_ => None,
|
||||
}).for_each(&mut process_fn),
|
||||
_ => continue,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
params
|
||||
.into_iter()
|
||||
.filter_map(|(label, param)| {
|
||||
let lookup = param.pat()?.syntax().text().to_string();
|
||||
Some((label, lookup))
|
||||
})
|
||||
.for_each(|(label, lookup)| {
|
||||
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
|
||||
.kind(CompletionItemKind::Binding)
|
||||
.lookup_by(lookup)
|
||||
.add_to(acc)
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{test_utils::completion_list, CompletionKind};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Magic);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_completion_last_param() {
|
||||
check(
|
||||
r#"
|
||||
fn foo(file_id: FileId) {}
|
||||
fn bar(file_id: FileId) {}
|
||||
fn baz(file$0) {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn file_id: FileId
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_completion_nth_param() {
|
||||
check(
|
||||
r#"
|
||||
fn foo(file_id: FileId) {}
|
||||
fn baz(file$0, x: i32) {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn file_id: FileId
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_completion_trait_param() {
|
||||
check(
|
||||
r#"
|
||||
pub(crate) trait SourceRoot {
|
||||
pub fn contains(&self, file_id: FileId) -> bool;
|
||||
pub fn module_map(&self) -> &ModuleMap;
|
||||
pub fn lines(&self, file_id: FileId) -> &LineIndex;
|
||||
pub fn syntax(&self, file$0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn file_id: FileId
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_param_in_inner_function() {
|
||||
check(
|
||||
r#"
|
||||
fn outer(text: String) {
|
||||
fn inner($0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn text: String
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
668
crates/ide_completion/src/completions/keyword.rs
Normal file
668
crates/ide_completion/src/completions/keyword.rs
Normal file
|
@ -0,0 +1,668 @@
|
|||
//! Completes keywords.
|
||||
|
||||
use syntax::SyntaxKind;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
|
||||
|
||||
pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
// complete keyword "crate" in use stmt
|
||||
let source_range = ctx.source_range();
|
||||
|
||||
if ctx.use_item_syntax.is_some() {
|
||||
if ctx.path_qual.is_none() {
|
||||
CompletionItem::new(CompletionKind::Keyword, source_range, "crate::")
|
||||
.kind(CompletionItemKind::Keyword)
|
||||
.insert_text("crate::")
|
||||
.add_to(acc);
|
||||
}
|
||||
CompletionItem::new(CompletionKind::Keyword, source_range, "self")
|
||||
.kind(CompletionItemKind::Keyword)
|
||||
.add_to(acc);
|
||||
CompletionItem::new(CompletionKind::Keyword, source_range, "super::")
|
||||
.kind(CompletionItemKind::Keyword)
|
||||
.insert_text("super::")
|
||||
.add_to(acc);
|
||||
}
|
||||
|
||||
// Suggest .await syntax for types that implement Future trait
|
||||
if let Some(receiver) = &ctx.dot_receiver {
|
||||
if let Some(ty) = ctx.sema.type_of_expr(receiver) {
|
||||
if ty.impls_future(ctx.db) {
|
||||
CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await")
|
||||
.kind(CompletionItemKind::Keyword)
|
||||
.detail("expr.await")
|
||||
.insert_text("await")
|
||||
.add_to(acc);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if ctx.token.kind() == SyntaxKind::COMMENT {
|
||||
mark::hit!(no_keyword_completion_in_comments);
|
||||
return;
|
||||
}
|
||||
if ctx.record_lit_syntax.is_some() {
|
||||
mark::hit!(no_keyword_completion_in_record_lit);
|
||||
return;
|
||||
}
|
||||
|
||||
let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent;
|
||||
if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling {
|
||||
add_keyword(ctx, acc, "where", "where ");
|
||||
return;
|
||||
}
|
||||
if ctx.unsafe_is_prev {
|
||||
if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "fn", "fn $0() {}")
|
||||
}
|
||||
|
||||
if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "trait", "trait $0 {}");
|
||||
add_keyword(ctx, acc, "impl", "impl $0 {}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent
|
||||
{
|
||||
add_keyword(ctx, acc, "fn", "fn $0() {}");
|
||||
}
|
||||
if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "use", "use ");
|
||||
add_keyword(ctx, acc, "impl", "impl $0 {}");
|
||||
add_keyword(ctx, acc, "trait", "trait $0 {}");
|
||||
}
|
||||
|
||||
if ctx.has_item_list_or_source_file_parent {
|
||||
add_keyword(ctx, acc, "enum", "enum $0 {}");
|
||||
add_keyword(ctx, acc, "struct", "struct $0");
|
||||
add_keyword(ctx, acc, "union", "union $0 {}");
|
||||
}
|
||||
|
||||
if ctx.is_expr {
|
||||
add_keyword(ctx, acc, "match", "match $0 {}");
|
||||
add_keyword(ctx, acc, "while", "while $0 {}");
|
||||
add_keyword(ctx, acc, "loop", "loop {$0}");
|
||||
add_keyword(ctx, acc, "if", "if $0 {}");
|
||||
add_keyword(ctx, acc, "if let", "if let $1 = $0 {}");
|
||||
add_keyword(ctx, acc, "for", "for $1 in $0 {}");
|
||||
}
|
||||
|
||||
if ctx.if_is_prev || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "let", "let ");
|
||||
}
|
||||
|
||||
if ctx.after_if {
|
||||
add_keyword(ctx, acc, "else", "else {$0}");
|
||||
add_keyword(ctx, acc, "else if", "else if $0 {}");
|
||||
}
|
||||
if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "mod", "mod $0");
|
||||
}
|
||||
if ctx.bind_pat_parent || ctx.ref_pat_parent {
|
||||
add_keyword(ctx, acc, "mut", "mut ");
|
||||
}
|
||||
if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent
|
||||
{
|
||||
add_keyword(ctx, acc, "const", "const ");
|
||||
add_keyword(ctx, acc, "type", "type ");
|
||||
}
|
||||
if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "static", "static ");
|
||||
};
|
||||
if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent {
|
||||
add_keyword(ctx, acc, "extern", "extern ");
|
||||
}
|
||||
if ctx.has_item_list_or_source_file_parent
|
||||
|| has_trait_or_impl_parent
|
||||
|| ctx.block_expr_parent
|
||||
|| ctx.is_match_arm
|
||||
{
|
||||
add_keyword(ctx, acc, "unsafe", "unsafe ");
|
||||
}
|
||||
if ctx.in_loop_body {
|
||||
if ctx.can_be_stmt {
|
||||
add_keyword(ctx, acc, "continue", "continue;");
|
||||
add_keyword(ctx, acc, "break", "break;");
|
||||
} else {
|
||||
add_keyword(ctx, acc, "continue", "continue");
|
||||
add_keyword(ctx, acc, "break", "break");
|
||||
}
|
||||
}
|
||||
if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent | ctx.has_field_list_parent {
|
||||
add_keyword(ctx, acc, "pub(crate)", "pub(crate) ");
|
||||
add_keyword(ctx, acc, "pub", "pub ");
|
||||
}
|
||||
|
||||
if !ctx.is_trivial_path {
|
||||
return;
|
||||
}
|
||||
let fn_def = match &ctx.function_syntax {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
|
||||
add_keyword(
|
||||
ctx,
|
||||
acc,
|
||||
"return",
|
||||
match (ctx.can_be_stmt, fn_def.ret_type().is_some()) {
|
||||
(true, true) => "return $0;",
|
||||
(true, false) => "return;",
|
||||
(false, true) => "return $0",
|
||||
(false, false) => "return",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
|
||||
let builder = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
|
||||
.kind(CompletionItemKind::Keyword);
|
||||
let builder = match ctx.config.snippet_cap {
|
||||
Some(cap) => {
|
||||
let tmp;
|
||||
let snippet = if snippet.ends_with('}') && ctx.incomplete_let {
|
||||
mark::hit!(let_semi);
|
||||
tmp = format!("{};", snippet);
|
||||
&tmp
|
||||
} else {
|
||||
snippet
|
||||
};
|
||||
builder.insert_snippet(cap, snippet)
|
||||
}
|
||||
None => builder.insert_text(if snippet.contains('$') { kw } else { snippet }),
|
||||
};
|
||||
acc.add(builder.build());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, completion_list},
|
||||
CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Keyword);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_in_use_stmt() {
|
||||
check(
|
||||
r"use $0",
|
||||
expect![[r#"
|
||||
kw crate::
|
||||
kw self
|
||||
kw super::
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"use a::$0",
|
||||
expect![[r#"
|
||||
kw self
|
||||
kw super::
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"use a::{b, $0}",
|
||||
expect![[r#"
|
||||
kw self
|
||||
kw super::
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_at_source_file_level() {
|
||||
check(
|
||||
r"m$0",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw enum
|
||||
kw struct
|
||||
kw union
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_in_function() {
|
||||
check(
|
||||
r"fn quux() { $0 }",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_inside_block() {
|
||||
check(
|
||||
r"fn quux() { if true { $0 } }",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_after_if() {
|
||||
check(
|
||||
r#"fn quux() { if true { () } $0 }"#,
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw else
|
||||
kw else if
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
check_edit(
|
||||
"else",
|
||||
r#"fn quux() { if true { () } $0 }"#,
|
||||
r#"fn quux() { if true { () } else {$0} }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_in_match_arm() {
|
||||
check(
|
||||
r#"
|
||||
fn quux() -> i32 {
|
||||
match () { () => $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw unsafe
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_in_trait_def() {
|
||||
check(
|
||||
r"trait My { $0 }",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw unsafe
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_in_impl_def() {
|
||||
check(
|
||||
r"impl My { $0 }",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
kw unsafe
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_in_loop() {
|
||||
check(
|
||||
r"fn my() { loop { $0 } }",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw use
|
||||
kw impl
|
||||
kw trait
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw let
|
||||
kw mod
|
||||
kw const
|
||||
kw type
|
||||
kw static
|
||||
kw extern
|
||||
kw unsafe
|
||||
kw continue
|
||||
kw break
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_after_unsafe_in_item_list() {
|
||||
check(
|
||||
r"unsafe $0",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw trait
|
||||
kw impl
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keywords_after_unsafe_in_block_expr() {
|
||||
check(
|
||||
r"fn my_fn() { unsafe $0 }",
|
||||
expect![[r#"
|
||||
kw fn
|
||||
kw trait
|
||||
kw impl
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mut_in_ref_and_in_fn_parameters_list() {
|
||||
check(
|
||||
r"fn my_fn(&$0) {}",
|
||||
expect![[r#"
|
||||
kw mut
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r"fn my_fn($0) {}",
|
||||
expect![[r#"
|
||||
kw mut
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r"fn my_fn() { let &$0 }",
|
||||
expect![[r#"
|
||||
kw mut
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_where_keyword() {
|
||||
check(
|
||||
r"trait A $0",
|
||||
expect![[r#"
|
||||
kw where
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r"impl A $0",
|
||||
expect![[r#"
|
||||
kw where
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_keyword_completion_in_comments() {
|
||||
mark::check!(no_keyword_completion_in_comments);
|
||||
check(
|
||||
r#"
|
||||
fn test() {
|
||||
let x = 2; // A comment$0
|
||||
}
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
/*
|
||||
Some multi-line comment$0
|
||||
*/
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
/// Some doc comment
|
||||
/// let test$0 = 1
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_await_impls_future() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
use std::future::*;
|
||||
struct A {}
|
||||
impl Future for A {}
|
||||
fn foo(a: A) { a.$0 }
|
||||
|
||||
//- /std/lib.rs crate:std
|
||||
pub mod future {
|
||||
#[lang = "future_trait"]
|
||||
pub trait Future {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw await expr.await
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
use std::future::*;
|
||||
fn foo() {
|
||||
let a = async {};
|
||||
a.$0
|
||||
}
|
||||
|
||||
//- /std/lib.rs crate:std
|
||||
pub mod future {
|
||||
#[lang = "future_trait"]
|
||||
pub trait Future {
|
||||
type Output;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw await expr.await
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_let() {
|
||||
check(
|
||||
r#"fn main() { let _ = $0 }"#,
|
||||
expect![[r#"
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw return
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn before_field() {
|
||||
check(
|
||||
r#"
|
||||
struct Foo {
|
||||
$0
|
||||
pub f: i32,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw pub(crate)
|
||||
kw pub
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_struct_initializer() {
|
||||
mark::check!(no_keyword_completion_in_record_lit);
|
||||
check(
|
||||
r#"
|
||||
struct Foo {
|
||||
pub f: i32,
|
||||
}
|
||||
fn foo() {
|
||||
Foo {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_initializer_field_expr() {
|
||||
check(
|
||||
r#"
|
||||
struct Foo {
|
||||
pub f: i32,
|
||||
}
|
||||
fn foo() {
|
||||
Foo {
|
||||
f: $0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
kw match
|
||||
kw while
|
||||
kw loop
|
||||
kw if
|
||||
kw if let
|
||||
kw for
|
||||
kw return
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_semi() {
|
||||
mark::check!(let_semi);
|
||||
check_edit(
|
||||
"match",
|
||||
r#"
|
||||
fn main() { let x = $0 }
|
||||
"#,
|
||||
r#"
|
||||
fn main() { let x = match $0 {}; }
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"if",
|
||||
r#"
|
||||
fn main() {
|
||||
let x = $0
|
||||
let y = 92;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = if $0 {};
|
||||
let y = 92;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"loop",
|
||||
r#"
|
||||
fn main() {
|
||||
let x = $0
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = loop {$0};
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
//! Completes macro invocations used in item position.
|
||||
|
||||
use crate::{CompletionContext, Completions};
|
||||
|
||||
pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
// Show only macros in top level.
|
||||
if ctx.is_new_item {
|
||||
ctx.scope.process_all_names(&mut |name, res| {
|
||||
if let hir::ScopeDef::MacroDef(mac) = res {
|
||||
acc.add_macro(ctx, Some(name.to_string()), mac);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{test_utils::completion_list, CompletionKind};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_macros_as_item() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo { () => {} }
|
||||
fn foo() {}
|
||||
|
||||
$0
|
||||
"#,
|
||||
expect![[r#"
|
||||
ma foo!(…) macro_rules! foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
315
crates/ide_completion/src/completions/mod_.rs
Normal file
315
crates/ide_completion/src/completions/mod_.rs
Normal file
|
@ -0,0 +1,315 @@
|
|||
//! Completes mod declarations.
|
||||
|
||||
use std::iter;
|
||||
|
||||
use hir::{Module, ModuleSource};
|
||||
use ide_db::{
|
||||
base_db::{SourceDatabaseExt, VfsPath},
|
||||
RootDatabase, SymbolKind,
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::CompletionItem;
|
||||
|
||||
use crate::{context::CompletionContext, item::CompletionKind, Completions};
|
||||
|
||||
/// Complete mod declaration, i.e. `mod $0 ;`
|
||||
pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||
let mod_under_caret = match &ctx.mod_declaration_under_caret {
|
||||
Some(mod_under_caret) if mod_under_caret.item_list().is_none() => mod_under_caret,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let _p = profile::span("completion::complete_mod");
|
||||
|
||||
let current_module = ctx.scope.module()?;
|
||||
|
||||
let module_definition_file =
|
||||
current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
|
||||
let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
|
||||
let directory_to_look_for_submodules = directory_to_look_for_submodules(
|
||||
current_module,
|
||||
ctx.db,
|
||||
source_root.path_for_file(&module_definition_file)?,
|
||||
)?;
|
||||
|
||||
let existing_mod_declarations = current_module
|
||||
.children(ctx.db)
|
||||
.filter_map(|module| Some(module.name(ctx.db)?.to_string()))
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
let module_declaration_file =
|
||||
current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
|
||||
module_declaration_source_file.file_id.original_file(ctx.db)
|
||||
});
|
||||
|
||||
source_root
|
||||
.iter()
|
||||
.filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
|
||||
.filter(|submodule_candidate_file| {
|
||||
Some(submodule_candidate_file) != module_declaration_file.as_ref()
|
||||
})
|
||||
.filter_map(|submodule_file| {
|
||||
let submodule_path = source_root.path_for_file(&submodule_file)?;
|
||||
let directory_with_submodule = submodule_path.parent()?;
|
||||
let (name, ext) = submodule_path.name_and_extension()?;
|
||||
if ext != Some("rs") {
|
||||
return None;
|
||||
}
|
||||
match name {
|
||||
"lib" | "main" => None,
|
||||
"mod" => {
|
||||
if directory_with_submodule.parent()? == directory_to_look_for_submodules {
|
||||
match directory_with_submodule.name_and_extension()? {
|
||||
(directory_name, None) => Some(directory_name.to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
file_name if directory_with_submodule == directory_to_look_for_submodules => {
|
||||
Some(file_name.to_owned())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.filter(|name| !existing_mod_declarations.contains(name))
|
||||
.for_each(|submodule_name| {
|
||||
let mut label = submodule_name;
|
||||
if mod_under_caret.semicolon_token().is_none() {
|
||||
label.push(';');
|
||||
}
|
||||
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
|
||||
.kind(SymbolKind::Module)
|
||||
.add_to(acc)
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn directory_to_look_for_submodules(
|
||||
module: Module,
|
||||
db: &RootDatabase,
|
||||
module_file_path: &VfsPath,
|
||||
) -> Option<VfsPath> {
|
||||
let directory_with_module_path = module_file_path.parent()?;
|
||||
let (name, ext) = module_file_path.name_and_extension()?;
|
||||
if ext != Some("rs") {
|
||||
return None;
|
||||
}
|
||||
let base_directory = match name {
|
||||
"mod" | "lib" | "main" => Some(directory_with_module_path),
|
||||
regular_rust_file_name => {
|
||||
if matches!(
|
||||
(
|
||||
directory_with_module_path
|
||||
.parent()
|
||||
.as_ref()
|
||||
.and_then(|path| path.name_and_extension()),
|
||||
directory_with_module_path.name_and_extension(),
|
||||
),
|
||||
(Some(("src", None)), Some(("bin", None)))
|
||||
) {
|
||||
// files in /src/bin/ can import each other directly
|
||||
Some(directory_with_module_path)
|
||||
} else {
|
||||
directory_with_module_path.join(regular_rust_file_name)
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
||||
module_chain_to_containing_module_file(module, db)
|
||||
.into_iter()
|
||||
.filter_map(|module| module.name(db))
|
||||
.try_fold(base_directory, |path, name| path.join(&name.to_string()))
|
||||
}
|
||||
|
||||
fn module_chain_to_containing_module_file(
|
||||
current_module: Module,
|
||||
db: &RootDatabase,
|
||||
) -> Vec<Module> {
|
||||
let mut path =
|
||||
iter::successors(Some(current_module), |current_module| current_module.parent(db))
|
||||
.take_while(|current_module| {
|
||||
matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
path.reverse();
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{test_utils::completion_list, CompletionKind};
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Magic);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lib_module_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod $0
|
||||
//- /foo.rs
|
||||
fn foo() {}
|
||||
//- /foo/ignored_foo.rs
|
||||
fn ignored_foo() {}
|
||||
//- /bar/mod.rs
|
||||
fn bar() {}
|
||||
//- /bar/ignored_bar.rs
|
||||
fn ignored_bar() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md foo;
|
||||
md bar;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_module_completion_with_module_body() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod $0 {
|
||||
|
||||
}
|
||||
//- /foo.rs
|
||||
fn foo() {}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_module_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs
|
||||
mod $0
|
||||
//- /foo.rs
|
||||
fn foo() {}
|
||||
//- /foo/ignored_foo.rs
|
||||
fn ignored_foo() {}
|
||||
//- /bar/mod.rs
|
||||
fn bar() {}
|
||||
//- /bar/ignored_bar.rs
|
||||
fn ignored_bar() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md foo;
|
||||
md bar;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_test_module_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs
|
||||
mod tests {
|
||||
mod $0;
|
||||
}
|
||||
//- /tests/foo.rs
|
||||
fn foo() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directly_nested_module_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
//- /foo.rs
|
||||
mod $0;
|
||||
//- /foo/bar.rs
|
||||
fn bar() {}
|
||||
//- /foo/bar/ignored_bar.rs
|
||||
fn ignored_bar() {}
|
||||
//- /foo/baz/mod.rs
|
||||
fn baz() {}
|
||||
//- /foo/moar/ignored_moar.rs
|
||||
fn ignored_moar() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md bar
|
||||
md baz
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_in_source_module_completion() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
//- /foo.rs
|
||||
mod bar {
|
||||
mod $0
|
||||
}
|
||||
//- /foo/bar/baz.rs
|
||||
fn baz() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md baz;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
// FIXME binary modules are not supported in tests properly
|
||||
// Binary modules are a bit special, they allow importing the modules from `/src/bin`
|
||||
// and that's why are good to test two things:
|
||||
// * no cycles are allowed in mod declarations
|
||||
// * no modules from the parent directory are proposed
|
||||
// Unfortunately, binary modules support is in cargo not rustc,
|
||||
// hence the test does not work now
|
||||
//
|
||||
// #[test]
|
||||
// fn regular_bin_module_completion() {
|
||||
// check(
|
||||
// r#"
|
||||
// //- /src/bin.rs
|
||||
// fn main() {}
|
||||
// //- /src/bin/foo.rs
|
||||
// mod $0
|
||||
// //- /src/bin/bar.rs
|
||||
// fn bar() {}
|
||||
// //- /src/bin/bar/bar_ignored.rs
|
||||
// fn bar_ignored() {}
|
||||
// "#,
|
||||
// expect![[r#"
|
||||
// md bar;
|
||||
// "#]],foo
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn already_declared_bin_module_completion_omitted() {
|
||||
check(
|
||||
r#"
|
||||
//- /src/bin.rs crate:main
|
||||
fn main() {}
|
||||
//- /src/bin/foo.rs
|
||||
mod $0
|
||||
//- /src/bin/bar.rs
|
||||
mod foo;
|
||||
fn bar() {}
|
||||
//- /src/bin/bar/bar_ignored.rs
|
||||
fn bar_ignored() {}
|
||||
"#,
|
||||
expect![[r#""#]],
|
||||
);
|
||||
}
|
||||
}
|
317
crates/ide_completion/src/completions/pattern.rs
Normal file
317
crates/ide_completion/src/completions/pattern.rs
Normal file
|
@ -0,0 +1,317 @@
|
|||
//! Completes constats and paths in patterns.
|
||||
|
||||
use crate::{CompletionContext, Completions};
|
||||
|
||||
/// Completes constants and paths in patterns.
|
||||
pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) {
|
||||
return;
|
||||
}
|
||||
if ctx.record_pat_syntax.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ty) = &ctx.expected_type {
|
||||
super::complete_enum_variants(acc, ctx, ty, |acc, ctx, variant, path| {
|
||||
acc.add_qualified_variant_pat(ctx, variant, path)
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: ideally, we should look at the type we are matching against and
|
||||
// suggest variants + auto-imports
|
||||
ctx.scope.process_all_names(&mut |name, res| {
|
||||
let add_resolution = match &res {
|
||||
hir::ScopeDef::ModuleDef(def) => match def {
|
||||
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
|
||||
acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone()));
|
||||
true
|
||||
}
|
||||
hir::ModuleDef::Variant(variant) if !ctx.is_irrefutable_pat_binding => {
|
||||
acc.add_variant_pat(ctx, variant.clone(), Some(name.clone()));
|
||||
true
|
||||
}
|
||||
hir::ModuleDef::Adt(hir::Adt::Enum(..))
|
||||
| hir::ModuleDef::Variant(..)
|
||||
| hir::ModuleDef::Const(..)
|
||||
| hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding,
|
||||
_ => false,
|
||||
},
|
||||
hir::ScopeDef::MacroDef(_) => true,
|
||||
hir::ScopeDef::ImplSelfType(impl_) => match impl_.target_ty(ctx.db).as_adt() {
|
||||
Some(hir::Adt::Struct(strukt)) => {
|
||||
acc.add_struct_pat(ctx, strukt, Some(name.clone()));
|
||||
true
|
||||
}
|
||||
Some(hir::Adt::Enum(_)) => !ctx.is_irrefutable_pat_binding,
|
||||
_ => true,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if add_resolution {
|
||||
acc.add_resolution(ctx, name.to_string(), &res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, completion_list},
|
||||
CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
fn check_snippet(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Snippet);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variants_and_modules() {
|
||||
check(
|
||||
r#"
|
||||
enum E { X }
|
||||
use self::E::X;
|
||||
const Z: E = E::X;
|
||||
mod m {}
|
||||
|
||||
static FOO: E = E::X;
|
||||
struct Bar { f: u32 }
|
||||
|
||||
fn foo() {
|
||||
match E::X { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
en E
|
||||
ct Z
|
||||
st Bar
|
||||
ev X
|
||||
md m
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_simple_macro_call() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
enum E { X }
|
||||
|
||||
fn foo() {
|
||||
m!(match E::X { $0 })
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
en E
|
||||
ma m!(…) macro_rules! m
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_irrefutable_let() {
|
||||
check(
|
||||
r#"
|
||||
enum E { X }
|
||||
use self::E::X;
|
||||
const Z: E = E::X;
|
||||
mod m {}
|
||||
|
||||
static FOO: E = E::X;
|
||||
struct Bar { f: u32 }
|
||||
|
||||
fn foo() {
|
||||
let $0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Bar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_param() {
|
||||
check(
|
||||
r#"
|
||||
enum E { X }
|
||||
|
||||
static FOO: E = E::X;
|
||||
struct Bar { f: u32 }
|
||||
|
||||
fn foo($0) {
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Bar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_pat_in_let() {
|
||||
check_snippet(
|
||||
r#"
|
||||
struct Bar { f: u32 }
|
||||
|
||||
fn foo() {
|
||||
let $0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Bar Bar { f$1 }$0
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_param_pattern() {
|
||||
check_snippet(
|
||||
r#"
|
||||
struct Foo { bar: String, baz: String }
|
||||
struct Bar(String, String);
|
||||
struct Baz;
|
||||
fn outer($0) {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Foo Foo { bar$1, baz$2 }: Foo$0
|
||||
bn Bar Bar($1, $2): Bar$0
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_let_pattern() {
|
||||
check_snippet(
|
||||
r#"
|
||||
struct Foo { bar: String, baz: String }
|
||||
struct Bar(String, String);
|
||||
struct Baz;
|
||||
fn outer() {
|
||||
let $0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Foo Foo { bar$1, baz$2 }$0
|
||||
bn Bar Bar($1, $2)$0
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_refutable_pattern() {
|
||||
check_snippet(
|
||||
r#"
|
||||
struct Foo { bar: i32, baz: i32 }
|
||||
struct Bar(String, String);
|
||||
struct Baz;
|
||||
fn outer() {
|
||||
match () {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Foo Foo { bar$1, baz$2 }$0
|
||||
bn Bar Bar($1, $2)$0
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omits_private_fields_pat() {
|
||||
check_snippet(
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Foo { pub bar: i32, baz: i32 }
|
||||
pub struct Bar(pub String, String);
|
||||
pub struct Invisible(String, String);
|
||||
}
|
||||
use foo::*;
|
||||
|
||||
fn outer() {
|
||||
match () {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Foo Foo { bar$1, .. }$0
|
||||
bn Bar Bar($1, ..)$0
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_shows_ident_completion() {
|
||||
check_edit(
|
||||
"Foo",
|
||||
r#"
|
||||
struct Foo(i32);
|
||||
fn main() {
|
||||
match Foo(92) {
|
||||
$0(92) => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo(i32);
|
||||
fn main() {
|
||||
match Foo(92) {
|
||||
Foo(92) => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_self_pats() {
|
||||
check_snippet(
|
||||
r#"
|
||||
struct Foo(i32);
|
||||
impl Foo {
|
||||
fn foo() {
|
||||
match () {
|
||||
$0
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Self Self($1)$0
|
||||
bn Foo Foo($1)$0
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_qualified_variant() {
|
||||
check_snippet(
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar { baz: i32 }
|
||||
}
|
||||
impl Foo {
|
||||
fn foo() {
|
||||
match {Foo::Bar { baz: 0 }} {
|
||||
B$0
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn Self::Bar Self::Bar { baz$1 }$0
|
||||
bn Foo::Bar Foo::Bar { baz$1 }$0
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
565
crates/ide_completion/src/completions/postfix.rs
Normal file
565
crates/ide_completion/src/completions/postfix.rs
Normal file
|
@ -0,0 +1,565 @@
|
|||
//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
|
||||
|
||||
mod format_like;
|
||||
|
||||
use ide_db::{helpers::SnippetCap, ty_filter::TryEnum};
|
||||
use syntax::{
|
||||
ast::{self, AstNode, AstToken},
|
||||
SyntaxKind::{BLOCK_EXPR, EXPR_STMT},
|
||||
TextRange, TextSize,
|
||||
};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{
|
||||
completions::postfix::format_like::add_format_like_completions,
|
||||
context::CompletionContext,
|
||||
item::{Builder, CompletionKind},
|
||||
CompletionItem, CompletionItemKind, Completions,
|
||||
};
|
||||
|
||||
pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !ctx.config.enable_postfix_completions {
|
||||
return;
|
||||
}
|
||||
|
||||
let dot_receiver = match &ctx.dot_receiver {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let receiver_text =
|
||||
get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
|
||||
|
||||
let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let ref_removed_ty =
|
||||
std::iter::successors(Some(receiver_ty.clone()), |ty| ty.remove_ref()).last().unwrap();
|
||||
|
||||
let cap = match ctx.config.snippet_cap {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
let try_enum = TryEnum::from_ty(&ctx.sema, &ref_removed_ty);
|
||||
if let Some(try_enum) = &try_enum {
|
||||
match try_enum {
|
||||
TryEnum::Result => {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"ifl",
|
||||
"if let Ok {}",
|
||||
&format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"while",
|
||||
"while let Ok {}",
|
||||
&format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
}
|
||||
TryEnum::Option => {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"ifl",
|
||||
"if let Some {}",
|
||||
&format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"while",
|
||||
"while let Some {}",
|
||||
&format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
} else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"if",
|
||||
"if expr {}",
|
||||
&format!("if {} {{\n $0\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"while",
|
||||
"while expr {}",
|
||||
&format!("while {} {{\n $0\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
|
||||
.add_to(acc);
|
||||
}
|
||||
|
||||
postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
|
||||
.add_to(acc);
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"refm",
|
||||
"&mut expr",
|
||||
&format!("&mut {}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
// The rest of the postfix completions create an expression that moves an argument,
|
||||
// so it's better to consider references now to avoid breaking the compilation
|
||||
let dot_receiver = include_references(dot_receiver);
|
||||
let receiver_text =
|
||||
get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
|
||||
|
||||
match try_enum {
|
||||
Some(try_enum) => match try_enum {
|
||||
TryEnum::Result => {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"match",
|
||||
"match expr {}",
|
||||
&format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
}
|
||||
TryEnum::Option => {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"match",
|
||||
"match expr {}",
|
||||
&format!(
|
||||
"match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
|
||||
receiver_text
|
||||
),
|
||||
)
|
||||
.add_to(acc);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"match",
|
||||
"match expr {}",
|
||||
&format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"box",
|
||||
"Box::new(expr)",
|
||||
&format!("Box::new({})", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(ctx, cap, &dot_receiver, "ok", "Ok(expr)", &format!("Ok({})", receiver_text))
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"some",
|
||||
"Some(expr)",
|
||||
&format!("Some({})", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"dbg",
|
||||
"dbg!(expr)",
|
||||
&format!("dbg!({})", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"dbgr",
|
||||
"dbg!(&expr)",
|
||||
&format!("dbg!(&{})", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"call",
|
||||
"function(expr)",
|
||||
&format!("${{1}}({})", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
|
||||
if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
|
||||
if matches!(parent.kind(), BLOCK_EXPR | EXPR_STMT) {
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"let",
|
||||
"let",
|
||||
&format!("let $0 = {};", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
postfix_snippet(
|
||||
ctx,
|
||||
cap,
|
||||
&dot_receiver,
|
||||
"letm",
|
||||
"let mut",
|
||||
&format!("let mut $0 = {};", receiver_text),
|
||||
)
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
|
||||
if let ast::Expr::Literal(literal) = dot_receiver.clone() {
|
||||
if let Some(literal_text) = ast::String::cast(literal.token()) {
|
||||
add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
|
||||
if receiver_is_ambiguous_float_literal {
|
||||
let text = receiver.syntax().text();
|
||||
let without_dot = ..text.len() - TextSize::of('.');
|
||||
text.slice(without_dot).to_string()
|
||||
} else {
|
||||
receiver.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn include_references(initial_element: &ast::Expr) -> ast::Expr {
|
||||
let mut resulting_element = initial_element.clone();
|
||||
while let Some(parent_ref_element) =
|
||||
resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
|
||||
{
|
||||
resulting_element = ast::Expr::from(parent_ref_element);
|
||||
}
|
||||
resulting_element
|
||||
}
|
||||
|
||||
fn postfix_snippet(
|
||||
ctx: &CompletionContext,
|
||||
cap: SnippetCap,
|
||||
receiver: &ast::Expr,
|
||||
label: &str,
|
||||
detail: &str,
|
||||
snippet: &str,
|
||||
) -> Builder {
|
||||
let edit = {
|
||||
let receiver_syntax = receiver.syntax();
|
||||
let receiver_range = ctx.sema.original_range(receiver_syntax).range;
|
||||
let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
|
||||
TextEdit::replace(delete_range, snippet.to_string())
|
||||
};
|
||||
CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
|
||||
.detail(detail)
|
||||
.kind(CompletionItemKind::Snippet)
|
||||
.snippet_edit(cap, edit)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, completion_list},
|
||||
CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Postfix);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postfix_completion_works_for_trivial_path_expression() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
let bar = true;
|
||||
bar.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
sn if if expr {}
|
||||
sn while while expr {}
|
||||
sn not !expr
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn match match expr {}
|
||||
sn box Box::new(expr)
|
||||
sn ok Ok(expr)
|
||||
sn some Some(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn call function(expr)
|
||||
sn let let
|
||||
sn letm let mut
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postfix_completion_works_for_function_calln() {
|
||||
check(
|
||||
r#"
|
||||
fn foo(elt: bool) -> bool {
|
||||
!elt
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let bar = true;
|
||||
foo(bar.$0)
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
sn if if expr {}
|
||||
sn while while expr {}
|
||||
sn not !expr
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn match match expr {}
|
||||
sn box Box::new(expr)
|
||||
sn ok Ok(expr)
|
||||
sn some Some(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn call function(expr)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postfix_type_filtering() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
let bar: u8 = 12;
|
||||
bar.$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn match match expr {}
|
||||
sn box Box::new(expr)
|
||||
sn ok Ok(expr)
|
||||
sn some Some(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn call function(expr)
|
||||
sn let let
|
||||
sn letm let mut
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_middle_block() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
baz.l$0
|
||||
res
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
sn if if expr {}
|
||||
sn while while expr {}
|
||||
sn not !expr
|
||||
sn ref &expr
|
||||
sn refm &mut expr
|
||||
sn match match expr {}
|
||||
sn box Box::new(expr)
|
||||
sn ok Ok(expr)
|
||||
sn some Some(expr)
|
||||
sn dbg dbg!(expr)
|
||||
sn dbgr dbg!(&expr)
|
||||
sn call function(expr)
|
||||
sn let let
|
||||
sn letm let mut
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_iflet() {
|
||||
check_edit(
|
||||
"ifl",
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main() {
|
||||
let bar = Option::Some(true);
|
||||
bar.$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main() {
|
||||
let bar = Option::Some(true);
|
||||
if let Some($1) = bar {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_match() {
|
||||
check_edit(
|
||||
"match",
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
|
||||
fn main() {
|
||||
let bar = Result::Ok(true);
|
||||
bar.$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
|
||||
fn main() {
|
||||
let bar = Result::Ok(true);
|
||||
match bar {
|
||||
Ok(${1:_}) => {$2},
|
||||
Err(${3:_}) => {$0},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postfix_completion_works_for_ambiguous_float_literal() {
|
||||
check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_in_simple_macro() {
|
||||
check_edit(
|
||||
"dbg",
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
fn main() {
|
||||
let bar: u8 = 12;
|
||||
m!(bar.d$0)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
fn main() {
|
||||
let bar: u8 = 12;
|
||||
m!(dbg!(bar))
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postfix_completion_for_references() {
|
||||
check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
|
||||
check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
|
||||
check_edit(
|
||||
"ifl",
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main() {
|
||||
let bar = &Option::Some(true);
|
||||
bar.$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main() {
|
||||
let bar = &Option::Some(true);
|
||||
if let Some($1) = bar {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postfix_completion_for_format_like_strings() {
|
||||
check_edit(
|
||||
"format",
|
||||
r#"fn main() { "{some_var:?}".$0 }"#,
|
||||
r#"fn main() { format!("{:?}", some_var) }"#,
|
||||
);
|
||||
check_edit(
|
||||
"panic",
|
||||
r#"fn main() { "Panic with {a}".$0 }"#,
|
||||
r#"fn main() { panic!("Panic with {}", a) }"#,
|
||||
);
|
||||
check_edit(
|
||||
"println",
|
||||
r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
|
||||
r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
|
||||
);
|
||||
check_edit(
|
||||
"loge",
|
||||
r#"fn main() { "{2+2}".$0 }"#,
|
||||
r#"fn main() { log::error!("{}", 2+2) }"#,
|
||||
);
|
||||
check_edit(
|
||||
"logt",
|
||||
r#"fn main() { "{2+2}".$0 }"#,
|
||||
r#"fn main() { log::trace!("{}", 2+2) }"#,
|
||||
);
|
||||
check_edit(
|
||||
"logd",
|
||||
r#"fn main() { "{2+2}".$0 }"#,
|
||||
r#"fn main() { log::debug!("{}", 2+2) }"#,
|
||||
);
|
||||
check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
|
||||
check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
|
||||
check_edit(
|
||||
"loge",
|
||||
r#"fn main() { "{2+2}".$0 }"#,
|
||||
r#"fn main() { log::error!("{}", 2+2) }"#,
|
||||
);
|
||||
}
|
||||
}
|
287
crates/ide_completion/src/completions/postfix/format_like.rs
Normal file
287
crates/ide_completion/src/completions/postfix/format_like.rs
Normal file
|
@ -0,0 +1,287 @@
|
|||
// Feature: Format String Completion.
|
||||
//
|
||||
// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
|
||||
//
|
||||
// The following postfix snippets are available:
|
||||
//
|
||||
// - `format` -> `format!(...)`
|
||||
// - `panic` -> `panic!(...)`
|
||||
// - `println` -> `println!(...)`
|
||||
// - `log`:
|
||||
// + `logd` -> `log::debug!(...)`
|
||||
// + `logt` -> `log::trace!(...)`
|
||||
// + `logi` -> `log::info!(...)`
|
||||
// + `logw` -> `log::warn!(...)`
|
||||
// + `loge` -> `log::error!(...)`
|
||||
|
||||
use ide_db::helpers::SnippetCap;
|
||||
use syntax::ast::{self, AstToken};
|
||||
|
||||
use crate::{completions::postfix::postfix_snippet, context::CompletionContext, Completions};
|
||||
|
||||
/// Mapping ("postfix completion item" => "macro to use")
|
||||
static KINDS: &[(&str, &str)] = &[
|
||||
("format", "format!"),
|
||||
("panic", "panic!"),
|
||||
("println", "println!"),
|
||||
("eprintln", "eprintln!"),
|
||||
("logd", "log::debug!"),
|
||||
("logt", "log::trace!"),
|
||||
("logi", "log::info!"),
|
||||
("logw", "log::warn!"),
|
||||
("loge", "log::error!"),
|
||||
];
|
||||
|
||||
pub(crate) fn add_format_like_completions(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
dot_receiver: &ast::Expr,
|
||||
cap: SnippetCap,
|
||||
receiver_text: &ast::String,
|
||||
) {
|
||||
let input = match string_literal_contents(receiver_text) {
|
||||
// It's not a string literal, do not parse input.
|
||||
Some(input) => input,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut parser = FormatStrParser::new(input);
|
||||
|
||||
if parser.parse().is_ok() {
|
||||
for (label, macro_name) in KINDS {
|
||||
let snippet = parser.into_suggestion(macro_name);
|
||||
|
||||
postfix_snippet(ctx, cap, &dot_receiver, label, macro_name, &snippet).add_to(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether provided item is a string literal.
|
||||
fn string_literal_contents(item: &ast::String) -> Option<String> {
|
||||
let item = item.text();
|
||||
if item.len() >= 2 && item.starts_with("\"") && item.ends_with("\"") {
|
||||
return Some(item[1..item.len() - 1].to_owned());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Parser for a format-like string. It is more allowing in terms of string contents,
|
||||
/// as we expect variable placeholders to be filled with expressions.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FormatStrParser {
|
||||
input: String,
|
||||
output: String,
|
||||
extracted_expressions: Vec<String>,
|
||||
state: State,
|
||||
parsed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum State {
|
||||
NotExpr,
|
||||
MaybeExpr,
|
||||
Expr,
|
||||
MaybeIncorrect,
|
||||
FormatOpts,
|
||||
}
|
||||
|
||||
impl FormatStrParser {
|
||||
pub(crate) fn new(input: String) -> Self {
|
||||
Self {
|
||||
input: input.into(),
|
||||
output: String::new(),
|
||||
extracted_expressions: Vec::new(),
|
||||
state: State::NotExpr,
|
||||
parsed: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse(&mut self) -> Result<(), ()> {
|
||||
let mut current_expr = String::new();
|
||||
|
||||
let mut placeholder_id = 1;
|
||||
|
||||
// Count of open braces inside of an expression.
|
||||
// We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
|
||||
// "{MyStruct { val_a: 0, val_b: 1 }}".
|
||||
let mut inexpr_open_count = 0;
|
||||
|
||||
let mut chars = self.input.chars().peekable();
|
||||
while let Some(chr) = chars.next() {
|
||||
match (self.state, chr) {
|
||||
(State::NotExpr, '{') => {
|
||||
self.output.push(chr);
|
||||
self.state = State::MaybeExpr;
|
||||
}
|
||||
(State::NotExpr, '}') => {
|
||||
self.output.push(chr);
|
||||
self.state = State::MaybeIncorrect;
|
||||
}
|
||||
(State::NotExpr, _) => {
|
||||
self.output.push(chr);
|
||||
}
|
||||
(State::MaybeIncorrect, '}') => {
|
||||
// It's okay, we met "}}".
|
||||
self.output.push(chr);
|
||||
self.state = State::NotExpr;
|
||||
}
|
||||
(State::MaybeIncorrect, _) => {
|
||||
// Error in the string.
|
||||
return Err(());
|
||||
}
|
||||
(State::MaybeExpr, '{') => {
|
||||
self.output.push(chr);
|
||||
self.state = State::NotExpr;
|
||||
}
|
||||
(State::MaybeExpr, '}') => {
|
||||
// This is an empty sequence '{}'. Replace it with placeholder.
|
||||
self.output.push(chr);
|
||||
self.extracted_expressions.push(format!("${}", placeholder_id));
|
||||
placeholder_id += 1;
|
||||
self.state = State::NotExpr;
|
||||
}
|
||||
(State::MaybeExpr, _) => {
|
||||
current_expr.push(chr);
|
||||
self.state = State::Expr;
|
||||
}
|
||||
(State::Expr, '}') => {
|
||||
if inexpr_open_count == 0 {
|
||||
self.output.push(chr);
|
||||
self.extracted_expressions.push(current_expr.trim().into());
|
||||
current_expr = String::new();
|
||||
self.state = State::NotExpr;
|
||||
} else {
|
||||
// We're closing one brace met before inside of the expression.
|
||||
current_expr.push(chr);
|
||||
inexpr_open_count -= 1;
|
||||
}
|
||||
}
|
||||
(State::Expr, ':') if chars.peek().copied() == Some(':') => {
|
||||
// path seperator
|
||||
current_expr.push_str("::");
|
||||
chars.next();
|
||||
}
|
||||
(State::Expr, ':') => {
|
||||
if inexpr_open_count == 0 {
|
||||
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
|
||||
self.output.push(chr);
|
||||
self.extracted_expressions.push(current_expr.trim().into());
|
||||
current_expr = String::new();
|
||||
self.state = State::FormatOpts;
|
||||
} else {
|
||||
// We're inside of braced expression, assume that it's a struct field name/value delimeter.
|
||||
current_expr.push(chr);
|
||||
}
|
||||
}
|
||||
(State::Expr, '{') => {
|
||||
current_expr.push(chr);
|
||||
inexpr_open_count += 1;
|
||||
}
|
||||
(State::Expr, _) => {
|
||||
current_expr.push(chr);
|
||||
}
|
||||
(State::FormatOpts, '}') => {
|
||||
self.output.push(chr);
|
||||
self.state = State::NotExpr;
|
||||
}
|
||||
(State::FormatOpts, _) => {
|
||||
self.output.push(chr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.state != State::NotExpr {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.parsed = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn into_suggestion(&self, macro_name: &str) -> String {
|
||||
assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
|
||||
|
||||
let expressions_as_string = self.extracted_expressions.join(", ");
|
||||
format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
fn check(input: &str, expect: &Expect) {
|
||||
let mut parser = FormatStrParser::new((*input).to_owned());
|
||||
let outcome_repr = if parser.parse().is_ok() {
|
||||
// Parsing should be OK, expected repr is "string; expr_1, expr_2".
|
||||
if parser.extracted_expressions.is_empty() {
|
||||
parser.output
|
||||
} else {
|
||||
format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
|
||||
}
|
||||
} else {
|
||||
// Parsing should fail, expected repr is "-".
|
||||
"-".to_owned()
|
||||
};
|
||||
|
||||
expect.assert_eq(&outcome_repr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_str_parser() {
|
||||
let test_vector = &[
|
||||
("no expressions", expect![["no expressions"]]),
|
||||
("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
|
||||
("{expr:?}", expect![["{:?}; expr"]]),
|
||||
("{malformed", expect![["-"]]),
|
||||
("malformed}", expect![["-"]]),
|
||||
("{{correct", expect![["{{correct"]]),
|
||||
("correct}}", expect![["correct}}"]]),
|
||||
("{correct}}}", expect![["{}}}; correct"]]),
|
||||
("{correct}}}}}", expect![["{}}}}}; correct"]]),
|
||||
("{incorrect}}", expect![["-"]]),
|
||||
("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
|
||||
("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
|
||||
(
|
||||
"{SomeStruct { val_a: 0, val_b: 1 }}",
|
||||
expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
|
||||
),
|
||||
("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
|
||||
(
|
||||
"{SomeStruct { val_a: 0, val_b: 1 }:?}",
|
||||
expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
|
||||
),
|
||||
("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
|
||||
("{strsim::jaro_winkle(a)}", expect![["{}; strsim::jaro_winkle(a)"]]),
|
||||
("{foo::bar::baz()}", expect![["{}; foo::bar::baz()"]]),
|
||||
("{foo::bar():?}", expect![["{:?}; foo::bar()"]]),
|
||||
];
|
||||
|
||||
for (input, output) in test_vector {
|
||||
check(input, output)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_suggestion() {
|
||||
let test_vector = &[
|
||||
("println!", "{}", r#"println!("{}", $1)"#),
|
||||
("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
|
||||
(
|
||||
"log::info!",
|
||||
"{} {expr} {} {2 + 2}",
|
||||
r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#,
|
||||
),
|
||||
("format!", "{expr:?}", r#"format!("{:?}", expr)"#),
|
||||
];
|
||||
|
||||
for (kind, input, output) in test_vector {
|
||||
let mut parser = FormatStrParser::new((*input).to_owned());
|
||||
parser.parse().expect("Parsing must succeed");
|
||||
|
||||
assert_eq!(&parser.into_suggestion(*kind), output);
|
||||
}
|
||||
}
|
||||
}
|
815
crates/ide_completion/src/completions/qualified_path.rs
Normal file
815
crates/ide_completion/src/completions/qualified_path.rs
Normal file
|
@ -0,0 +1,815 @@
|
|||
//! Completion of paths, i.e. `some::prefix::$0`.
|
||||
|
||||
use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::AstNode;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{CompletionContext, Completions};
|
||||
|
||||
pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let path = match &ctx.path_qual {
|
||||
Some(path) => path.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let context_module = ctx.scope.module();
|
||||
|
||||
let resolution = match ctx.sema.resolve_path(&path) {
|
||||
Some(res) => res,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Add associated types on type parameters and `Self`.
|
||||
resolution.assoc_type_shorthand_candidates(ctx.db, |alias| {
|
||||
acc.add_type_alias(ctx, alias);
|
||||
None::<()>
|
||||
});
|
||||
|
||||
match resolution {
|
||||
PathResolution::Def(hir::ModuleDef::Module(module)) => {
|
||||
let module_scope = module.scope(ctx.db, context_module);
|
||||
for (name, def) in module_scope {
|
||||
if ctx.use_item_syntax.is_some() {
|
||||
if let ScopeDef::Unknown = def {
|
||||
if let Some(name_ref) = ctx.name_ref_syntax.as_ref() {
|
||||
if name_ref.syntax().text() == name.to_string().as_str() {
|
||||
// for `use self::foo$0`, don't suggest `foo` as a completion
|
||||
mark::hit!(dont_complete_current_use);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
acc.add_resolution(ctx, name.to_string(), &def);
|
||||
}
|
||||
}
|
||||
PathResolution::Def(def @ hir::ModuleDef::Adt(_))
|
||||
| PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_))
|
||||
| PathResolution::Def(def @ hir::ModuleDef::BuiltinType(_)) => {
|
||||
if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
|
||||
for variant in e.variants(ctx.db) {
|
||||
acc.add_enum_variant(ctx, variant, None);
|
||||
}
|
||||
}
|
||||
let ty = match def {
|
||||
hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
|
||||
hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
|
||||
hir::ModuleDef::BuiltinType(builtin) => {
|
||||
let module = match ctx.scope.module() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
builtin.ty(ctx.db, module)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
|
||||
// (where AssocType is defined on a trait, not an inherent impl)
|
||||
|
||||
let krate = ctx.krate;
|
||||
if let Some(krate) = krate {
|
||||
let traits_in_scope = ctx.scope.traits_in_scope();
|
||||
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
|
||||
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
|
||||
return None;
|
||||
}
|
||||
match item {
|
||||
hir::AssocItem::Function(func) => {
|
||||
acc.add_function(ctx, func, None);
|
||||
}
|
||||
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
|
||||
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
|
||||
}
|
||||
None::<()>
|
||||
});
|
||||
|
||||
// Iterate assoc types separately
|
||||
ty.iterate_assoc_items(ctx.db, krate, |item| {
|
||||
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
|
||||
return None;
|
||||
}
|
||||
match item {
|
||||
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
|
||||
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
|
||||
}
|
||||
None::<()>
|
||||
});
|
||||
}
|
||||
}
|
||||
PathResolution::Def(hir::ModuleDef::Trait(t)) => {
|
||||
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
|
||||
for item in t.items(ctx.db) {
|
||||
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
hir::AssocItem::Function(func) => {
|
||||
acc.add_function(ctx, func, None);
|
||||
}
|
||||
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
|
||||
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
PathResolution::TypeParam(_) | PathResolution::SelfType(_) => {
|
||||
if let Some(krate) = ctx.krate {
|
||||
let ty = match resolution {
|
||||
PathResolution::TypeParam(param) => param.ty(ctx.db),
|
||||
PathResolution::SelfType(impl_def) => impl_def.target_ty(ctx.db),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let Some(Adt::Enum(e)) = ty.as_adt() {
|
||||
for variant in e.variants(ctx.db) {
|
||||
acc.add_enum_variant(ctx, variant, None);
|
||||
}
|
||||
}
|
||||
|
||||
let traits_in_scope = ctx.scope.traits_in_scope();
|
||||
let mut seen = FxHashSet::default();
|
||||
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
|
||||
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// We might iterate candidates of a trait multiple times here, so deduplicate
|
||||
// them.
|
||||
if seen.insert(item) {
|
||||
match item {
|
||||
hir::AssocItem::Function(func) => {
|
||||
acc.add_function(ctx, func, None);
|
||||
}
|
||||
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
|
||||
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
|
||||
}
|
||||
}
|
||||
None::<()>
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, completion_list},
|
||||
CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
fn check_builtin(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::BuiltinType);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_complete_current_use() {
|
||||
mark::check!(dont_complete_current_use);
|
||||
check(r#"use self::foo$0;"#, expect![[""]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_complete_current_use_in_braces_with_glob() {
|
||||
check(
|
||||
r#"
|
||||
mod foo { pub struct S; }
|
||||
use self::{foo::*, bar$0};
|
||||
"#,
|
||||
expect![[r#"
|
||||
st S
|
||||
md foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_complete_primitive_in_use() {
|
||||
check_builtin(r#"use self::$0;"#, expect![[""]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_complete_primitive_in_module_scope() {
|
||||
check_builtin(r#"fn foo() { self::$0 }"#, expect![[""]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_primitives() {
|
||||
check_builtin(
|
||||
r#"fn main() { let _: $0 = 92; }"#,
|
||||
expect![[r#"
|
||||
bt u32
|
||||
bt bool
|
||||
bt u8
|
||||
bt isize
|
||||
bt u16
|
||||
bt u64
|
||||
bt u128
|
||||
bt f32
|
||||
bt i128
|
||||
bt i16
|
||||
bt str
|
||||
bt i64
|
||||
bt char
|
||||
bt f64
|
||||
bt i32
|
||||
bt i8
|
||||
bt usize
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_mod_with_same_name_as_function() {
|
||||
check(
|
||||
r#"
|
||||
use self::my::$0;
|
||||
|
||||
mod my { pub struct Bar; }
|
||||
fn my() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Bar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_visibility() {
|
||||
check(
|
||||
r#"
|
||||
use self::my::$0;
|
||||
|
||||
mod my {
|
||||
struct Bar;
|
||||
pub struct Foo;
|
||||
pub use Bar as PublicBar;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Foo
|
||||
st PublicBar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_use_item_starting_with_self() {
|
||||
check(
|
||||
r#"
|
||||
use self::m::$0;
|
||||
|
||||
mod m { pub struct Bar; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Bar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_use_item_starting_with_crate() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
struct Spam;
|
||||
//- /foo.rs
|
||||
use crate::Sp$0
|
||||
"#,
|
||||
expect![[r#"
|
||||
md foo
|
||||
st Spam
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_nested_use_tree() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
struct Spam;
|
||||
//- /foo.rs
|
||||
use crate::{Sp$0};
|
||||
"#,
|
||||
expect![[r#"
|
||||
md foo
|
||||
st Spam
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_deeply_nested_use_tree() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
pub mod bar {
|
||||
pub mod baz {
|
||||
pub struct Spam;
|
||||
}
|
||||
}
|
||||
//- /foo.rs
|
||||
use crate::{bar::{baz::Sp$0}};
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Spam
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant() {
|
||||
check(
|
||||
r#"
|
||||
enum E { Foo, Bar(i32) }
|
||||
fn foo() { let _ = E::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Foo ()
|
||||
ev Bar(…) (i32)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_struct_associated_items() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
struct S;
|
||||
|
||||
impl S {
|
||||
fn a() {}
|
||||
fn b(&self) {}
|
||||
const C: i32 = 42;
|
||||
type T = i32;
|
||||
}
|
||||
|
||||
fn foo() { let _ = S::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn a() -> ()
|
||||
me b(…) -> ()
|
||||
ct C const C: i32 = 42;
|
||||
ta T type T = i32;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn associated_item_visibility() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
|
||||
mod m {
|
||||
impl super::S {
|
||||
pub(crate) fn public_method() { }
|
||||
fn private_method() { }
|
||||
pub(crate) type PublicType = u32;
|
||||
type PrivateType = u32;
|
||||
pub(crate) const PUBLIC_CONST: u32 = 1;
|
||||
const PRIVATE_CONST: u32 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn foo() { let _ = S::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn public_method() -> ()
|
||||
ct PUBLIC_CONST pub(crate) const PUBLIC_CONST: u32 = 1;
|
||||
ta PublicType pub(crate) type PublicType = u32;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_associated_method() {
|
||||
check(
|
||||
r#"
|
||||
enum E {};
|
||||
impl E { fn m() { } }
|
||||
|
||||
fn foo() { let _ = E::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_union_associated_method() {
|
||||
check(
|
||||
r#"
|
||||
union U {};
|
||||
impl U { fn m() { } }
|
||||
|
||||
fn foo() { let _ = U::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_use_paths_across_crates() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:foo
|
||||
use foo::$0;
|
||||
|
||||
//- /foo/lib.rs crate:foo
|
||||
pub mod bar { pub struct S; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
md bar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_trait_associated_method_1() {
|
||||
check(
|
||||
r#"
|
||||
trait Trait { fn m(); }
|
||||
|
||||
fn foo() { let _ = Trait::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_trait_associated_method_2() {
|
||||
check(
|
||||
r#"
|
||||
trait Trait { fn m(); }
|
||||
|
||||
struct S;
|
||||
impl Trait for S {}
|
||||
|
||||
fn foo() { let _ = S::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_trait_associated_method_3() {
|
||||
check(
|
||||
r#"
|
||||
trait Trait { fn m(); }
|
||||
|
||||
struct S;
|
||||
impl Trait for S {}
|
||||
|
||||
fn foo() { let _ = <S as Trait>::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn m() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_ty_param_assoc_ty() {
|
||||
check(
|
||||
r#"
|
||||
trait Super {
|
||||
type Ty;
|
||||
const CONST: u8;
|
||||
fn func() {}
|
||||
fn method(&self) {}
|
||||
}
|
||||
|
||||
trait Sub: Super {
|
||||
type SubTy;
|
||||
const C2: ();
|
||||
fn subfunc() {}
|
||||
fn submethod(&self) {}
|
||||
}
|
||||
|
||||
fn foo<T: Sub>() { T::$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
ta SubTy type SubTy;
|
||||
ta Ty type Ty;
|
||||
ct C2 const C2: ();
|
||||
fn subfunc() -> ()
|
||||
me submethod(…) -> ()
|
||||
ct CONST const CONST: u8;
|
||||
fn func() -> ()
|
||||
me method(…) -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_self_param_assoc_ty() {
|
||||
check(
|
||||
r#"
|
||||
trait Super {
|
||||
type Ty;
|
||||
const CONST: u8 = 0;
|
||||
fn func() {}
|
||||
fn method(&self) {}
|
||||
}
|
||||
|
||||
trait Sub: Super {
|
||||
type SubTy;
|
||||
const C2: () = ();
|
||||
fn subfunc() {}
|
||||
fn submethod(&self) {}
|
||||
}
|
||||
|
||||
struct Wrap<T>(T);
|
||||
impl<T> Super for Wrap<T> {}
|
||||
impl<T> Sub for Wrap<T> {
|
||||
fn subfunc() {
|
||||
// Should be able to assume `Self: Sub + Super`
|
||||
Self::$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ta SubTy type SubTy;
|
||||
ta Ty type Ty;
|
||||
ct CONST const CONST: u8 = 0;
|
||||
fn func() -> ()
|
||||
me method(…) -> ()
|
||||
ct C2 const C2: () = ();
|
||||
fn subfunc() -> ()
|
||||
me submethod(…) -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_type_alias() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl S { fn foo() {} }
|
||||
type T = S;
|
||||
impl T { fn bar() {} }
|
||||
|
||||
fn main() { T::$0; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn foo() -> ()
|
||||
fn bar() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_qualified_macros() {
|
||||
check(
|
||||
r#"
|
||||
#[macro_export]
|
||||
macro_rules! foo { () => {} }
|
||||
|
||||
fn main() { let _ = crate::$0 }
|
||||
"#,
|
||||
expect![[r##"
|
||||
fn main() -> ()
|
||||
ma foo!(…) #[macro_export] macro_rules! foo
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_super_super_completion() {
|
||||
check(
|
||||
r#"
|
||||
mod a {
|
||||
const A: usize = 0;
|
||||
mod b {
|
||||
const B: usize = 0;
|
||||
mod c { use super::super::$0 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md b
|
||||
ct A
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_reexported_items_under_correct_name() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() { self::m::$0 }
|
||||
|
||||
mod m {
|
||||
pub use super::p::wrong_fn as right_fn;
|
||||
pub use super::p::WRONG_CONST as RIGHT_CONST;
|
||||
pub use super::p::WrongType as RightType;
|
||||
}
|
||||
mod p {
|
||||
fn wrong_fn() {}
|
||||
const WRONG_CONST: u32 = 1;
|
||||
struct WrongType {};
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ct RIGHT_CONST
|
||||
fn right_fn() -> ()
|
||||
st RightType
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"RightType",
|
||||
r#"
|
||||
fn foo() { self::m::$0 }
|
||||
|
||||
mod m {
|
||||
pub use super::p::wrong_fn as right_fn;
|
||||
pub use super::p::WRONG_CONST as RIGHT_CONST;
|
||||
pub use super::p::WrongType as RightType;
|
||||
}
|
||||
mod p {
|
||||
fn wrong_fn() {}
|
||||
const WRONG_CONST: u32 = 1;
|
||||
struct WrongType {};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() { self::m::RightType }
|
||||
|
||||
mod m {
|
||||
pub use super::p::wrong_fn as right_fn;
|
||||
pub use super::p::WRONG_CONST as RIGHT_CONST;
|
||||
pub use super::p::WrongType as RightType;
|
||||
}
|
||||
mod p {
|
||||
fn wrong_fn() {}
|
||||
const WRONG_CONST: u32 = 1;
|
||||
struct WrongType {};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_simple_macro_call() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
fn main() { m!(self::f$0); }
|
||||
fn foo() {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() -> ()
|
||||
fn foo() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_mod_share_name() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() { self::m::$0 }
|
||||
|
||||
mod m {
|
||||
pub mod z {}
|
||||
pub fn z() {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
md z
|
||||
fn z() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_hashmap_new() {
|
||||
check(
|
||||
r#"
|
||||
struct RandomState;
|
||||
struct HashMap<K, V, S = RandomState> {}
|
||||
|
||||
impl<K, V> HashMap<K, V, RandomState> {
|
||||
pub fn new() -> HashMap<K, V, RandomState> { }
|
||||
}
|
||||
fn foo() {
|
||||
HashMap::$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn new() -> HashMap<K, V, RandomState>
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_complete_attr() {
|
||||
check(
|
||||
r#"
|
||||
mod foo { pub struct Foo; }
|
||||
#[foo::$0]
|
||||
fn f() {}
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_function() {
|
||||
check(
|
||||
r#"
|
||||
fn foo(
|
||||
a: i32,
|
||||
b: i32
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fo$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() -> ()
|
||||
fn foo(…) -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_self_enum() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar,
|
||||
Baz,
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
fn foo(self) {
|
||||
Self::$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Bar ()
|
||||
ev Baz ()
|
||||
me foo(…) -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_primitive_assoc_const() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:lib deps:core
|
||||
fn f() {
|
||||
u8::$0
|
||||
}
|
||||
|
||||
//- /core.rs crate:core
|
||||
#[lang = "u8"]
|
||||
impl u8 {
|
||||
pub const MAX: Self = 255;
|
||||
|
||||
pub fn func(self) {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ct MAX pub const MAX: Self = 255;
|
||||
me func(…) -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
390
crates/ide_completion/src/completions/record.rs
Normal file
390
crates/ide_completion/src/completions/record.rs
Normal file
|
@ -0,0 +1,390 @@
|
|||
//! Complete fields in record literals and patterns.
|
||||
use ide_db::{helpers::FamousDefs, SymbolKind};
|
||||
use syntax::ast::Expr;
|
||||
|
||||
use crate::{item::CompletionKind, CompletionContext, CompletionItem, Completions};
|
||||
|
||||
pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||
let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
|
||||
(None, None) => return None,
|
||||
(Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
|
||||
(Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat),
|
||||
(_, Some(record_lit)) => {
|
||||
let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone()));
|
||||
let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default();
|
||||
let impl_default_trait = default_trait
|
||||
.and_then(|default_trait| ty.map(|ty| ty.impls_trait(ctx.db, default_trait, &[])))
|
||||
.unwrap_or(false);
|
||||
|
||||
let missing_fields = ctx.sema.record_literal_missing_fields(record_lit);
|
||||
if impl_default_trait && !missing_fields.is_empty() {
|
||||
let completion_text = "..Default::default()";
|
||||
let completion_text = completion_text
|
||||
.strip_prefix(ctx.token.to_string().as_str())
|
||||
.unwrap_or(completion_text);
|
||||
acc.add(
|
||||
CompletionItem::new(
|
||||
CompletionKind::Snippet,
|
||||
ctx.source_range(),
|
||||
"..Default::default()",
|
||||
)
|
||||
.insert_text(completion_text)
|
||||
.kind(SymbolKind::Field)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
missing_fields
|
||||
}
|
||||
};
|
||||
|
||||
for (field, ty) in missing_fields {
|
||||
acc.add_field(ctx, field, &ty);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use ide_db::helpers::FamousDefs;
|
||||
|
||||
use crate::{
|
||||
test_utils::{self, completion_list},
|
||||
CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
fn check_snippet(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(
|
||||
&format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE),
|
||||
CompletionKind::Snippet,
|
||||
);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
test_utils::check_edit(
|
||||
what,
|
||||
&format!(
|
||||
"//- /main.rs crate:main deps:core{}\n{}",
|
||||
ra_fixture_before,
|
||||
FamousDefs::FIXTURE,
|
||||
),
|
||||
&(ra_fixture_after.to_owned() + "\n"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_field_default() {
|
||||
let test_code = r#"
|
||||
struct S { foo: u32, bar: usize }
|
||||
|
||||
impl core::default::Default for S {
|
||||
fn default() -> Self {
|
||||
S {
|
||||
foo: 0,
|
||||
bar: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process(f: S) {
|
||||
let other = S {
|
||||
foo: 5,
|
||||
.$0
|
||||
};
|
||||
}
|
||||
"#;
|
||||
check(
|
||||
test_code,
|
||||
expect![[r#"
|
||||
fd bar usize
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_snippet(
|
||||
test_code,
|
||||
expect![[r#"
|
||||
sn pd
|
||||
sn ppd
|
||||
fd ..Default::default()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_field_default_completion() {
|
||||
check_edit(
|
||||
"..Default::default()",
|
||||
r#"
|
||||
struct S { foo: u32, bar: usize }
|
||||
|
||||
impl core::default::Default for S {
|
||||
fn default() -> Self {
|
||||
S {
|
||||
foo: 0,
|
||||
bar: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process(f: S) {
|
||||
let other = S {
|
||||
foo: 5,
|
||||
.$0
|
||||
};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S { foo: u32, bar: usize }
|
||||
|
||||
impl core::default::Default for S {
|
||||
fn default() -> Self {
|
||||
S {
|
||||
foo: 0,
|
||||
bar: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process(f: S) {
|
||||
let other = S {
|
||||
foo: 5,
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_field_without_default() {
|
||||
let test_code = r#"
|
||||
struct S { foo: u32, bar: usize }
|
||||
|
||||
fn process(f: S) {
|
||||
let other = S {
|
||||
foo: 5,
|
||||
.$0
|
||||
};
|
||||
}
|
||||
"#;
|
||||
check(
|
||||
test_code,
|
||||
expect![[r#"
|
||||
fd bar usize
|
||||
"#]],
|
||||
);
|
||||
|
||||
check_snippet(
|
||||
test_code,
|
||||
expect![[r#"
|
||||
sn pd
|
||||
sn ppd
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_pattern_field() {
|
||||
check(
|
||||
r#"
|
||||
struct S { foo: u32 }
|
||||
|
||||
fn process(f: S) {
|
||||
match f {
|
||||
S { f$0: 92 } => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd foo u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_pattern_enum_variant() {
|
||||
check(
|
||||
r#"
|
||||
enum E { S { foo: u32, bar: () } }
|
||||
|
||||
fn process(e: E) {
|
||||
match e {
|
||||
E::S { $0 } => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd foo u32
|
||||
fd bar ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_pattern_field_in_simple_macro() {
|
||||
check(
|
||||
r"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
struct S { foo: u32 }
|
||||
|
||||
fn process(f: S) {
|
||||
m!(match f {
|
||||
S { f$0: 92 } => (),
|
||||
})
|
||||
}
|
||||
",
|
||||
expect![[r#"
|
||||
fd foo u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_missing_fields_are_completed_in_destruct_pats() {
|
||||
check(
|
||||
r#"
|
||||
struct S {
|
||||
foo1: u32, foo2: u32,
|
||||
bar: u32, baz: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let s = S {
|
||||
foo1: 1, foo2: 2,
|
||||
bar: 3, baz: 4,
|
||||
};
|
||||
if let S { foo1, foo2: a, $0 } = s {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd bar u32
|
||||
fd baz u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_field() {
|
||||
check(
|
||||
r#"
|
||||
struct A { the_field: u32 }
|
||||
fn foo() {
|
||||
A { the$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_enum_variant() {
|
||||
check(
|
||||
r#"
|
||||
enum E { A { a: u32 } }
|
||||
fn foo() {
|
||||
let _ = E::A { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd a u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_two_structs() {
|
||||
check(
|
||||
r#"
|
||||
struct A { a: u32 }
|
||||
struct B { b: u32 }
|
||||
|
||||
fn foo() {
|
||||
let _: A = B { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd b u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_generic_struct() {
|
||||
check(
|
||||
r#"
|
||||
struct A<T> { a: T }
|
||||
|
||||
fn foo() {
|
||||
let _: A<u32> = A { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd a u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_literal_field_in_simple_macro() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
struct A { the_field: u32 }
|
||||
fn foo() {
|
||||
m!(A { the$0 })
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd the_field u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_missing_fields_are_completed() {
|
||||
check(
|
||||
r#"
|
||||
struct S {
|
||||
foo1: u32, foo2: u32,
|
||||
bar: u32, baz: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let foo1 = 1;
|
||||
let s = S { foo1, foo2: 5, $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd bar u32
|
||||
fd baz u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_functional_update() {
|
||||
check(
|
||||
r#"
|
||||
struct S { foo1: u32, foo2: u32 }
|
||||
|
||||
fn main() {
|
||||
let foo1 = 1;
|
||||
let s = S { foo1, $0 .. loop {} }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd foo2 u32
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
116
crates/ide_completion/src/completions/snippet.rs
Normal file
116
crates/ide_completion/src/completions/snippet.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
|
||||
|
||||
use ide_db::helpers::SnippetCap;
|
||||
|
||||
use crate::{
|
||||
item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
|
||||
Completions,
|
||||
};
|
||||
|
||||
fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
|
||||
CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label)
|
||||
.insert_snippet(cap, snippet)
|
||||
.kind(CompletionItemKind::Snippet)
|
||||
}
|
||||
|
||||
pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
|
||||
return;
|
||||
}
|
||||
let cap = match ctx.config.snippet_cap {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
|
||||
snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
|
||||
snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
|
||||
}
|
||||
|
||||
pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !ctx.is_new_item {
|
||||
return;
|
||||
}
|
||||
let cap = match ctx.config.snippet_cap {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
|
||||
snippet(
|
||||
ctx,
|
||||
cap,
|
||||
"tmod (Test module)",
|
||||
"\
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ${1:test_name}() {
|
||||
$0
|
||||
}
|
||||
}",
|
||||
)
|
||||
.lookup_by("tmod")
|
||||
.add_to(acc);
|
||||
|
||||
snippet(
|
||||
ctx,
|
||||
cap,
|
||||
"tfn (Test function)",
|
||||
"\
|
||||
#[test]
|
||||
fn ${1:feature}() {
|
||||
$0
|
||||
}",
|
||||
)
|
||||
.lookup_by("tfn")
|
||||
.add_to(acc);
|
||||
|
||||
snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}").add_to(acc);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{test_utils::completion_list, CompletionKind};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Snippet);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_snippets_in_expressions() {
|
||||
check(
|
||||
r#"fn foo(x: i32) { $0 }"#,
|
||||
expect![[r#"
|
||||
sn pd
|
||||
sn ppd
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_complete_snippets_in_path() {
|
||||
check(r#"fn foo(x: i32) { ::foo$0 }"#, expect![[""]]);
|
||||
check(r#"fn foo(x: i32) { ::$0 }"#, expect![[""]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_snippets_in_items() {
|
||||
check(
|
||||
r#"
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
sn tmod (Test module)
|
||||
sn tfn (Test function)
|
||||
sn macro_rules
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
736
crates/ide_completion/src/completions/trait_impl.rs
Normal file
736
crates/ide_completion/src/completions/trait_impl.rs
Normal file
|
@ -0,0 +1,736 @@
|
|||
//! Completion for associated items in a trait implementation.
|
||||
//!
|
||||
//! This module adds the completion items related to implementing associated
|
||||
//! items within a `impl Trait for Struct` block. The current context node
|
||||
//! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node
|
||||
//! and an direct child of an `IMPL`.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Considering the following trait `impl`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! trait SomeTrait {
|
||||
//! fn foo();
|
||||
//! }
|
||||
//!
|
||||
//! impl SomeTrait for () {
|
||||
//! fn f$0
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! may result in the completion of the following method:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! # trait SomeTrait {
|
||||
//! # fn foo();
|
||||
//! # }
|
||||
//!
|
||||
//! impl SomeTrait for () {
|
||||
//! fn foo() {}$0
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use hir::{self, HasAttrs, HasSource};
|
||||
use ide_db::{traits::get_missing_assoc_items, SymbolKind};
|
||||
use syntax::{
|
||||
ast::{self, edit, Impl},
|
||||
display::function_declaration,
|
||||
AstNode, SyntaxKind, SyntaxNode, TextRange, T,
|
||||
};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{
|
||||
CompletionContext,
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
CompletionKind,
|
||||
Completions,
|
||||
// display::function_declaration,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum ImplCompletionKind {
|
||||
All,
|
||||
Fn,
|
||||
TypeAlias,
|
||||
Const,
|
||||
}
|
||||
|
||||
pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if let Some((kind, trigger, impl_def)) = completion_match(ctx) {
|
||||
get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item {
|
||||
hir::AssocItem::Function(fn_item)
|
||||
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn =>
|
||||
{
|
||||
add_function_impl(&trigger, acc, ctx, fn_item)
|
||||
}
|
||||
hir::AssocItem::TypeAlias(type_item)
|
||||
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::TypeAlias =>
|
||||
{
|
||||
add_type_alias_impl(&trigger, acc, ctx, type_item)
|
||||
}
|
||||
hir::AssocItem::Const(const_item)
|
||||
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Const =>
|
||||
{
|
||||
add_const_impl(&trigger, acc, ctx, const_item)
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, SyntaxNode, Impl)> {
|
||||
let mut token = ctx.token.clone();
|
||||
// For keywork without name like `impl .. { fn $0 }`, the current position is inside
|
||||
// the whitespace token, which is outside `FN` syntax node.
|
||||
// We need to follow the previous token in this case.
|
||||
if token.kind() == SyntaxKind::WHITESPACE {
|
||||
token = token.prev_token()?;
|
||||
}
|
||||
|
||||
let impl_item_offset = match token.kind() {
|
||||
// `impl .. { const $0 }`
|
||||
// ERROR 0
|
||||
// CONST_KW <- *
|
||||
T![const] => 0,
|
||||
// `impl .. { fn/type $0 }`
|
||||
// FN/TYPE_ALIAS 0
|
||||
// FN_KW <- *
|
||||
T![fn] | T![type] => 0,
|
||||
// `impl .. { fn/type/const foo$0 }`
|
||||
// FN/TYPE_ALIAS/CONST 1
|
||||
// NAME 0
|
||||
// IDENT <- *
|
||||
SyntaxKind::IDENT if token.parent().kind() == SyntaxKind::NAME => 1,
|
||||
// `impl .. { foo$0 }`
|
||||
// MACRO_CALL 3
|
||||
// PATH 2
|
||||
// PATH_SEGMENT 1
|
||||
// NAME_REF 0
|
||||
// IDENT <- *
|
||||
SyntaxKind::IDENT if token.parent().kind() == SyntaxKind::NAME_REF => 3,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let impl_item = token.ancestors().nth(impl_item_offset)?;
|
||||
// Must directly belong to an impl block.
|
||||
// IMPL
|
||||
// ASSOC_ITEM_LIST
|
||||
// <item>
|
||||
let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
|
||||
let kind = match impl_item.kind() {
|
||||
// `impl ... { const $0 fn/type/const }`
|
||||
_ if token.kind() == T![const] => ImplCompletionKind::Const,
|
||||
SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
|
||||
SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
|
||||
SyntaxKind::FN => ImplCompletionKind::Fn,
|
||||
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
|
||||
_ => return None,
|
||||
};
|
||||
Some((kind, impl_item, impl_def))
|
||||
}
|
||||
|
||||
fn add_function_impl(
|
||||
fn_def_node: &SyntaxNode,
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
func: hir::Function,
|
||||
) {
|
||||
let fn_name = func.name(ctx.db).to_string();
|
||||
|
||||
let label = if func.assoc_fn_params(ctx.db).is_empty() {
|
||||
format!("fn {}()", fn_name)
|
||||
} else {
|
||||
format!("fn {}(..)", fn_name)
|
||||
};
|
||||
|
||||
let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
|
||||
.lookup_by(fn_name)
|
||||
.set_documentation(func.docs(ctx.db));
|
||||
|
||||
let completion_kind = if func.self_param(ctx.db).is_some() {
|
||||
CompletionItemKind::Method
|
||||
} else {
|
||||
CompletionItemKind::SymbolKind(SymbolKind::Function)
|
||||
};
|
||||
let range = TextRange::new(fn_def_node.text_range().start(), ctx.source_range().end());
|
||||
|
||||
if let Some(src) = func.source(ctx.db) {
|
||||
let function_decl = function_declaration(&src.value);
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => {
|
||||
let snippet = format!("{} {{\n $0\n}}", function_decl);
|
||||
builder.snippet_edit(cap, TextEdit::replace(range, snippet))
|
||||
}
|
||||
None => {
|
||||
let header = format!("{} {{", function_decl);
|
||||
builder.text_edit(TextEdit::replace(range, header))
|
||||
}
|
||||
}
|
||||
.kind(completion_kind)
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_type_alias_impl(
|
||||
type_def_node: &SyntaxNode,
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
type_alias: hir::TypeAlias,
|
||||
) {
|
||||
let alias_name = type_alias.name(ctx.db).to_string();
|
||||
|
||||
let snippet = format!("type {} = ", alias_name);
|
||||
|
||||
let range = TextRange::new(type_def_node.text_range().start(), ctx.source_range().end());
|
||||
|
||||
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone())
|
||||
.text_edit(TextEdit::replace(range, snippet))
|
||||
.lookup_by(alias_name)
|
||||
.kind(SymbolKind::TypeAlias)
|
||||
.set_documentation(type_alias.docs(ctx.db))
|
||||
.add_to(acc);
|
||||
}
|
||||
|
||||
fn add_const_impl(
|
||||
const_def_node: &SyntaxNode,
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
const_: hir::Const,
|
||||
) {
|
||||
let const_name = const_.name(ctx.db).map(|n| n.to_string());
|
||||
|
||||
if let Some(const_name) = const_name {
|
||||
if let Some(source) = const_.source(ctx.db) {
|
||||
let snippet = make_const_compl_syntax(&source.value);
|
||||
|
||||
let range =
|
||||
TextRange::new(const_def_node.text_range().start(), ctx.source_range().end());
|
||||
|
||||
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone())
|
||||
.text_edit(TextEdit::replace(range, snippet))
|
||||
.lookup_by(const_name)
|
||||
.kind(SymbolKind::Const)
|
||||
.set_documentation(const_.docs(ctx.db))
|
||||
.add_to(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_const_compl_syntax(const_: &ast::Const) -> String {
|
||||
let const_ = edit::remove_attrs_and_docs(const_);
|
||||
|
||||
let const_start = const_.syntax().text_range().start();
|
||||
let const_end = const_.syntax().text_range().end();
|
||||
|
||||
let start =
|
||||
const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start());
|
||||
|
||||
let end = const_
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.find(|s| s.kind() == T![;] || s.kind() == T![=])
|
||||
.map_or(const_end, |f| f.text_range().start());
|
||||
|
||||
let len = end - start;
|
||||
let range = TextRange::new(0.into(), len);
|
||||
|
||||
let syntax = const_.syntax().text().slice(range).to_string();
|
||||
|
||||
format!("{} = ", syntax.trim_end())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, completion_list},
|
||||
CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list(ra_fixture, CompletionKind::Magic);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_ref_function_type_const() {
|
||||
check(
|
||||
r#"
|
||||
trait Test {
|
||||
type TestType;
|
||||
const TEST_CONST: u16;
|
||||
fn test();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
t$0
|
||||
}
|
||||
"#,
|
||||
expect![["
|
||||
ta type TestType = \n\
|
||||
ct const TEST_CONST: u16 = \n\
|
||||
fn fn test()
|
||||
"]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completion_inside_fn() {
|
||||
check(
|
||||
r"
|
||||
trait Test { fn test(); fn test2(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test() {
|
||||
t$0
|
||||
}
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { fn test(); fn test2(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test() {
|
||||
fn t$0
|
||||
}
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { fn test(); fn test2(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test() {
|
||||
fn $0
|
||||
}
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
// https://github.com/rust-analyzer/rust-analyzer/pull/5976#issuecomment-692332191
|
||||
check(
|
||||
r"
|
||||
trait Test { fn test(); fn test2(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test() {
|
||||
foo.$0
|
||||
}
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { fn test(_: i32); fn test2(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test(t$0)
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { fn test(_: fn()); fn test2(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test(f: fn $0)
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completion_inside_const() {
|
||||
check(
|
||||
r"
|
||||
trait Test { const TEST: fn(); const TEST2: u32; type Test; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
const TEST: fn $0
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
const TEST: T$0
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
const TEST: u32 = f$0
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
const TEST: u32 = {
|
||||
t$0
|
||||
};
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
const TEST: u32 = {
|
||||
fn $0
|
||||
};
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
const TEST: u32 = {
|
||||
fn t$0
|
||||
};
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completion_inside_type() {
|
||||
check(
|
||||
r"
|
||||
trait Test { type Test; type Test2; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
type Test = T$0;
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
trait Test { type Test; type Test2; fn test(); }
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
type Test = fn $0;
|
||||
}
|
||||
",
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_ref_single_function() {
|
||||
check_edit(
|
||||
"test",
|
||||
r#"
|
||||
trait Test {
|
||||
fn test();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
t$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
trait Test {
|
||||
fn test();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test() {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_function() {
|
||||
check_edit(
|
||||
"test",
|
||||
r#"
|
||||
trait Test {
|
||||
fn test();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn t$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
trait Test {
|
||||
fn test();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn test() {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hide_implemented_fn() {
|
||||
check(
|
||||
r#"
|
||||
trait Test {
|
||||
fn foo();
|
||||
fn foo_bar();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn foo() {}
|
||||
fn f$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn fn foo_bar()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_fn() {
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
trait Test {
|
||||
fn foo<T>();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn f$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
trait Test {
|
||||
fn foo<T>();
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn foo<T>() {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
trait Test {
|
||||
fn foo<T>() where T: Into<String>;
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn f$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
trait Test {
|
||||
fn foo<T>() where T: Into<String>;
|
||||
}
|
||||
struct T;
|
||||
|
||||
impl Test for T {
|
||||
fn foo<T>()
|
||||
where T: Into<String> {
|
||||
$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn associated_type() {
|
||||
check_edit(
|
||||
"SomeType",
|
||||
r#"
|
||||
trait Test {
|
||||
type SomeType;
|
||||
}
|
||||
|
||||
impl Test for () {
|
||||
type S$0
|
||||
}
|
||||
"#,
|
||||
"
|
||||
trait Test {
|
||||
type SomeType;
|
||||
}
|
||||
|
||||
impl Test for () {
|
||||
type SomeType = \n\
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn associated_const() {
|
||||
check_edit(
|
||||
"SOME_CONST",
|
||||
r#"
|
||||
trait Test {
|
||||
const SOME_CONST: u16;
|
||||
}
|
||||
|
||||
impl Test for () {
|
||||
const S$0
|
||||
}
|
||||
"#,
|
||||
"
|
||||
trait Test {
|
||||
const SOME_CONST: u16;
|
||||
}
|
||||
|
||||
impl Test for () {
|
||||
const SOME_CONST: u16 = \n\
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"SOME_CONST",
|
||||
r#"
|
||||
trait Test {
|
||||
const SOME_CONST: u16 = 92;
|
||||
}
|
||||
|
||||
impl Test for () {
|
||||
const S$0
|
||||
}
|
||||
"#,
|
||||
"
|
||||
trait Test {
|
||||
const SOME_CONST: u16 = 92;
|
||||
}
|
||||
|
||||
impl Test for () {
|
||||
const SOME_CONST: u16 = \n\
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_without_name() {
|
||||
let test = |completion: &str, hint: &str, completed: &str, next_sibling: &str| {
|
||||
check_edit(
|
||||
completion,
|
||||
&format!(
|
||||
r#"
|
||||
trait Test {{
|
||||
type Foo;
|
||||
const CONST: u16;
|
||||
fn bar();
|
||||
}}
|
||||
struct T;
|
||||
|
||||
impl Test for T {{
|
||||
{}
|
||||
{}
|
||||
}}
|
||||
"#,
|
||||
hint, next_sibling
|
||||
),
|
||||
&format!(
|
||||
r#"
|
||||
trait Test {{
|
||||
type Foo;
|
||||
const CONST: u16;
|
||||
fn bar();
|
||||
}}
|
||||
struct T;
|
||||
|
||||
impl Test for T {{
|
||||
{}
|
||||
{}
|
||||
}}
|
||||
"#,
|
||||
completed, next_sibling
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
// Enumerate some possible next siblings.
|
||||
for next_sibling in &[
|
||||
"",
|
||||
"fn other_fn() {}", // `const $0 fn` -> `const fn`
|
||||
"type OtherType = i32;",
|
||||
"const OTHER_CONST: i32 = 0;",
|
||||
"async fn other_fn() {}",
|
||||
"unsafe fn other_fn() {}",
|
||||
"default fn other_fn() {}",
|
||||
"default type OtherType = i32;",
|
||||
"default const OTHER_CONST: i32 = 0;",
|
||||
] {
|
||||
test("bar", "fn $0", "fn bar() {\n $0\n}", next_sibling);
|
||||
test("Foo", "type $0", "type Foo = ", next_sibling);
|
||||
test("CONST", "const $0", "const CONST: u16 = ", next_sibling);
|
||||
}
|
||||
}
|
||||
}
|
755
crates/ide_completion/src/completions/unqualified_path.rs
Normal file
755
crates/ide_completion/src/completions/unqualified_path.rs
Normal file
|
@ -0,0 +1,755 @@
|
|||
//! Completion of names from the current scope, e.g. locals and imported items.
|
||||
|
||||
use hir::ScopeDef;
|
||||
use syntax::AstNode;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{CompletionContext, Completions};
|
||||
|
||||
pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
|
||||
return;
|
||||
}
|
||||
if ctx.record_lit_syntax.is_some()
|
||||
|| ctx.record_pat_syntax.is_some()
|
||||
|| ctx.attribute_under_caret.is_some()
|
||||
|| ctx.mod_declaration_under_caret.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ty) = &ctx.expected_type {
|
||||
super::complete_enum_variants(acc, ctx, ty, |acc, ctx, variant, path| {
|
||||
acc.add_qualified_enum_variant(ctx, variant, path)
|
||||
});
|
||||
}
|
||||
|
||||
if ctx.is_pat_binding_or_const {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.scope.process_all_names(&mut |name, res| {
|
||||
if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res {
|
||||
mark::hit!(skip_lifetime_completion);
|
||||
return;
|
||||
}
|
||||
if ctx.use_item_syntax.is_some() {
|
||||
if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
|
||||
if name_ref.syntax().text() == name.to_string().as_str() {
|
||||
mark::hit!(self_fulfilling_completion);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
acc.add_resolution(ctx, name.to_string(), &res);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
|
||||
CompletionConfig, CompletionKind,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
check_with_config(TEST_CONFIG, ra_fixture, expect);
|
||||
}
|
||||
|
||||
fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) {
|
||||
let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_eq(&actual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_fulfilling_completion() {
|
||||
mark::check!(self_fulfilling_completion);
|
||||
check(
|
||||
r#"
|
||||
use foo$0
|
||||
use std::collections;
|
||||
"#,
|
||||
expect![[r#"
|
||||
?? collections
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_pat_and_path_ignore_at() {
|
||||
check(
|
||||
r#"
|
||||
enum Enum { A, B }
|
||||
fn quux(x: Option<Enum>) {
|
||||
match x {
|
||||
None => (),
|
||||
Some(en$0 @ Enum::A) => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_pat_and_path_ignore_ref() {
|
||||
check(
|
||||
r#"
|
||||
enum Enum { A, B }
|
||||
fn quux(x: Option<Enum>) {
|
||||
match x {
|
||||
None => (),
|
||||
Some(ref en$0) => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[""]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_pat_and_path() {
|
||||
check(
|
||||
r#"
|
||||
enum Enum { A, B }
|
||||
fn quux(x: Option<Enum>) {
|
||||
match x {
|
||||
None => (),
|
||||
Some(En$0) => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
en Enum
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_bindings_from_let() {
|
||||
check(
|
||||
r#"
|
||||
fn quux(x: i32) {
|
||||
let y = 92;
|
||||
1 + $0;
|
||||
let z = ();
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lc y i32
|
||||
lc x i32
|
||||
fn quux(…) -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_bindings_from_if_let() {
|
||||
check(
|
||||
r#"
|
||||
fn quux() {
|
||||
if let Some(x) = foo() {
|
||||
let y = 92;
|
||||
};
|
||||
if let Some(a) = bar() {
|
||||
let b = 62;
|
||||
1 + $0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lc b i32
|
||||
lc a
|
||||
fn quux() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_bindings_from_for() {
|
||||
check(
|
||||
r#"
|
||||
fn quux() {
|
||||
for x in &[1, 2, 3] { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lc x
|
||||
fn quux() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_if_prefix_is_keyword() {
|
||||
mark::check!(completes_if_prefix_is_keyword);
|
||||
check_edit(
|
||||
"wherewolf",
|
||||
r#"
|
||||
fn main() {
|
||||
let wherewolf = 92;
|
||||
drop(where$0)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let wherewolf = 92;
|
||||
drop(wherewolf)
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_generic_params() {
|
||||
check(
|
||||
r#"fn quux<T>() { $0 }"#,
|
||||
expect![[r#"
|
||||
tp T
|
||||
fn quux() -> ()
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"fn quux<const C: usize>() { $0 }"#,
|
||||
expect![[r#"
|
||||
cp C
|
||||
fn quux() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_complete_lifetimes() {
|
||||
mark::check!(skip_lifetime_completion);
|
||||
check(
|
||||
r#"fn quux<'a>() { $0 }"#,
|
||||
expect![[r#"
|
||||
fn quux() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_generic_params_in_struct() {
|
||||
check(
|
||||
r#"struct S<T> { x: $0}"#,
|
||||
expect![[r#"
|
||||
sp Self
|
||||
tp T
|
||||
st S<…>
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_self_in_enum() {
|
||||
check(
|
||||
r#"enum X { Y($0) }"#,
|
||||
expect![[r#"
|
||||
sp Self
|
||||
en X
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_module_items() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
enum E {}
|
||||
fn quux() { $0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
st S
|
||||
fn quux() -> ()
|
||||
en E
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression test for issue #6091.
|
||||
#[test]
|
||||
fn correctly_completes_module_items_prefixed_with_underscore() {
|
||||
check_edit(
|
||||
"_alpha",
|
||||
r#"
|
||||
fn main() {
|
||||
_$0
|
||||
}
|
||||
fn _alpha() {}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
_alpha()$0
|
||||
}
|
||||
fn _alpha() {}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_extern_prelude() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:main deps:other_crate
|
||||
use $0;
|
||||
|
||||
//- /other_crate/lib.rs crate:other_crate
|
||||
// nothing here
|
||||
"#,
|
||||
expect![[r#"
|
||||
md other_crate
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_module_items_in_nested_modules() {
|
||||
check(
|
||||
r#"
|
||||
struct Foo;
|
||||
mod m {
|
||||
struct Bar;
|
||||
fn quux() { $0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn quux() -> ()
|
||||
st Bar
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_return_type() {
|
||||
check(
|
||||
r#"
|
||||
struct Foo;
|
||||
fn x() -> $0
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Foo
|
||||
fn x() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_show_both_completions_for_shadowing() {
|
||||
check(
|
||||
r#"
|
||||
fn foo() {
|
||||
let bar = 92;
|
||||
{
|
||||
let bar = 62;
|
||||
drop($0)
|
||||
}
|
||||
}
|
||||
"#,
|
||||
// FIXME: should be only one bar here
|
||||
expect![[r#"
|
||||
lc bar i32
|
||||
lc bar i32
|
||||
fn foo() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_self_in_methods() {
|
||||
check(
|
||||
r#"impl S { fn foo(&self) { $0 } }"#,
|
||||
expect![[r#"
|
||||
lc self &{unknown}
|
||||
sp Self
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_prelude() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
fn foo() { let x: $0 }
|
||||
|
||||
//- /std/lib.rs crate:std
|
||||
#[prelude_import]
|
||||
use prelude::*;
|
||||
|
||||
mod prelude { struct Option; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn foo() -> ()
|
||||
md std
|
||||
st Option
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_prelude_macros() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
fn f() {$0}
|
||||
|
||||
//- /std/lib.rs crate:std
|
||||
#[prelude_import]
|
||||
pub use prelude::*;
|
||||
|
||||
#[macro_use]
|
||||
mod prelude {
|
||||
pub use crate::concat;
|
||||
}
|
||||
|
||||
mod macros {
|
||||
#[rustc_builtin_macro]
|
||||
#[macro_export]
|
||||
macro_rules! concat { }
|
||||
}
|
||||
"#,
|
||||
expect![[r##"
|
||||
fn f() -> ()
|
||||
ma concat!(…) #[macro_export] macro_rules! concat
|
||||
md std
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_std_prelude_if_core_is_defined() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:core,std
|
||||
fn foo() { let x: $0 }
|
||||
|
||||
//- /core/lib.rs crate:core
|
||||
#[prelude_import]
|
||||
use prelude::*;
|
||||
|
||||
mod prelude { struct Option; }
|
||||
|
||||
//- /std/lib.rs crate:std deps:core
|
||||
#[prelude_import]
|
||||
use prelude::*;
|
||||
|
||||
mod prelude { struct String; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn foo() -> ()
|
||||
md std
|
||||
md core
|
||||
st String
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_macros_as_value() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo { () => {} }
|
||||
|
||||
#[macro_use]
|
||||
mod m1 {
|
||||
macro_rules! bar { () => {} }
|
||||
}
|
||||
|
||||
mod m2 {
|
||||
macro_rules! nope { () => {} }
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! baz { () => {} }
|
||||
}
|
||||
|
||||
fn main() { let v = $0 }
|
||||
"#,
|
||||
expect![[r##"
|
||||
md m1
|
||||
ma baz!(…) #[macro_export] macro_rules! baz
|
||||
fn main() -> ()
|
||||
md m2
|
||||
ma bar!(…) macro_rules! bar
|
||||
ma foo!(…) macro_rules! foo
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_both_macro_and_value() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo { () => {} }
|
||||
fn foo() { $0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn foo() -> ()
|
||||
ma foo!(…) macro_rules! foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_macros_as_type() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo { () => {} }
|
||||
fn main() { let x: $0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() -> ()
|
||||
ma foo!(…) macro_rules! foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_macros_as_stmt() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! foo { () => {} }
|
||||
fn main() { $0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() -> ()
|
||||
ma foo!(…) macro_rules! foo
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_local_item() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
return f$0;
|
||||
fn frobnicate() {}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn frobnicate() -> ()
|
||||
fn main() -> ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_simple_macro_1() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
fn quux(x: i32) {
|
||||
let y = 92;
|
||||
m!($0);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lc y i32
|
||||
lc x i32
|
||||
fn quux(…) -> ()
|
||||
ma m!(…) macro_rules! m
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_simple_macro_2() {
|
||||
check(
|
||||
r"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
fn quux(x: i32) {
|
||||
let y = 92;
|
||||
m!(x$0);
|
||||
}
|
||||
",
|
||||
expect![[r#"
|
||||
lc y i32
|
||||
lc x i32
|
||||
fn quux(…) -> ()
|
||||
ma m!(…) macro_rules! m
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_in_simple_macro_without_closing_parens() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! m { ($e:expr) => { $e } }
|
||||
fn quux(x: i32) {
|
||||
let y = 92;
|
||||
m!(x$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
lc y i32
|
||||
lc x i32
|
||||
fn quux(…) -> ()
|
||||
ma m!(…) macro_rules! m
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_unresolved_uses() {
|
||||
check(
|
||||
r#"
|
||||
use spam::Quux;
|
||||
|
||||
fn main() { $0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn main() -> ()
|
||||
?? Quux
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant_matcharm() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
|
||||
fn main() {
|
||||
let foo = Foo::Quux;
|
||||
match foo { Qu$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Foo::Bar ()
|
||||
ev Foo::Baz ()
|
||||
ev Foo::Quux ()
|
||||
en Foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant_matcharm_ref() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
|
||||
fn main() {
|
||||
let foo = Foo::Quux;
|
||||
match &foo { Qu$0 }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Foo::Bar ()
|
||||
ev Foo::Baz ()
|
||||
ev Foo::Quux ()
|
||||
en Foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant_iflet() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
|
||||
fn main() {
|
||||
let foo = Foo::Quux;
|
||||
if let Qu$0 = foo { }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Foo::Bar ()
|
||||
ev Foo::Baz ()
|
||||
ev Foo::Quux ()
|
||||
en Foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant_basic_expr() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
fn main() { let foo: Foo = Q$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Foo::Bar ()
|
||||
ev Foo::Baz ()
|
||||
ev Foo::Quux ()
|
||||
en Foo
|
||||
fn main() -> ()
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant_from_module() {
|
||||
check(
|
||||
r#"
|
||||
mod m { pub enum E { V } }
|
||||
fn f() -> m::E { V$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev m::E::V ()
|
||||
md m
|
||||
fn f() -> E
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_enum_variant_impl() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Bar, Baz, Quux }
|
||||
impl Foo {
|
||||
fn foo() { match Foo::Bar { Q$0 } }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ev Self::Bar ()
|
||||
ev Self::Baz ()
|
||||
ev Self::Quux ()
|
||||
ev Foo::Bar ()
|
||||
ev Foo::Baz ()
|
||||
ev Foo::Quux ()
|
||||
sp Self
|
||||
en Foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_complete_attr() {
|
||||
check(
|
||||
r#"
|
||||
struct Foo;
|
||||
#[$0]
|
||||
fn f() {}
|
||||
"#,
|
||||
expect![[""]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completes_type_or_trait_in_impl_block() {
|
||||
check(
|
||||
r#"
|
||||
trait MyTrait {}
|
||||
struct MyStruct {}
|
||||
|
||||
impl My$0
|
||||
"#,
|
||||
expect![[r#"
|
||||
sp Self
|
||||
tt MyTrait
|
||||
st MyStruct
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
17
crates/ide_completion/src/config.rs
Normal file
17
crates/ide_completion/src/config.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
//! Settings for tweaking completion.
|
||||
//!
|
||||
//! The fun thing here is `SnippetCap` -- this type can only be created in this
|
||||
//! module, and we use to statically check that we only produce snippet
|
||||
//! completions if we are allowed to.
|
||||
|
||||
use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CompletionConfig {
|
||||
pub enable_postfix_completions: bool,
|
||||
pub enable_imports_on_the_fly: bool,
|
||||
pub add_call_parenthesis: bool,
|
||||
pub add_call_argument_snippets: bool,
|
||||
pub snippet_cap: Option<SnippetCap>,
|
||||
pub insert_use: InsertUseConfig,
|
||||
}
|
537
crates/ide_completion/src/context.rs
Normal file
537
crates/ide_completion/src/context.rs
Normal file
|
@ -0,0 +1,537 @@
|
|||
//! See `CompletionContext` structure.
|
||||
|
||||
use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
|
||||
use ide_db::base_db::{FilePosition, SourceDatabase};
|
||||
use ide_db::{call_info::ActiveParameter, RootDatabase};
|
||||
use syntax::{
|
||||
algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode,
|
||||
SyntaxToken, TextRange, TextSize,
|
||||
};
|
||||
use test_utils::mark;
|
||||
use text_edit::Indel;
|
||||
|
||||
use crate::{
|
||||
patterns::{
|
||||
fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent,
|
||||
has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent,
|
||||
has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
|
||||
has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm,
|
||||
unsafe_is_prev,
|
||||
},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
||||
/// `CompletionContext` is created early during completion to figure out, where
|
||||
/// exactly is the cursor, syntax-wise.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CompletionContext<'a> {
|
||||
pub(super) sema: Semantics<'a, RootDatabase>,
|
||||
pub(super) scope: SemanticsScope<'a>,
|
||||
pub(super) db: &'a RootDatabase,
|
||||
pub(super) config: &'a CompletionConfig,
|
||||
pub(super) position: FilePosition,
|
||||
/// The token before the cursor, in the original file.
|
||||
pub(super) original_token: SyntaxToken,
|
||||
/// The token before the cursor, in the macro-expanded file.
|
||||
pub(super) token: SyntaxToken,
|
||||
pub(super) krate: Option<hir::Crate>,
|
||||
pub(super) expected_type: Option<Type>,
|
||||
pub(super) name_ref_syntax: Option<ast::NameRef>,
|
||||
pub(super) function_syntax: Option<ast::Fn>,
|
||||
pub(super) use_item_syntax: Option<ast::Use>,
|
||||
pub(super) record_lit_syntax: Option<ast::RecordExpr>,
|
||||
pub(super) record_pat_syntax: Option<ast::RecordPat>,
|
||||
pub(super) record_field_syntax: Option<ast::RecordExprField>,
|
||||
pub(super) impl_def: Option<ast::Impl>,
|
||||
/// FIXME: `ActiveParameter` is string-based, which is very very wrong
|
||||
pub(super) active_parameter: Option<ActiveParameter>,
|
||||
pub(super) is_param: bool,
|
||||
/// If a name-binding or reference to a const in a pattern.
|
||||
/// Irrefutable patterns (like let) are excluded.
|
||||
pub(super) is_pat_binding_or_const: bool,
|
||||
pub(super) is_irrefutable_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).
|
||||
pub(super) path_qual: Option<ast::Path>,
|
||||
pub(super) after_if: bool,
|
||||
/// `true` if we are a statement or a last expr in the block.
|
||||
pub(super) can_be_stmt: bool,
|
||||
/// `true` if we expect an expression at the cursor position.
|
||||
pub(super) is_expr: bool,
|
||||
/// Something is typed at the "top" level, in module or impl/trait.
|
||||
pub(super) is_new_item: bool,
|
||||
/// The receiver if this is a field or method access, i.e. writing something.$0
|
||||
pub(super) dot_receiver: Option<ast::Expr>,
|
||||
pub(super) dot_receiver_is_ambiguous_float_literal: bool,
|
||||
/// If this is a call (method or function) in particular, i.e. the () are already there.
|
||||
pub(super) is_call: bool,
|
||||
/// Like `is_call`, but for tuple patterns.
|
||||
pub(super) is_pattern_call: bool,
|
||||
/// If this is a macro call, i.e. the () are already there.
|
||||
pub(super) is_macro_call: bool,
|
||||
pub(super) is_path_type: bool,
|
||||
pub(super) has_type_args: bool,
|
||||
pub(super) attribute_under_caret: Option<ast::Attr>,
|
||||
pub(super) mod_declaration_under_caret: Option<ast::Module>,
|
||||
pub(super) unsafe_is_prev: bool,
|
||||
pub(super) if_is_prev: bool,
|
||||
pub(super) block_expr_parent: bool,
|
||||
pub(super) bind_pat_parent: bool,
|
||||
pub(super) ref_pat_parent: bool,
|
||||
pub(super) in_loop_body: bool,
|
||||
pub(super) has_trait_parent: bool,
|
||||
pub(super) has_impl_parent: bool,
|
||||
pub(super) inside_impl_trait_block: bool,
|
||||
pub(super) has_field_list_parent: bool,
|
||||
pub(super) trait_as_prev_sibling: bool,
|
||||
pub(super) impl_as_prev_sibling: bool,
|
||||
pub(super) is_match_arm: bool,
|
||||
pub(super) has_item_list_or_source_file_parent: bool,
|
||||
pub(super) for_is_prev2: bool,
|
||||
pub(super) fn_is_prev: bool,
|
||||
pub(super) incomplete_let: bool,
|
||||
pub(super) locals: Vec<(String, Local)>,
|
||||
}
|
||||
|
||||
impl<'a> CompletionContext<'a> {
|
||||
pub(super) fn new(
|
||||
db: &'a RootDatabase,
|
||||
position: FilePosition,
|
||||
config: &'a CompletionConfig,
|
||||
) -> Option<CompletionContext<'a>> {
|
||||
let sema = Semantics::new(db);
|
||||
|
||||
let original_file = sema.parse(position.file_id);
|
||||
|
||||
// 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.
|
||||
let file_with_fake_ident = {
|
||||
let parse = db.parse(position.file_id);
|
||||
let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
|
||||
parse.reparse(&edit).tree()
|
||||
};
|
||||
let fake_ident_token =
|
||||
file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
|
||||
|
||||
let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
|
||||
let original_token =
|
||||
original_file.syntax().token_at_offset(position.offset).left_biased()?;
|
||||
let token = sema.descend_into_macros(original_token.clone());
|
||||
let scope = sema.scope_at_offset(&token.parent(), position.offset);
|
||||
let mut locals = vec![];
|
||||
scope.process_all_names(&mut |name, scope| {
|
||||
if let ScopeDef::Local(local) = scope {
|
||||
locals.push((name.to_string(), local));
|
||||
}
|
||||
});
|
||||
let mut ctx = CompletionContext {
|
||||
sema,
|
||||
scope,
|
||||
db,
|
||||
config,
|
||||
position,
|
||||
original_token,
|
||||
token,
|
||||
krate,
|
||||
expected_type: None,
|
||||
name_ref_syntax: None,
|
||||
function_syntax: None,
|
||||
use_item_syntax: None,
|
||||
record_lit_syntax: None,
|
||||
record_pat_syntax: None,
|
||||
record_field_syntax: None,
|
||||
impl_def: None,
|
||||
active_parameter: ActiveParameter::at(db, position),
|
||||
is_param: false,
|
||||
is_pat_binding_or_const: false,
|
||||
is_irrefutable_pat_binding: false,
|
||||
is_trivial_path: false,
|
||||
path_qual: None,
|
||||
after_if: false,
|
||||
can_be_stmt: false,
|
||||
is_expr: false,
|
||||
is_new_item: false,
|
||||
dot_receiver: None,
|
||||
dot_receiver_is_ambiguous_float_literal: false,
|
||||
is_call: false,
|
||||
is_pattern_call: false,
|
||||
is_macro_call: false,
|
||||
is_path_type: false,
|
||||
has_type_args: false,
|
||||
attribute_under_caret: None,
|
||||
mod_declaration_under_caret: None,
|
||||
unsafe_is_prev: false,
|
||||
if_is_prev: false,
|
||||
block_expr_parent: false,
|
||||
bind_pat_parent: false,
|
||||
ref_pat_parent: false,
|
||||
in_loop_body: false,
|
||||
has_trait_parent: false,
|
||||
has_impl_parent: false,
|
||||
inside_impl_trait_block: false,
|
||||
has_field_list_parent: false,
|
||||
trait_as_prev_sibling: false,
|
||||
impl_as_prev_sibling: false,
|
||||
is_match_arm: false,
|
||||
has_item_list_or_source_file_parent: false,
|
||||
for_is_prev2: false,
|
||||
fn_is_prev: false,
|
||||
incomplete_let: false,
|
||||
locals,
|
||||
};
|
||||
|
||||
let mut original_file = original_file.syntax().clone();
|
||||
let mut hypothetical_file = file_with_fake_ident.syntax().clone();
|
||||
let mut offset = position.offset;
|
||||
let mut fake_ident_token = fake_ident_token;
|
||||
|
||||
// Are we inside a macro call?
|
||||
while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
|
||||
find_node_at_offset::<ast::MacroCall>(&original_file, offset),
|
||||
find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
|
||||
) {
|
||||
if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
|
||||
!= macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
|
||||
{
|
||||
break;
|
||||
}
|
||||
let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
|
||||
Some(tt) => tt,
|
||||
None => break,
|
||||
};
|
||||
if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
|
||||
ctx.sema.expand(&actual_macro_call),
|
||||
ctx.sema.speculative_expand(
|
||||
&actual_macro_call,
|
||||
&hypothetical_args,
|
||||
fake_ident_token,
|
||||
),
|
||||
) {
|
||||
let new_offset = hypothetical_expansion.1.text_range().start();
|
||||
if new_offset > actual_expansion.text_range().end() {
|
||||
break;
|
||||
}
|
||||
original_file = actual_expansion;
|
||||
hypothetical_file = hypothetical_expansion.0;
|
||||
fake_ident_token = hypothetical_expansion.1;
|
||||
offset = new_offset;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.fill_keyword_patterns(&hypothetical_file, offset);
|
||||
ctx.fill(&original_file, hypothetical_file, offset);
|
||||
Some(ctx)
|
||||
}
|
||||
|
||||
/// Checks whether completions in that particular case don't make much sense.
|
||||
/// Examples:
|
||||
/// - `fn $0` -- we expect function name, it's unlikely that "hint" will be helpful.
|
||||
/// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names.
|
||||
/// - `for _ i$0` -- obviously, it'll be "in" keyword.
|
||||
pub(crate) fn no_completion_required(&self) -> bool {
|
||||
(self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2
|
||||
}
|
||||
|
||||
/// The range of the identifier that is being completed.
|
||||
pub(crate) fn source_range(&self) -> TextRange {
|
||||
// check kind of macro-expanded token, but use range of original token
|
||||
let kind = self.token.kind();
|
||||
if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() {
|
||||
mark::hit!(completes_if_prefix_is_keyword);
|
||||
self.original_token.text_range()
|
||||
} else {
|
||||
TextRange::empty(self.position.offset)
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
|
||||
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
|
||||
let syntax_element = NodeOrToken::Token(fake_ident_token);
|
||||
self.block_expr_parent = has_block_expr_parent(syntax_element.clone());
|
||||
self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone());
|
||||
self.if_is_prev = if_is_prev(syntax_element.clone());
|
||||
self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone());
|
||||
self.ref_pat_parent = has_ref_parent(syntax_element.clone());
|
||||
self.in_loop_body = is_in_loop_body(syntax_element.clone());
|
||||
self.has_trait_parent = has_trait_parent(syntax_element.clone());
|
||||
self.has_impl_parent = has_impl_parent(syntax_element.clone());
|
||||
self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
|
||||
self.has_field_list_parent = has_field_list_parent(syntax_element.clone());
|
||||
self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
|
||||
self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
|
||||
self.is_match_arm = is_match_arm(syntax_element.clone());
|
||||
self.has_item_list_or_source_file_parent =
|
||||
has_item_list_or_source_file_parent(syntax_element.clone());
|
||||
self.mod_declaration_under_caret =
|
||||
find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
|
||||
.filter(|module| module.item_list().is_none());
|
||||
self.for_is_prev2 = for_is_prev2(syntax_element.clone());
|
||||
self.fn_is_prev = fn_is_prev(syntax_element.clone());
|
||||
self.incomplete_let =
|
||||
syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| {
|
||||
it.syntax().text_range().end() == syntax_element.text_range().end()
|
||||
});
|
||||
}
|
||||
|
||||
fn fill_impl_def(&mut self) {
|
||||
self.impl_def = self
|
||||
.sema
|
||||
.ancestors_with_macros(self.token.parent())
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::Impl::cast);
|
||||
}
|
||||
|
||||
fn fill(
|
||||
&mut self,
|
||||
original_file: &SyntaxNode,
|
||||
file_with_fake_ident: SyntaxNode,
|
||||
offset: TextSize,
|
||||
) {
|
||||
// FIXME: this is wrong in at least two cases:
|
||||
// * when there's no token `foo($0)`
|
||||
// * when there is a token, but it happens to have type of it's own
|
||||
self.expected_type = self
|
||||
.token
|
||||
.ancestors()
|
||||
.find_map(|node| {
|
||||
let ty = match_ast! {
|
||||
match node {
|
||||
ast::Pat(it) => self.sema.type_of_pat(&it),
|
||||
ast::Expr(it) => self.sema.type_of_expr(&it),
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
Some(ty)
|
||||
})
|
||||
.flatten();
|
||||
self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
|
||||
|
||||
// First, let's try to complete a reference to some declaration.
|
||||
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
|
||||
// Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
|
||||
// See RFC#1685.
|
||||
if is_node::<ast::Param>(name_ref.syntax()) {
|
||||
self.is_param = true;
|
||||
return;
|
||||
}
|
||||
// FIXME: remove this (V) duplication and make the check more precise
|
||||
if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() {
|
||||
self.record_pat_syntax =
|
||||
self.sema.find_node_at_offset_with_macros(&original_file, offset);
|
||||
}
|
||||
self.classify_name_ref(original_file, name_ref, offset);
|
||||
}
|
||||
|
||||
// Otherwise, see if this is a declaration. We can use heuristics to
|
||||
// suggest declaration names, see `CompletionKind::Magic`.
|
||||
if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
|
||||
if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) {
|
||||
self.is_pat_binding_or_const = true;
|
||||
if bind_pat.at_token().is_some()
|
||||
|| bind_pat.ref_token().is_some()
|
||||
|| bind_pat.mut_token().is_some()
|
||||
{
|
||||
self.is_pat_binding_or_const = false;
|
||||
}
|
||||
if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
|
||||
self.is_pat_binding_or_const = false;
|
||||
}
|
||||
if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| {
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::LetStmt(it) => Some(it.pat()),
|
||||
ast::Param(it) => Some(it.pat()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) {
|
||||
self.is_pat_binding_or_const = false;
|
||||
self.is_irrefutable_pat_binding = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.fill_impl_def();
|
||||
}
|
||||
if is_node::<ast::Param>(name.syntax()) {
|
||||
self.is_param = true;
|
||||
return;
|
||||
}
|
||||
// FIXME: remove this (^) duplication and make the check more precise
|
||||
if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() {
|
||||
self.record_pat_syntax =
|
||||
self.sema.find_node_at_offset_with_macros(&original_file, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_name_ref(
|
||||
&mut self,
|
||||
original_file: &SyntaxNode,
|
||||
name_ref: ast::NameRef,
|
||||
offset: TextSize,
|
||||
) {
|
||||
self.name_ref_syntax =
|
||||
find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
|
||||
let name_range = name_ref.syntax().text_range();
|
||||
if ast::RecordExprField::for_field_name(&name_ref).is_some() {
|
||||
self.record_lit_syntax =
|
||||
self.sema.find_node_at_offset_with_macros(&original_file, offset);
|
||||
}
|
||||
|
||||
self.fill_impl_def();
|
||||
|
||||
let top_node = name_ref
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.take_while(|it| it.text_range() == name_range)
|
||||
.last()
|
||||
.unwrap();
|
||||
|
||||
match top_node.parent().map(|it| it.kind()) {
|
||||
Some(SOURCE_FILE) | Some(ITEM_LIST) => {
|
||||
self.is_new_item = true;
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.use_item_syntax =
|
||||
self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast);
|
||||
|
||||
self.function_syntax = self
|
||||
.sema
|
||||
.ancestors_with_macros(self.token.parent())
|
||||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::Fn::cast);
|
||||
|
||||
self.record_field_syntax = self
|
||||
.sema
|
||||
.ancestors_with_macros(self.token.parent())
|
||||
.take_while(|it| {
|
||||
it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR
|
||||
})
|
||||
.find_map(ast::RecordExprField::cast);
|
||||
|
||||
let parent = match name_ref.syntax().parent() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
|
||||
let path = segment.parent_path();
|
||||
self.is_call = path
|
||||
.syntax()
|
||||
.parent()
|
||||
.and_then(ast::PathExpr::cast)
|
||||
.and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
|
||||
.is_some();
|
||||
self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
|
||||
self.is_pattern_call =
|
||||
path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some();
|
||||
|
||||
self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
|
||||
self.has_type_args = segment.generic_arg_list().is_some();
|
||||
|
||||
if let Some(path) = path_or_use_tree_qualifier(&path) {
|
||||
self.path_qual = path
|
||||
.segment()
|
||||
.and_then(|it| {
|
||||
find_node_with_range::<ast::PathSegment>(
|
||||
original_file,
|
||||
it.syntax().text_range(),
|
||||
)
|
||||
})
|
||||
.map(|it| it.parent_path());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(segment) = path.segment() {
|
||||
if segment.coloncolon_token().is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.is_trivial_path = true;
|
||||
|
||||
// Find either enclosing expr statement (thing with `;`) or a
|
||||
// block. If block, check that we are the last expr.
|
||||
self.can_be_stmt = name_ref
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(|node| {
|
||||
if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
|
||||
return Some(stmt.syntax().text_range() == name_ref.syntax().text_range());
|
||||
}
|
||||
if let Some(block) = ast::BlockExpr::cast(node) {
|
||||
return Some(
|
||||
block.tail_expr().map(|e| e.syntax().text_range())
|
||||
== Some(name_ref.syntax().text_range()),
|
||||
);
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap_or(false);
|
||||
self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some();
|
||||
|
||||
if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
|
||||
if let Some(if_expr) =
|
||||
self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
|
||||
{
|
||||
if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start()
|
||||
{
|
||||
self.after_if = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
|
||||
// The receiver comes before the point of insertion of the fake
|
||||
// ident, so it should have the same range in the non-modified file
|
||||
self.dot_receiver = field_expr
|
||||
.expr()
|
||||
.map(|e| e.syntax().text_range())
|
||||
.and_then(|r| find_node_with_range(original_file, r));
|
||||
self.dot_receiver_is_ambiguous_float_literal =
|
||||
if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
|
||||
match l.kind() {
|
||||
ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
}
|
||||
if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
|
||||
// As above
|
||||
self.dot_receiver = method_call_expr
|
||||
.receiver()
|
||||
.map(|e| e.syntax().text_range())
|
||||
.and_then(|r| find_node_with_range(original_file, r));
|
||||
self.is_call = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
|
||||
syntax.covering_element(range).ancestors().find_map(N::cast)
|
||||
}
|
||||
|
||||
fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
|
||||
match node.ancestors().find_map(N::cast) {
|
||||
None => false,
|
||||
Some(n) => n.syntax().text_range() == node.text_range(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> {
|
||||
if let Some(qual) = path.qualifier() {
|
||||
return Some(qual);
|
||||
}
|
||||
let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
|
||||
let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
|
||||
use_tree.path()
|
||||
}
|
5
crates/ide_completion/src/generated_lint_completions.rs
Normal file
5
crates/ide_completion/src/generated_lint_completions.rs
Normal file
File diff suppressed because one or more lines are too long
450
crates/ide_completion/src/item.rs
Normal file
450
crates/ide_completion/src/item.rs
Normal file
|
@ -0,0 +1,450 @@
|
|||
//! See `CompletionItem` structure.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use hir::{Documentation, ModPath, Mutability};
|
||||
use ide_db::{
|
||||
helpers::{
|
||||
insert_use::{self, ImportScope, MergeBehavior},
|
||||
mod_path_to_ast, SnippetCap,
|
||||
},
|
||||
SymbolKind,
|
||||
};
|
||||
use stdx::{impl_from, never};
|
||||
use syntax::{algo, TextRange};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
/// `CompletionItem` describes a single completion variant in the editor pop-up.
|
||||
/// It is basically a POD with various properties. To construct a
|
||||
/// `CompletionItem`, use `new` method and the `Builder` struct.
|
||||
#[derive(Clone)]
|
||||
pub struct CompletionItem {
|
||||
/// Used only internally in tests, to check only specific kind of
|
||||
/// completion (postfix, keyword, reference, etc).
|
||||
#[allow(unused)]
|
||||
pub(crate) completion_kind: CompletionKind,
|
||||
/// Label in the completion pop up which identifies completion.
|
||||
label: String,
|
||||
/// Range of identifier that is being completed.
|
||||
///
|
||||
/// It should be used primarily for UI, but we also use this to convert
|
||||
/// genetic TextEdit into LSP's completion edit (see conv.rs).
|
||||
///
|
||||
/// `source_range` must contain the completion offset. `insert_text` should
|
||||
/// start with what `source_range` points to, or VSCode will filter out the
|
||||
/// completion silently.
|
||||
source_range: TextRange,
|
||||
/// What happens when user selects this item.
|
||||
///
|
||||
/// Typically, replaces `source_range` with new identifier.
|
||||
text_edit: TextEdit,
|
||||
|
||||
insert_text_format: InsertTextFormat,
|
||||
|
||||
/// What item (struct, function, etc) are we completing.
|
||||
kind: Option<CompletionItemKind>,
|
||||
|
||||
/// Lookup is used to check if completion item indeed can complete current
|
||||
/// ident.
|
||||
///
|
||||
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
|
||||
/// contains `bar` sub sequence), and `quux` will rejected.
|
||||
lookup: Option<String>,
|
||||
|
||||
/// Additional info to show in the UI pop up.
|
||||
detail: Option<String>,
|
||||
documentation: Option<Documentation>,
|
||||
|
||||
/// Whether this item is marked as deprecated
|
||||
deprecated: bool,
|
||||
|
||||
/// If completing a function call, ask the editor to show parameter popup
|
||||
/// after completion.
|
||||
trigger_call_info: bool,
|
||||
|
||||
/// Score is useful to pre select or display in better order completion items
|
||||
score: Option<CompletionScore>,
|
||||
|
||||
/// Indicates that a reference or mutable reference to this variable is a
|
||||
/// possible match.
|
||||
ref_match: Option<(Mutability, CompletionScore)>,
|
||||
|
||||
/// The import data to add to completion's edits.
|
||||
import_to_add: Option<ImportEdit>,
|
||||
}
|
||||
|
||||
// We use custom debug for CompletionItem to make snapshot tests more readable.
|
||||
impl fmt::Debug for CompletionItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut s = f.debug_struct("CompletionItem");
|
||||
s.field("label", &self.label()).field("source_range", &self.source_range());
|
||||
if self.text_edit().len() == 1 {
|
||||
let atom = &self.text_edit().iter().next().unwrap();
|
||||
s.field("delete", &atom.delete);
|
||||
s.field("insert", &atom.insert);
|
||||
} else {
|
||||
s.field("text_edit", &self.text_edit);
|
||||
}
|
||||
if let Some(kind) = self.kind().as_ref() {
|
||||
s.field("kind", kind);
|
||||
}
|
||||
if self.lookup() != self.label() {
|
||||
s.field("lookup", &self.lookup());
|
||||
}
|
||||
if let Some(detail) = self.detail() {
|
||||
s.field("detail", &detail);
|
||||
}
|
||||
if let Some(documentation) = self.documentation() {
|
||||
s.field("documentation", &documentation);
|
||||
}
|
||||
if self.deprecated {
|
||||
s.field("deprecated", &true);
|
||||
}
|
||||
if let Some(score) = &self.score {
|
||||
s.field("score", score);
|
||||
}
|
||||
if self.trigger_call_info {
|
||||
s.field("trigger_call_info", &true);
|
||||
}
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum CompletionScore {
|
||||
/// If only type match
|
||||
TypeMatch,
|
||||
/// If type and name match
|
||||
TypeAndNameMatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CompletionItemKind {
|
||||
SymbolKind(SymbolKind),
|
||||
Attribute,
|
||||
Binding,
|
||||
BuiltinType,
|
||||
Keyword,
|
||||
Method,
|
||||
Snippet,
|
||||
UnresolvedReference,
|
||||
}
|
||||
|
||||
impl_from!(SymbolKind for CompletionItemKind);
|
||||
|
||||
impl CompletionItemKind {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn tag(&self) -> &'static str {
|
||||
match self {
|
||||
CompletionItemKind::SymbolKind(kind) => match kind {
|
||||
SymbolKind::Const => "ct",
|
||||
SymbolKind::ConstParam => "cp",
|
||||
SymbolKind::Enum => "en",
|
||||
SymbolKind::Field => "fd",
|
||||
SymbolKind::Function => "fn",
|
||||
SymbolKind::Impl => "im",
|
||||
SymbolKind::Label => "lb",
|
||||
SymbolKind::LifetimeParam => "lt",
|
||||
SymbolKind::Local => "lc",
|
||||
SymbolKind::Macro => "ma",
|
||||
SymbolKind::Module => "md",
|
||||
SymbolKind::SelfParam => "sp",
|
||||
SymbolKind::Static => "sc",
|
||||
SymbolKind::Struct => "st",
|
||||
SymbolKind::Trait => "tt",
|
||||
SymbolKind::TypeAlias => "ta",
|
||||
SymbolKind::TypeParam => "tp",
|
||||
SymbolKind::Union => "un",
|
||||
SymbolKind::ValueParam => "vp",
|
||||
SymbolKind::Variant => "ev",
|
||||
},
|
||||
CompletionItemKind::Attribute => "at",
|
||||
CompletionItemKind::Binding => "bn",
|
||||
CompletionItemKind::BuiltinType => "bt",
|
||||
CompletionItemKind::Keyword => "kw",
|
||||
CompletionItemKind::Method => "me",
|
||||
CompletionItemKind::Snippet => "sn",
|
||||
CompletionItemKind::UnresolvedReference => "??",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub(crate) enum CompletionKind {
|
||||
/// Parser-based keyword completion.
|
||||
Keyword,
|
||||
/// Your usual "complete all valid identifiers".
|
||||
Reference,
|
||||
/// "Secret sauce" completions.
|
||||
Magic,
|
||||
Snippet,
|
||||
Postfix,
|
||||
BuiltinType,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum InsertTextFormat {
|
||||
PlainText,
|
||||
Snippet,
|
||||
}
|
||||
|
||||
impl CompletionItem {
|
||||
pub(crate) fn new(
|
||||
completion_kind: CompletionKind,
|
||||
source_range: TextRange,
|
||||
label: impl Into<String>,
|
||||
) -> Builder {
|
||||
let label = label.into();
|
||||
Builder {
|
||||
source_range,
|
||||
completion_kind,
|
||||
label,
|
||||
insert_text: None,
|
||||
insert_text_format: InsertTextFormat::PlainText,
|
||||
detail: None,
|
||||
documentation: None,
|
||||
lookup: None,
|
||||
kind: None,
|
||||
text_edit: None,
|
||||
deprecated: None,
|
||||
trigger_call_info: None,
|
||||
score: None,
|
||||
ref_match: None,
|
||||
import_to_add: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// What user sees in pop-up in the UI.
|
||||
pub fn label(&self) -> &str {
|
||||
&self.label
|
||||
}
|
||||
pub fn source_range(&self) -> TextRange {
|
||||
self.source_range
|
||||
}
|
||||
|
||||
pub fn insert_text_format(&self) -> InsertTextFormat {
|
||||
self.insert_text_format
|
||||
}
|
||||
|
||||
pub fn text_edit(&self) -> &TextEdit {
|
||||
&self.text_edit
|
||||
}
|
||||
|
||||
/// Short one-line additional information, like a type
|
||||
pub fn detail(&self) -> Option<&str> {
|
||||
self.detail.as_deref()
|
||||
}
|
||||
/// A doc-comment
|
||||
pub fn documentation(&self) -> Option<Documentation> {
|
||||
self.documentation.clone()
|
||||
}
|
||||
/// What string is used for filtering.
|
||||
pub fn lookup(&self) -> &str {
|
||||
self.lookup.as_deref().unwrap_or(&self.label)
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> Option<CompletionItemKind> {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub fn deprecated(&self) -> bool {
|
||||
self.deprecated
|
||||
}
|
||||
|
||||
pub fn score(&self) -> Option<CompletionScore> {
|
||||
self.score
|
||||
}
|
||||
|
||||
pub fn trigger_call_info(&self) -> bool {
|
||||
self.trigger_call_info
|
||||
}
|
||||
|
||||
pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
|
||||
self.ref_match
|
||||
}
|
||||
|
||||
pub fn import_to_add(&self) -> Option<&ImportEdit> {
|
||||
self.import_to_add.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// An extra import to add after the completion is applied.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImportEdit {
|
||||
pub import_path: ModPath,
|
||||
pub import_scope: ImportScope,
|
||||
pub import_for_trait_assoc_item: bool,
|
||||
}
|
||||
|
||||
impl ImportEdit {
|
||||
/// Attempts to insert the import to the given scope, producing a text edit.
|
||||
/// May return no edit in edge cases, such as scope already containing the import.
|
||||
pub fn to_text_edit(&self, merge_behavior: Option<MergeBehavior>) -> Option<TextEdit> {
|
||||
let _p = profile::span("ImportEdit::to_text_edit");
|
||||
|
||||
let rewriter = insert_use::insert_use(
|
||||
&self.import_scope,
|
||||
mod_path_to_ast(&self.import_path),
|
||||
merge_behavior,
|
||||
);
|
||||
let old_ast = rewriter.rewrite_root()?;
|
||||
let mut import_insert = TextEdit::builder();
|
||||
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
|
||||
|
||||
Some(import_insert.finish())
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper to make `CompletionItem`s.
|
||||
#[must_use]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Builder {
|
||||
source_range: TextRange,
|
||||
completion_kind: CompletionKind,
|
||||
import_to_add: Option<ImportEdit>,
|
||||
label: String,
|
||||
insert_text: Option<String>,
|
||||
insert_text_format: InsertTextFormat,
|
||||
detail: Option<String>,
|
||||
documentation: Option<Documentation>,
|
||||
lookup: Option<String>,
|
||||
kind: Option<CompletionItemKind>,
|
||||
text_edit: Option<TextEdit>,
|
||||
deprecated: Option<bool>,
|
||||
trigger_call_info: Option<bool>,
|
||||
score: Option<CompletionScore>,
|
||||
ref_match: Option<(Mutability, CompletionScore)>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub(crate) fn build(self) -> CompletionItem {
|
||||
let _p = profile::span("item::Builder::build");
|
||||
|
||||
let mut label = self.label;
|
||||
let mut lookup = self.lookup;
|
||||
let mut insert_text = self.insert_text;
|
||||
|
||||
if let Some(import_to_add) = self.import_to_add.as_ref() {
|
||||
if import_to_add.import_for_trait_assoc_item {
|
||||
lookup = lookup.or_else(|| Some(label.clone()));
|
||||
insert_text = insert_text.or_else(|| Some(label.clone()));
|
||||
label = format!("{} ({})", label, import_to_add.import_path);
|
||||
} else {
|
||||
let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
|
||||
let _ = import_path_without_last_segment.pop_segment();
|
||||
|
||||
if !import_path_without_last_segment.segments().is_empty() {
|
||||
lookup = lookup.or_else(|| Some(label.clone()));
|
||||
insert_text = insert_text.or_else(|| Some(label.clone()));
|
||||
label = format!("{}::{}", import_path_without_last_segment, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let text_edit = match self.text_edit {
|
||||
Some(it) => it,
|
||||
None => {
|
||||
TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
|
||||
}
|
||||
};
|
||||
|
||||
CompletionItem {
|
||||
source_range: self.source_range,
|
||||
label,
|
||||
insert_text_format: self.insert_text_format,
|
||||
text_edit,
|
||||
detail: self.detail,
|
||||
documentation: self.documentation,
|
||||
lookup,
|
||||
kind: self.kind,
|
||||
completion_kind: self.completion_kind,
|
||||
deprecated: self.deprecated.unwrap_or(false),
|
||||
trigger_call_info: self.trigger_call_info.unwrap_or(false),
|
||||
score: self.score,
|
||||
ref_match: self.ref_match,
|
||||
import_to_add: self.import_to_add,
|
||||
}
|
||||
}
|
||||
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
|
||||
self.lookup = Some(lookup.into());
|
||||
self
|
||||
}
|
||||
pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
|
||||
self.label = label.into();
|
||||
self
|
||||
}
|
||||
pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
|
||||
self.insert_text = Some(insert_text.into());
|
||||
self
|
||||
}
|
||||
pub(crate) fn insert_snippet(
|
||||
mut self,
|
||||
_cap: SnippetCap,
|
||||
snippet: impl Into<String>,
|
||||
) -> Builder {
|
||||
self.insert_text_format = InsertTextFormat::Snippet;
|
||||
self.insert_text(snippet)
|
||||
}
|
||||
pub(crate) fn kind(mut self, kind: impl Into<CompletionItemKind>) -> Builder {
|
||||
self.kind = Some(kind.into());
|
||||
self
|
||||
}
|
||||
pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
|
||||
self.text_edit = Some(edit);
|
||||
self
|
||||
}
|
||||
pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder {
|
||||
self.insert_text_format = InsertTextFormat::Snippet;
|
||||
self.text_edit(edit)
|
||||
}
|
||||
pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
|
||||
self.set_detail(Some(detail))
|
||||
}
|
||||
pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
|
||||
self.detail = detail.map(Into::into);
|
||||
if let Some(detail) = &self.detail {
|
||||
if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
|
||||
self.detail = Some(detail.splitn(2, '\n').next().unwrap().to_string());
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub(crate) fn documentation(self, docs: Documentation) -> Builder {
|
||||
self.set_documentation(Some(docs))
|
||||
}
|
||||
pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
|
||||
self.documentation = docs.map(Into::into);
|
||||
self
|
||||
}
|
||||
pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
|
||||
self.deprecated = Some(deprecated);
|
||||
self
|
||||
}
|
||||
pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
|
||||
self.score = Some(score);
|
||||
self
|
||||
}
|
||||
pub(crate) fn trigger_call_info(mut self) -> Builder {
|
||||
self.trigger_call_info = Some(true);
|
||||
self
|
||||
}
|
||||
pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder {
|
||||
self.import_to_add = import_to_add;
|
||||
self
|
||||
}
|
||||
pub(crate) fn set_ref_match(
|
||||
mut self,
|
||||
ref_match: Option<(Mutability, CompletionScore)>,
|
||||
) -> Builder {
|
||||
self.ref_match = ref_match;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<CompletionItem> for Builder {
|
||||
fn into(self) -> CompletionItem {
|
||||
self.build()
|
||||
}
|
||||
}
|
275
crates/ide_completion/src/lib.rs
Normal file
275
crates/ide_completion/src/lib.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
//! `completions` crate provides utilities for generating completions of user input.
|
||||
|
||||
mod config;
|
||||
mod item;
|
||||
mod context;
|
||||
mod patterns;
|
||||
mod generated_lint_completions;
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
mod render;
|
||||
|
||||
mod completions;
|
||||
|
||||
use completions::flyimport::position_for_import;
|
||||
use ide_db::{
|
||||
base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase,
|
||||
};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
|
||||
|
||||
pub use crate::{
|
||||
config::CompletionConfig,
|
||||
item::{CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat},
|
||||
};
|
||||
|
||||
//FIXME: split the following feature into fine-grained features.
|
||||
|
||||
// Feature: Magic Completions
|
||||
//
|
||||
// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
|
||||
// completions as well:
|
||||
//
|
||||
// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
|
||||
// is placed at the appropriate position. Even though `if` is easy to type, you
|
||||
// still want to complete it, to get ` { }` for free! `return` is inserted with a
|
||||
// space or `;` depending on the return type of the function.
|
||||
//
|
||||
// When completing a function call, `()` are automatically inserted. If a function
|
||||
// takes arguments, the cursor is positioned inside the parenthesis.
|
||||
//
|
||||
// There are postfix completions, which can be triggered by typing something like
|
||||
// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
|
||||
//
|
||||
// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
|
||||
// - `expr.match` -> `match expr {}`
|
||||
// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
|
||||
// - `expr.ref` -> `&expr`
|
||||
// - `expr.refm` -> `&mut expr`
|
||||
// - `expr.let` -> `let $0 = expr;`
|
||||
// - `expr.letm` -> `let mut $0 = expr;`
|
||||
// - `expr.not` -> `!expr`
|
||||
// - `expr.dbg` -> `dbg!(expr)`
|
||||
// - `expr.dbgr` -> `dbg!(&expr)`
|
||||
// - `expr.call` -> `(expr)`
|
||||
//
|
||||
// There also snippet completions:
|
||||
//
|
||||
// .Expressions
|
||||
// - `pd` -> `eprintln!(" = {:?}", );`
|
||||
// - `ppd` -> `eprintln!(" = {:#?}", );`
|
||||
//
|
||||
// .Items
|
||||
// - `tfn` -> `#[test] fn feature(){}`
|
||||
// - `tmod` ->
|
||||
// ```rust
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
//
|
||||
// #[test]
|
||||
// fn test_name() {}
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
|
||||
// Those are the additional completion options with automatic `use` import and options from all project importable items,
|
||||
// fuzzy matched agains the completion imput.
|
||||
|
||||
/// Main entry point for completion. We run completion as a two-phase process.
|
||||
///
|
||||
/// First, we look at the position and collect a so-called `CompletionContext.
|
||||
/// This is a somewhat messy process, because, during completion, syntax tree is
|
||||
/// incomplete and can look really weird.
|
||||
///
|
||||
/// Once the context is collected, we run a series of completion routines which
|
||||
/// look at the context and produce completion items. One subtlety about this
|
||||
/// phase is that completion engine should not filter by the substring which is
|
||||
/// already present, it should give all possible variants for the identifier at
|
||||
/// the caret. In other words, for
|
||||
///
|
||||
/// ```no_run
|
||||
/// fn f() {
|
||||
/// let foo = 92;
|
||||
/// let _ = bar$0
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `foo` *should* be present among the completion variants. Filtering by
|
||||
/// identifier prefix/fuzzy match should be done higher in the stack, together
|
||||
/// with ordering of completions (currently this is done by the client).
|
||||
pub fn completions(
|
||||
db: &RootDatabase,
|
||||
config: &CompletionConfig,
|
||||
position: FilePosition,
|
||||
) -> Option<Completions> {
|
||||
let ctx = CompletionContext::new(db, position, config)?;
|
||||
|
||||
if ctx.no_completion_required() {
|
||||
// No work required here.
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut acc = Completions::default();
|
||||
completions::attribute::complete_attribute(&mut acc, &ctx);
|
||||
completions::fn_param::complete_fn_param(&mut acc, &ctx);
|
||||
completions::keyword::complete_expr_keyword(&mut acc, &ctx);
|
||||
completions::keyword::complete_use_tree_keyword(&mut acc, &ctx);
|
||||
completions::snippet::complete_expr_snippet(&mut acc, &ctx);
|
||||
completions::snippet::complete_item_snippet(&mut acc, &ctx);
|
||||
completions::qualified_path::complete_qualified_path(&mut acc, &ctx);
|
||||
completions::unqualified_path::complete_unqualified_path(&mut acc, &ctx);
|
||||
completions::dot::complete_dot(&mut acc, &ctx);
|
||||
completions::record::complete_record(&mut acc, &ctx);
|
||||
completions::pattern::complete_pattern(&mut acc, &ctx);
|
||||
completions::postfix::complete_postfix(&mut acc, &ctx);
|
||||
completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
|
||||
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
|
||||
completions::mod_::complete_mod(&mut acc, &ctx);
|
||||
completions::flyimport::import_on_the_fly(&mut acc, &ctx);
|
||||
|
||||
Some(acc)
|
||||
}
|
||||
|
||||
/// Resolves additional completion data at the position given.
|
||||
pub fn resolve_completion_edits(
|
||||
db: &RootDatabase,
|
||||
config: &CompletionConfig,
|
||||
position: FilePosition,
|
||||
full_import_path: &str,
|
||||
imported_name: String,
|
||||
import_for_trait_assoc_item: bool,
|
||||
) -> Option<Vec<TextEdit>> {
|
||||
let ctx = CompletionContext::new(db, position, config)?;
|
||||
let position_for_import = position_for_import(&ctx, None)?;
|
||||
let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
|
||||
|
||||
let current_module = ctx.sema.scope(position_for_import).module()?;
|
||||
let current_crate = current_module.krate();
|
||||
|
||||
let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name)
|
||||
.filter_map(|candidate| {
|
||||
let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
|
||||
current_module.find_use_path(db, item)
|
||||
})
|
||||
.find(|mod_path| mod_path.to_string() == full_import_path)?;
|
||||
|
||||
ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
|
||||
.to_text_edit(config.insert_use.merge)
|
||||
.map(|edit| vec![edit])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test_utils::{self, TEST_CONFIG};
|
||||
|
||||
struct DetailAndDocumentation<'a> {
|
||||
detail: &'a str,
|
||||
documentation: &'a str,
|
||||
}
|
||||
|
||||
fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
|
||||
let (db, position) = test_utils::position(ra_fixture);
|
||||
let config = TEST_CONFIG;
|
||||
let completions: Vec<_> = crate::completions(&db, &config, position).unwrap().into();
|
||||
for item in completions {
|
||||
if item.detail() == Some(expected.detail) {
|
||||
let opt = item.documentation();
|
||||
let doc = opt.as_ref().map(|it| it.as_str());
|
||||
assert_eq!(doc, Some(expected.documentation));
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("completion detail not found: {}", expected.detail)
|
||||
}
|
||||
|
||||
fn check_no_completion(ra_fixture: &str) {
|
||||
let (db, position) = test_utils::position(ra_fixture);
|
||||
let config = TEST_CONFIG;
|
||||
|
||||
let completions: Option<Vec<String>> = crate::completions(&db, &config, position)
|
||||
.and_then(|completions| {
|
||||
let completions: Vec<_> = completions.into();
|
||||
if completions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(completions)
|
||||
}
|
||||
})
|
||||
.map(|completions| {
|
||||
completions.into_iter().map(|completion| format!("{:?}", completion)).collect()
|
||||
});
|
||||
|
||||
// `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic.
|
||||
assert_eq!(completions, None, "Completions were generated, but weren't expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
|
||||
check_detail_and_documentation(
|
||||
r#"
|
||||
macro_rules! bar {
|
||||
() => {
|
||||
struct Bar;
|
||||
impl Bar {
|
||||
#[doc = "Do the foo"]
|
||||
fn foo(&self) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar!();
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar;
|
||||
bar.fo$0;
|
||||
}
|
||||
"#,
|
||||
DetailAndDocumentation { detail: "-> ()", documentation: "Do the foo" },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
|
||||
check_detail_and_documentation(
|
||||
r#"
|
||||
macro_rules! bar {
|
||||
() => {
|
||||
struct Bar;
|
||||
impl Bar {
|
||||
/// Do the foo
|
||||
fn foo(&self) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar!();
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar;
|
||||
bar.fo$0;
|
||||
}
|
||||
"#,
|
||||
DetailAndDocumentation { detail: "-> ()", documentation: " Do the foo" },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_completions_required() {
|
||||
// There must be no hint for 'in' keyword.
|
||||
check_no_completion(r#"fn foo() { for i i$0 }"#);
|
||||
// After 'in' keyword hints may be spawned.
|
||||
check_detail_and_documentation(
|
||||
r#"
|
||||
/// Do the foo
|
||||
fn foo() -> &'static str { "foo" }
|
||||
|
||||
fn bar() {
|
||||
for c in fo$0
|
||||
}
|
||||
"#,
|
||||
DetailAndDocumentation { detail: "-> &str", documentation: "Do the foo" },
|
||||
);
|
||||
}
|
||||
}
|
249
crates/ide_completion/src/patterns.rs
Normal file
249
crates/ide_completion/src/patterns.rs
Normal file
|
@ -0,0 +1,249 @@
|
|||
//! Patterns telling us certain facts about current syntax element, they are used in completion context
|
||||
|
||||
use syntax::{
|
||||
algo::non_trivia_sibling,
|
||||
ast::{self, LoopBodyOwner},
|
||||
match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken, T,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
|
||||
|
||||
pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element)
|
||||
.filter(|it| it.kind() == ASSOC_ITEM_LIST)
|
||||
.and_then(|it| it.parent())
|
||||
.filter(|it| it.kind() == TRAIT)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_trait_parent() {
|
||||
check_pattern_is_applicable(r"trait A { f$0 }", has_trait_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element)
|
||||
.filter(|it| it.kind() == ASSOC_ITEM_LIST)
|
||||
.and_then(|it| it.parent())
|
||||
.filter(|it| it.kind() == IMPL)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_impl_parent() {
|
||||
check_pattern_is_applicable(r"impl A { f$0 }", has_impl_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
|
||||
// Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
|
||||
// where we only check the first parent with different text range.
|
||||
element
|
||||
.ancestors()
|
||||
.find(|it| it.kind() == IMPL)
|
||||
.map(|it| ast::Impl::cast(it).unwrap())
|
||||
.map(|it| it.trait_().is_some())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
#[test]
|
||||
fn test_inside_impl_trait_block() {
|
||||
check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block);
|
||||
check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block);
|
||||
check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block);
|
||||
check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block);
|
||||
}
|
||||
|
||||
pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_field_list_parent() {
|
||||
check_pattern_is_applicable(r"struct Foo { f$0 }", has_field_list_parent);
|
||||
check_pattern_is_applicable(r"struct Foo { f$0 pub f: i32}", has_field_list_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_block_expr_parent() {
|
||||
check_pattern_is_applicable(r"fn my_fn() { let a = 2; f$0 }", has_block_expr_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
|
||||
element.ancestors().find(|it| it.kind() == IDENT_PAT).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_bind_pat_parent() {
|
||||
check_pattern_is_applicable(r"fn my_fn(m$0) {}", has_bind_pat_parent);
|
||||
check_pattern_is_applicable(r"fn my_fn() { let m$0 }", has_bind_pat_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element)
|
||||
.filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_ref_parent() {
|
||||
check_pattern_is_applicable(r"fn my_fn(&m$0) {}", has_ref_parent);
|
||||
check_pattern_is_applicable(r"fn my() { let &m$0 }", has_ref_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool {
|
||||
let ancestor = not_same_range_ancestor(element);
|
||||
if !ancestor.is_some() {
|
||||
return true;
|
||||
}
|
||||
ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_item_list_or_source_file_parent() {
|
||||
check_pattern_is_applicable(r"i$0", has_item_list_or_source_file_parent);
|
||||
check_pattern_is_applicable(r"mod foo { f$0 }", has_item_list_or_source_file_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
|
||||
&& previous_sibling_or_ancestor_sibling(element)
|
||||
.and_then(|it| it.into_token())
|
||||
.filter(|it| it.kind() == FAT_ARROW)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_is_match_arm() {
|
||||
check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm);
|
||||
}
|
||||
|
||||
pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.filter(|it| it.kind() == T![unsafe])
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_unsafe_is_prev() {
|
||||
check_pattern_is_applicable(r"unsafe i$0", unsafe_is_prev);
|
||||
}
|
||||
|
||||
pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.filter(|it| it.kind() == T![if])
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.filter(|it| it.kind() == T![fn])
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_fn_is_prev() {
|
||||
check_pattern_is_applicable(r"fn l$0", fn_is_prev);
|
||||
}
|
||||
|
||||
/// Check if the token previous to the previous one is `for`.
|
||||
/// For example, `for _ i$0` => true.
|
||||
pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.filter(|it| it.kind() == T![for])
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_for_is_prev2() {
|
||||
check_pattern_is_applicable(r"for i i$0", for_is_prev2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_is_prev() {
|
||||
check_pattern_is_applicable(r"if l$0", if_is_prev);
|
||||
}
|
||||
|
||||
pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool {
|
||||
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_trait_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"trait A w$0 {}", has_trait_as_prev_sibling);
|
||||
}
|
||||
|
||||
pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool {
|
||||
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_impl_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"impl A w$0 {}", has_impl_as_prev_sibling);
|
||||
}
|
||||
|
||||
pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
|
||||
let leaf = match element {
|
||||
NodeOrToken::Node(node) => node,
|
||||
NodeOrToken::Token(token) => token.parent(),
|
||||
};
|
||||
for node in leaf.ancestors() {
|
||||
if node.kind() == FN || node.kind() == CLOSURE_EXPR {
|
||||
break;
|
||||
}
|
||||
let loop_body = match_ast! {
|
||||
match node {
|
||||
ast::ForExpr(it) => it.loop_body(),
|
||||
ast::WhileExpr(it) => it.loop_body(),
|
||||
ast::LoopExpr(it) => it.loop_body(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
if let Some(body) = loop_body {
|
||||
if body.syntax().text_range().contains_range(leaf.text_range()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
|
||||
element
|
||||
.ancestors()
|
||||
.take_while(|it| it.text_range() == element.text_range())
|
||||
.last()
|
||||
.and_then(|it| it.parent())
|
||||
}
|
||||
|
||||
fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
|
||||
let mut token = token.prev_token();
|
||||
while let Some(inner) = token.clone() {
|
||||
if !inner.kind().is_trivia() {
|
||||
return Some(inner);
|
||||
} else {
|
||||
token = inner.prev_token();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> {
|
||||
let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev);
|
||||
if let Some(sibling) = token_sibling {
|
||||
Some(sibling)
|
||||
} else {
|
||||
// if not trying to find first ancestor which has such a sibling
|
||||
let node = match element {
|
||||
NodeOrToken::Node(node) => node,
|
||||
NodeOrToken::Token(token) => token.parent(),
|
||||
};
|
||||
let range = node.text_range();
|
||||
let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?;
|
||||
let prev_sibling_node = top_node.ancestors().find(|it| {
|
||||
non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some()
|
||||
})?;
|
||||
non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
|
||||
}
|
||||
}
|
945
crates/ide_completion/src/render.rs
Normal file
945
crates/ide_completion/src/render.rs
Normal file
|
@ -0,0 +1,945 @@
|
|||
//! `render` module provides utilities for rendering completion suggestions
|
||||
//! into code pieces that will be presented to user.
|
||||
|
||||
pub(crate) mod macro_;
|
||||
pub(crate) mod function;
|
||||
pub(crate) mod enum_variant;
|
||||
pub(crate) mod const_;
|
||||
pub(crate) mod pattern;
|
||||
pub(crate) mod type_alias;
|
||||
|
||||
mod builder_ext;
|
||||
|
||||
use hir::{
|
||||
AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type,
|
||||
};
|
||||
use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind};
|
||||
use syntax::TextRange;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
item::ImportEdit, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
|
||||
CompletionScore,
|
||||
};
|
||||
|
||||
use crate::render::{enum_variant::render_variant, function::render_fn, macro_::render_macro};
|
||||
|
||||
pub(crate) fn render_field<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
field: hir::Field,
|
||||
ty: &Type,
|
||||
) -> CompletionItem {
|
||||
Render::new(ctx).add_field(field, ty)
|
||||
}
|
||||
|
||||
pub(crate) fn render_tuple_field<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
field: usize,
|
||||
ty: &Type,
|
||||
) -> CompletionItem {
|
||||
Render::new(ctx).add_tuple_field(field, ty)
|
||||
}
|
||||
|
||||
pub(crate) fn render_resolution<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
local_name: String,
|
||||
resolution: &ScopeDef,
|
||||
) -> Option<CompletionItem> {
|
||||
Render::new(ctx).render_resolution(local_name, None, resolution)
|
||||
}
|
||||
|
||||
pub(crate) fn render_resolution_with_import<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
import_edit: ImportEdit,
|
||||
resolution: &ScopeDef,
|
||||
) -> Option<CompletionItem> {
|
||||
let local_name = match resolution {
|
||||
ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
|
||||
ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
|
||||
ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
|
||||
_ => import_edit.import_path.segments().last()?.to_string(),
|
||||
};
|
||||
Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| {
|
||||
item.completion_kind = CompletionKind::Magic;
|
||||
item
|
||||
})
|
||||
}
|
||||
|
||||
/// Interface for data and methods required for items rendering.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RenderContext<'a> {
|
||||
completion: &'a CompletionContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> RenderContext<'a> {
|
||||
pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
|
||||
RenderContext { completion }
|
||||
}
|
||||
|
||||
fn snippet_cap(&self) -> Option<SnippetCap> {
|
||||
self.completion.config.snippet_cap.clone()
|
||||
}
|
||||
|
||||
fn db(&self) -> &'a RootDatabase {
|
||||
&self.completion.db
|
||||
}
|
||||
|
||||
fn source_range(&self) -> TextRange {
|
||||
self.completion.source_range()
|
||||
}
|
||||
|
||||
fn is_deprecated(&self, node: impl HasAttrs) -> bool {
|
||||
let attrs = node.attrs(self.db());
|
||||
attrs.by_key("deprecated").exists() || attrs.by_key("rustc_deprecated").exists()
|
||||
}
|
||||
|
||||
fn is_deprecated_assoc_item(&self, as_assoc_item: impl AsAssocItem) -> bool {
|
||||
let db = self.db();
|
||||
let assoc = match as_assoc_item.as_assoc_item(db) {
|
||||
Some(assoc) => assoc,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let is_assoc_deprecated = match assoc {
|
||||
hir::AssocItem::Function(it) => self.is_deprecated(it),
|
||||
hir::AssocItem::Const(it) => self.is_deprecated(it),
|
||||
hir::AssocItem::TypeAlias(it) => self.is_deprecated(it),
|
||||
};
|
||||
is_assoc_deprecated
|
||||
|| assoc.containing_trait(db).map(|trait_| self.is_deprecated(trait_)).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn docs(&self, node: impl HasAttrs) -> Option<Documentation> {
|
||||
node.docs(self.db())
|
||||
}
|
||||
|
||||
fn active_name_and_type(&self) -> Option<(String, Type)> {
|
||||
if let Some(record_field) = &self.completion.record_field_syntax {
|
||||
mark::hit!(record_field_type_match);
|
||||
let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?;
|
||||
Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db())))
|
||||
} else if let Some(active_parameter) = &self.completion.active_parameter {
|
||||
mark::hit!(active_param_type_match);
|
||||
Some((active_parameter.name.clone(), active_parameter.ty.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic renderer for completion items.
|
||||
#[derive(Debug)]
|
||||
struct Render<'a> {
|
||||
ctx: RenderContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Render<'a> {
|
||||
fn new(ctx: RenderContext<'a>) -> Render<'a> {
|
||||
Render { ctx }
|
||||
}
|
||||
|
||||
fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem {
|
||||
let is_deprecated = self.ctx.is_deprecated(field);
|
||||
let name = field.name(self.ctx.db());
|
||||
let mut item = CompletionItem::new(
|
||||
CompletionKind::Reference,
|
||||
self.ctx.source_range(),
|
||||
name.to_string(),
|
||||
)
|
||||
.kind(SymbolKind::Field)
|
||||
.detail(ty.display(self.ctx.db()).to_string())
|
||||
.set_documentation(field.docs(self.ctx.db()))
|
||||
.set_deprecated(is_deprecated);
|
||||
|
||||
if let Some(score) = compute_score(&self.ctx, &ty, &name.to_string()) {
|
||||
item = item.set_score(score);
|
||||
}
|
||||
|
||||
item.build()
|
||||
}
|
||||
|
||||
fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem {
|
||||
CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string())
|
||||
.kind(SymbolKind::Field)
|
||||
.detail(ty.display(self.ctx.db()).to_string())
|
||||
.build()
|
||||
}
|
||||
|
||||
fn render_resolution(
|
||||
self,
|
||||
local_name: String,
|
||||
import_to_add: Option<ImportEdit>,
|
||||
resolution: &ScopeDef,
|
||||
) -> Option<CompletionItem> {
|
||||
let _p = profile::span("render_resolution");
|
||||
use hir::ModuleDef::*;
|
||||
|
||||
let completion_kind = match resolution {
|
||||
ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType,
|
||||
_ => CompletionKind::Reference,
|
||||
};
|
||||
|
||||
let kind = match resolution {
|
||||
ScopeDef::ModuleDef(Function(func)) => {
|
||||
return render_fn(self.ctx, import_to_add, Some(local_name), *func);
|
||||
}
|
||||
ScopeDef::ModuleDef(Variant(_))
|
||||
if self.ctx.completion.is_pat_binding_or_const
|
||||
| self.ctx.completion.is_irrefutable_pat_binding =>
|
||||
{
|
||||
CompletionItemKind::SymbolKind(SymbolKind::Variant)
|
||||
}
|
||||
ScopeDef::ModuleDef(Variant(var)) => {
|
||||
let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None);
|
||||
return Some(item);
|
||||
}
|
||||
ScopeDef::MacroDef(mac) => {
|
||||
let item = render_macro(self.ctx, import_to_add, local_name, *mac);
|
||||
return item;
|
||||
}
|
||||
|
||||
ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::SymbolKind(SymbolKind::Module),
|
||||
ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt {
|
||||
hir::Adt::Struct(_) => SymbolKind::Struct,
|
||||
hir::Adt::Union(_) => SymbolKind::Union,
|
||||
hir::Adt::Enum(_) => SymbolKind::Enum,
|
||||
}),
|
||||
ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const),
|
||||
ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static),
|
||||
ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait),
|
||||
ScopeDef::ModuleDef(TypeAlias(..)) => {
|
||||
CompletionItemKind::SymbolKind(SymbolKind::TypeAlias)
|
||||
}
|
||||
ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
|
||||
ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param {
|
||||
hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam,
|
||||
hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam,
|
||||
hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
|
||||
}),
|
||||
ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local),
|
||||
ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
|
||||
CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
|
||||
}
|
||||
ScopeDef::Unknown => {
|
||||
let item = CompletionItem::new(
|
||||
CompletionKind::Reference,
|
||||
self.ctx.source_range(),
|
||||
local_name,
|
||||
)
|
||||
.kind(CompletionItemKind::UnresolvedReference)
|
||||
.add_import(import_to_add)
|
||||
.build();
|
||||
return Some(item);
|
||||
}
|
||||
};
|
||||
|
||||
let mut item =
|
||||
CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone());
|
||||
if let ScopeDef::Local(local) = resolution {
|
||||
let ty = local.ty(self.ctx.db());
|
||||
if !ty.is_unknown() {
|
||||
item = item.detail(ty.display(self.ctx.db()).to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let mut ref_match = None;
|
||||
if let ScopeDef::Local(local) = resolution {
|
||||
if let Some((active_name, active_type)) = self.ctx.active_name_and_type() {
|
||||
let ty = local.ty(self.ctx.db());
|
||||
if let Some(score) =
|
||||
compute_score_from_active(&active_type, &active_name, &ty, &local_name)
|
||||
{
|
||||
item = item.set_score(score);
|
||||
}
|
||||
ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Add `<>` for generic types
|
||||
if self.ctx.completion.is_path_type
|
||||
&& !self.ctx.completion.has_type_args
|
||||
&& self.ctx.completion.config.add_call_parenthesis
|
||||
{
|
||||
if let Some(cap) = self.ctx.snippet_cap() {
|
||||
let has_non_default_type_params = match resolution {
|
||||
ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(TypeAlias(it)) => {
|
||||
it.has_non_default_type_params(self.ctx.db())
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if has_non_default_type_params {
|
||||
mark::hit!(inserts_angle_brackets_for_generics);
|
||||
item = item
|
||||
.lookup_by(local_name.clone())
|
||||
.label(format!("{}<…>", local_name))
|
||||
.insert_snippet(cap, format!("{}<$0>", local_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(
|
||||
item.kind(kind)
|
||||
.add_import(import_to_add)
|
||||
.set_ref_match(ref_match)
|
||||
.set_documentation(self.docs(resolution))
|
||||
.set_deprecated(self.is_deprecated(resolution))
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> {
|
||||
use hir::ModuleDef::*;
|
||||
match resolution {
|
||||
ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(Variant(it)) => it.docs(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()),
|
||||
ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_deprecated(&self, resolution: &ScopeDef) -> bool {
|
||||
match resolution {
|
||||
ScopeDef::ModuleDef(it) => self.ctx.is_deprecated_assoc_item(*it),
|
||||
ScopeDef::MacroDef(it) => self.ctx.is_deprecated(*it),
|
||||
ScopeDef::GenericParam(it) => self.ctx.is_deprecated(*it),
|
||||
ScopeDef::AdtSelfType(it) => self.ctx.is_deprecated(*it),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_score_from_active(
|
||||
active_type: &Type,
|
||||
active_name: &str,
|
||||
ty: &Type,
|
||||
name: &str,
|
||||
) -> Option<CompletionScore> {
|
||||
// Compute score
|
||||
// For the same type
|
||||
if active_type != ty {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut res = CompletionScore::TypeMatch;
|
||||
|
||||
// If same type + same name then go top position
|
||||
if active_name == name {
|
||||
res = CompletionScore::TypeAndNameMatch
|
||||
}
|
||||
|
||||
Some(res)
|
||||
}
|
||||
fn refed_type_matches(
|
||||
active_type: &Type,
|
||||
active_name: &str,
|
||||
ty: &Type,
|
||||
name: &str,
|
||||
) -> Option<(Mutability, CompletionScore)> {
|
||||
let derefed_active = active_type.remove_ref()?;
|
||||
let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?;
|
||||
Some((
|
||||
if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared },
|
||||
score,
|
||||
))
|
||||
}
|
||||
|
||||
fn compute_score(ctx: &RenderContext, ty: &Type, name: &str) -> Option<CompletionScore> {
|
||||
let (active_name, active_type) = ctx.active_name_and_type()?;
|
||||
compute_score_from_active(&active_type, &active_name, ty, name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cmp::Reverse;
|
||||
|
||||
use expect_test::{expect, Expect};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG},
|
||||
CompletionKind, CompletionScore,
|
||||
};
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = do_completion(ra_fixture, CompletionKind::Reference);
|
||||
expect.assert_debug_eq(&actual);
|
||||
}
|
||||
|
||||
fn check_scores(ra_fixture: &str, expect: Expect) {
|
||||
fn display_score(score: Option<CompletionScore>) -> &'static str {
|
||||
match score {
|
||||
Some(CompletionScore::TypeMatch) => "[type]",
|
||||
Some(CompletionScore::TypeAndNameMatch) => "[type+name]",
|
||||
None => "[]".into(),
|
||||
}
|
||||
}
|
||||
|
||||
let mut completions = get_all_items(TEST_CONFIG, ra_fixture);
|
||||
completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string()));
|
||||
let actual = completions
|
||||
.into_iter()
|
||||
.filter(|it| it.completion_kind == CompletionKind::Reference)
|
||||
.map(|it| {
|
||||
let tag = it.kind().unwrap().tag();
|
||||
let score = display_score(it.score());
|
||||
format!("{} {} {}\n", tag, it.label(), score)
|
||||
})
|
||||
.collect::<String>();
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_detail_includes_record_fields() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Foo { x: i32, y: i32 } }
|
||||
|
||||
fn main() { Foo::Fo$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "Foo",
|
||||
source_range: 54..56,
|
||||
delete: 54..56,
|
||||
insert: "Foo",
|
||||
kind: SymbolKind(
|
||||
Variant,
|
||||
),
|
||||
detail: "{ x: i32, y: i32 }",
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_detail_doesnt_include_tuple_fields() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Foo (i32, i32) }
|
||||
|
||||
fn main() { Foo::Fo$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "Foo(…)",
|
||||
source_range: 46..48,
|
||||
delete: 46..48,
|
||||
insert: "Foo($0)",
|
||||
kind: SymbolKind(
|
||||
Variant,
|
||||
),
|
||||
lookup: "Foo",
|
||||
detail: "(i32, i32)",
|
||||
trigger_call_info: true,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_detail_just_parentheses_for_unit() {
|
||||
check(
|
||||
r#"
|
||||
enum Foo { Foo }
|
||||
|
||||
fn main() { Foo::Fo$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "Foo",
|
||||
source_range: 35..37,
|
||||
delete: 35..37,
|
||||
insert: "Foo",
|
||||
kind: SymbolKind(
|
||||
Variant,
|
||||
),
|
||||
detail: "()",
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_enums_by_two_qualifiers() {
|
||||
check(
|
||||
r#"
|
||||
mod m {
|
||||
pub enum Spam { Foo, Bar(i32) }
|
||||
}
|
||||
fn main() { let _: m::Spam = S$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "Spam::Bar(…)",
|
||||
source_range: 75..76,
|
||||
delete: 75..76,
|
||||
insert: "Spam::Bar($0)",
|
||||
kind: SymbolKind(
|
||||
Variant,
|
||||
),
|
||||
lookup: "Spam::Bar",
|
||||
detail: "(i32)",
|
||||
trigger_call_info: true,
|
||||
},
|
||||
CompletionItem {
|
||||
label: "m",
|
||||
source_range: 75..76,
|
||||
delete: 75..76,
|
||||
insert: "m",
|
||||
kind: SymbolKind(
|
||||
Module,
|
||||
),
|
||||
},
|
||||
CompletionItem {
|
||||
label: "m::Spam::Foo",
|
||||
source_range: 75..76,
|
||||
delete: 75..76,
|
||||
insert: "m::Spam::Foo",
|
||||
kind: SymbolKind(
|
||||
Variant,
|
||||
),
|
||||
lookup: "Spam::Foo",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "main()",
|
||||
source_range: 75..76,
|
||||
delete: 75..76,
|
||||
insert: "main()$0",
|
||||
kind: SymbolKind(
|
||||
Function,
|
||||
),
|
||||
lookup: "main",
|
||||
detail: "-> ()",
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets_deprecated_flag_in_items() {
|
||||
check(
|
||||
r#"
|
||||
#[deprecated]
|
||||
fn something_deprecated() {}
|
||||
#[rustc_deprecated(since = "1.0.0")]
|
||||
fn something_else_deprecated() {}
|
||||
|
||||
fn main() { som$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "main()",
|
||||
source_range: 127..130,
|
||||
delete: 127..130,
|
||||
insert: "main()$0",
|
||||
kind: SymbolKind(
|
||||
Function,
|
||||
),
|
||||
lookup: "main",
|
||||
detail: "-> ()",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "something_deprecated()",
|
||||
source_range: 127..130,
|
||||
delete: 127..130,
|
||||
insert: "something_deprecated()$0",
|
||||
kind: SymbolKind(
|
||||
Function,
|
||||
),
|
||||
lookup: "something_deprecated",
|
||||
detail: "-> ()",
|
||||
deprecated: true,
|
||||
},
|
||||
CompletionItem {
|
||||
label: "something_else_deprecated()",
|
||||
source_range: 127..130,
|
||||
delete: 127..130,
|
||||
insert: "something_else_deprecated()$0",
|
||||
kind: SymbolKind(
|
||||
Function,
|
||||
),
|
||||
lookup: "something_else_deprecated",
|
||||
detail: "-> ()",
|
||||
deprecated: true,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
struct A { #[deprecated] the_field: u32 }
|
||||
fn foo() { A { the$0 } }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "the_field",
|
||||
source_range: 57..60,
|
||||
delete: 57..60,
|
||||
insert: "the_field",
|
||||
kind: SymbolKind(
|
||||
Field,
|
||||
),
|
||||
detail: "u32",
|
||||
deprecated: true,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_docs() {
|
||||
check(
|
||||
r#"
|
||||
struct S {
|
||||
/// Field docs
|
||||
foo:
|
||||
}
|
||||
impl S {
|
||||
/// Method docs
|
||||
fn bar(self) { self.$0 }
|
||||
}"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "bar()",
|
||||
source_range: 94..94,
|
||||
delete: 94..94,
|
||||
insert: "bar()$0",
|
||||
kind: Method,
|
||||
lookup: "bar",
|
||||
detail: "-> ()",
|
||||
documentation: Documentation(
|
||||
"Method docs",
|
||||
),
|
||||
},
|
||||
CompletionItem {
|
||||
label: "foo",
|
||||
source_range: 94..94,
|
||||
delete: 94..94,
|
||||
insert: "foo",
|
||||
kind: SymbolKind(
|
||||
Field,
|
||||
),
|
||||
detail: "{unknown}",
|
||||
documentation: Documentation(
|
||||
"Field docs",
|
||||
),
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
use self::my$0;
|
||||
|
||||
/// mod docs
|
||||
mod my { }
|
||||
|
||||
/// enum docs
|
||||
enum E {
|
||||
/// variant docs
|
||||
V
|
||||
}
|
||||
use self::E::*;
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "E",
|
||||
source_range: 10..12,
|
||||
delete: 10..12,
|
||||
insert: "E",
|
||||
kind: SymbolKind(
|
||||
Enum,
|
||||
),
|
||||
documentation: Documentation(
|
||||
"enum docs",
|
||||
),
|
||||
},
|
||||
CompletionItem {
|
||||
label: "V",
|
||||
source_range: 10..12,
|
||||
delete: 10..12,
|
||||
insert: "V",
|
||||
kind: SymbolKind(
|
||||
Variant,
|
||||
),
|
||||
detail: "()",
|
||||
documentation: Documentation(
|
||||
"variant docs",
|
||||
),
|
||||
},
|
||||
CompletionItem {
|
||||
label: "my",
|
||||
source_range: 10..12,
|
||||
delete: 10..12,
|
||||
insert: "my",
|
||||
kind: SymbolKind(
|
||||
Module,
|
||||
),
|
||||
documentation: Documentation(
|
||||
"mod docs",
|
||||
),
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_render_attrs() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
#[inline]
|
||||
fn the_method(&self) { }
|
||||
}
|
||||
fn foo(s: S) { s.$0 }
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "the_method()",
|
||||
source_range: 81..81,
|
||||
delete: 81..81,
|
||||
insert: "the_method()$0",
|
||||
kind: Method,
|
||||
lookup: "the_method",
|
||||
detail: "-> ()",
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_call_parens_if_fn_ptr_needed() {
|
||||
mark::check!(no_call_parens_if_fn_ptr_needed);
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
fn foo(foo: u8, bar: u8) {}
|
||||
struct ManualVtable { f: fn(u8, u8) }
|
||||
|
||||
fn main() -> ManualVtable {
|
||||
ManualVtable { f: f$0 }
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo(foo: u8, bar: u8) {}
|
||||
struct ManualVtable { f: fn(u8, u8) }
|
||||
|
||||
fn main() -> ManualVtable {
|
||||
ManualVtable { f: foo }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_parens_in_use_item() {
|
||||
mark::check!(no_parens_in_use_item);
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
mod m { pub fn foo() {} }
|
||||
use crate::m::f$0;
|
||||
"#,
|
||||
r#"
|
||||
mod m { pub fn foo() {} }
|
||||
use crate::m::foo;
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_parens_in_call() {
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
fn foo(x: i32) {}
|
||||
fn main() { f$0(); }
|
||||
"#,
|
||||
r#"
|
||||
fn foo(x: i32) {}
|
||||
fn main() { foo(); }
|
||||
"#,
|
||||
);
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
struct Foo;
|
||||
impl Foo { fn foo(&self){} }
|
||||
fn f(foo: &Foo) { foo.f$0(); }
|
||||
"#,
|
||||
r#"
|
||||
struct Foo;
|
||||
impl Foo { fn foo(&self){} }
|
||||
fn f(foo: &Foo) { foo.foo(); }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserts_angle_brackets_for_generics() {
|
||||
mark::check!(inserts_angle_brackets_for_generics);
|
||||
check_edit(
|
||||
"Vec",
|
||||
r#"
|
||||
struct Vec<T> {}
|
||||
fn foo(xs: Ve$0)
|
||||
"#,
|
||||
r#"
|
||||
struct Vec<T> {}
|
||||
fn foo(xs: Vec<$0>)
|
||||
"#,
|
||||
);
|
||||
check_edit(
|
||||
"Vec",
|
||||
r#"
|
||||
type Vec<T> = (T,);
|
||||
fn foo(xs: Ve$0)
|
||||
"#,
|
||||
r#"
|
||||
type Vec<T> = (T,);
|
||||
fn foo(xs: Vec<$0>)
|
||||
"#,
|
||||
);
|
||||
check_edit(
|
||||
"Vec",
|
||||
r#"
|
||||
struct Vec<T = i128> {}
|
||||
fn foo(xs: Ve$0)
|
||||
"#,
|
||||
r#"
|
||||
struct Vec<T = i128> {}
|
||||
fn foo(xs: Vec)
|
||||
"#,
|
||||
);
|
||||
check_edit(
|
||||
"Vec",
|
||||
r#"
|
||||
struct Vec<T> {}
|
||||
fn foo(xs: Ve$0<i128>)
|
||||
"#,
|
||||
r#"
|
||||
struct Vec<T> {}
|
||||
fn foo(xs: Vec<i128>)
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_param_score() {
|
||||
mark::check!(active_param_type_match);
|
||||
check_scores(
|
||||
r#"
|
||||
struct S { foo: i64, bar: u32, baz: u32 }
|
||||
fn test(bar: u32) { }
|
||||
fn foo(s: S) { test(s.$0) }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd bar [type+name]
|
||||
fd baz [type]
|
||||
fd foo []
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_scores() {
|
||||
mark::check!(record_field_type_match);
|
||||
check_scores(
|
||||
r#"
|
||||
struct A { foo: i64, bar: u32, baz: u32 }
|
||||
struct B { x: (), y: f32, bar: u32 }
|
||||
fn foo(a: A) { B { bar: a.$0 }; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd bar [type+name]
|
||||
fd baz [type]
|
||||
fd foo []
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_and_call_scores() {
|
||||
check_scores(
|
||||
r#"
|
||||
struct A { foo: i64, bar: u32, baz: u32 }
|
||||
struct B { x: (), y: f32, bar: u32 }
|
||||
fn f(foo: i64) { }
|
||||
fn foo(a: A) { B { bar: f(a.$0) }; }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd foo [type+name]
|
||||
fd bar []
|
||||
fd baz []
|
||||
"#]],
|
||||
);
|
||||
check_scores(
|
||||
r#"
|
||||
struct A { foo: i64, bar: u32, baz: u32 }
|
||||
struct B { x: (), y: f32, bar: u32 }
|
||||
fn f(foo: i64) { }
|
||||
fn foo(a: A) { f(B { bar: a.$0 }); }
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd bar [type+name]
|
||||
fd baz [type]
|
||||
fd foo []
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prioritize_exact_ref_match() {
|
||||
check_scores(
|
||||
r#"
|
||||
struct WorldSnapshot { _f: () };
|
||||
fn go(world: &WorldSnapshot) { go(w$0) }
|
||||
"#,
|
||||
expect![[r#"
|
||||
lc world [type+name]
|
||||
st WorldSnapshot []
|
||||
fn go(…) []
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_arguments() {
|
||||
check_scores(
|
||||
r#"
|
||||
struct Foo;
|
||||
fn f(foo: &Foo) { f(foo, w$0) }
|
||||
"#,
|
||||
expect![[r#"
|
||||
st Foo []
|
||||
fn f(…) []
|
||||
lc foo []
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
94
crates/ide_completion/src/render/builder_ext.rs
Normal file
94
crates/ide_completion/src/render/builder_ext.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
//! Extensions for `Builder` structure required for item rendering.
|
||||
|
||||
use itertools::Itertools;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{item::Builder, CompletionContext};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum Params {
|
||||
Named(Vec<String>),
|
||||
Anonymous(usize),
|
||||
}
|
||||
|
||||
impl Params {
|
||||
pub(super) fn len(&self) -> usize {
|
||||
match self {
|
||||
Params::Named(xs) => xs.len(),
|
||||
Params::Anonymous(len) => *len,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn should_add_parens(&self, ctx: &CompletionContext) -> bool {
|
||||
if !ctx.config.add_call_parenthesis {
|
||||
return false;
|
||||
}
|
||||
if ctx.use_item_syntax.is_some() {
|
||||
mark::hit!(no_parens_in_use_item);
|
||||
return false;
|
||||
}
|
||||
if ctx.is_pattern_call {
|
||||
return false;
|
||||
}
|
||||
if ctx.is_call {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't add parentheses if the expected type is some function reference.
|
||||
if let Some(ty) = &ctx.expected_type {
|
||||
if ty.is_fn() {
|
||||
mark::hit!(no_call_parens_if_fn_ptr_needed);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing prevents us from adding parentheses
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn add_call_parens(
|
||||
mut self,
|
||||
ctx: &CompletionContext,
|
||||
name: String,
|
||||
params: Params,
|
||||
) -> Builder {
|
||||
if !self.should_add_parens(ctx) {
|
||||
return self;
|
||||
}
|
||||
|
||||
let cap = match ctx.config.snippet_cap {
|
||||
Some(it) => it,
|
||||
None => return self,
|
||||
};
|
||||
// If not an import, add parenthesis automatically.
|
||||
mark::hit!(inserts_parens_for_function_calls);
|
||||
|
||||
let (snippet, label) = if params.is_empty() {
|
||||
(format!("{}()$0", name), format!("{}()", name))
|
||||
} else {
|
||||
self = self.trigger_call_info();
|
||||
let snippet = match (ctx.config.add_call_argument_snippets, params) {
|
||||
(true, Params::Named(params)) => {
|
||||
let function_params_snippet =
|
||||
params.iter().enumerate().format_with(", ", |(index, param_name), f| {
|
||||
f(&format_args!("${{{}:{}}}", index + 1, param_name))
|
||||
});
|
||||
format!("{}({})$0", name, function_params_snippet)
|
||||
}
|
||||
_ => {
|
||||
mark::hit!(suppress_arg_snippets);
|
||||
format!("{}($0)", name)
|
||||
}
|
||||
};
|
||||
|
||||
(snippet, format!("{}(…)", name))
|
||||
};
|
||||
self.lookup_by(name).label(label).insert_snippet(cap, snippet)
|
||||
}
|
||||
}
|
59
crates/ide_completion/src/render/const_.rs
Normal file
59
crates/ide_completion/src/render/const_.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
//! Renderer for `const` fields.
|
||||
|
||||
use hir::HasSource;
|
||||
use ide_db::SymbolKind;
|
||||
use syntax::{
|
||||
ast::{Const, NameOwner},
|
||||
display::const_label,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
item::{CompletionItem, CompletionKind},
|
||||
render::RenderContext,
|
||||
};
|
||||
|
||||
pub(crate) fn render_const<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
const_: hir::Const,
|
||||
) -> Option<CompletionItem> {
|
||||
ConstRender::new(ctx, const_)?.render()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConstRender<'a> {
|
||||
ctx: RenderContext<'a>,
|
||||
const_: hir::Const,
|
||||
ast_node: Const,
|
||||
}
|
||||
|
||||
impl<'a> ConstRender<'a> {
|
||||
fn new(ctx: RenderContext<'a>, const_: hir::Const) -> Option<ConstRender<'a>> {
|
||||
let ast_node = const_.source(ctx.db())?.value;
|
||||
Some(ConstRender { ctx, const_, ast_node })
|
||||
}
|
||||
|
||||
fn render(self) -> Option<CompletionItem> {
|
||||
let name = self.name()?;
|
||||
let detail = self.detail();
|
||||
|
||||
let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name)
|
||||
.kind(SymbolKind::Const)
|
||||
.set_documentation(self.ctx.docs(self.const_))
|
||||
.set_deprecated(
|
||||
self.ctx.is_deprecated(self.const_)
|
||||
|| self.ctx.is_deprecated_assoc_item(self.const_),
|
||||
)
|
||||
.detail(detail)
|
||||
.build();
|
||||
|
||||
Some(item)
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<String> {
|
||||
self.ast_node.name().map(|name| name.text().to_string())
|
||||
}
|
||||
|
||||
fn detail(&self) -> String {
|
||||
const_label(&self.ast_node)
|
||||
}
|
||||
}
|
131
crates/ide_completion/src/render/enum_variant.rs
Normal file
131
crates/ide_completion/src/render/enum_variant.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
//! Renderer for `enum` variants.
|
||||
|
||||
use hir::{HasAttrs, HirDisplay, ModPath, StructKind};
|
||||
use ide_db::SymbolKind;
|
||||
use itertools::Itertools;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
item::{CompletionItem, CompletionKind, ImportEdit},
|
||||
render::{builder_ext::Params, RenderContext},
|
||||
};
|
||||
|
||||
pub(crate) fn render_variant<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
import_to_add: Option<ImportEdit>,
|
||||
local_name: Option<String>,
|
||||
variant: hir::Variant,
|
||||
path: Option<ModPath>,
|
||||
) -> CompletionItem {
|
||||
let _p = profile::span("render_enum_variant");
|
||||
EnumRender::new(ctx, local_name, variant, path).render(import_to_add)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EnumRender<'a> {
|
||||
ctx: RenderContext<'a>,
|
||||
name: String,
|
||||
variant: hir::Variant,
|
||||
path: Option<ModPath>,
|
||||
qualified_name: String,
|
||||
short_qualified_name: String,
|
||||
variant_kind: StructKind,
|
||||
}
|
||||
|
||||
impl<'a> EnumRender<'a> {
|
||||
fn new(
|
||||
ctx: RenderContext<'a>,
|
||||
local_name: Option<String>,
|
||||
variant: hir::Variant,
|
||||
path: Option<ModPath>,
|
||||
) -> EnumRender<'a> {
|
||||
let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string());
|
||||
let variant_kind = variant.kind(ctx.db());
|
||||
|
||||
let (qualified_name, short_qualified_name) = match &path {
|
||||
Some(path) => {
|
||||
let full = path.to_string();
|
||||
let segments = path.segments();
|
||||
let short = segments[segments.len().saturating_sub(2)..].iter().join("::");
|
||||
(full, short)
|
||||
}
|
||||
None => (name.to_string(), name.to_string()),
|
||||
};
|
||||
|
||||
EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind }
|
||||
}
|
||||
|
||||
fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
|
||||
let mut builder = CompletionItem::new(
|
||||
CompletionKind::Reference,
|
||||
self.ctx.source_range(),
|
||||
self.qualified_name.clone(),
|
||||
)
|
||||
.kind(SymbolKind::Variant)
|
||||
.set_documentation(self.variant.docs(self.ctx.db()))
|
||||
.set_deprecated(self.ctx.is_deprecated(self.variant))
|
||||
.add_import(import_to_add)
|
||||
.detail(self.detail());
|
||||
|
||||
if self.variant_kind == StructKind::Tuple {
|
||||
mark::hit!(inserts_parens_for_tuple_enums);
|
||||
let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len());
|
||||
builder =
|
||||
builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params);
|
||||
} else if self.path.is_some() {
|
||||
builder = builder.lookup_by(self.short_qualified_name);
|
||||
}
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn detail(&self) -> String {
|
||||
let detail_types = self
|
||||
.variant
|
||||
.fields(self.ctx.db())
|
||||
.into_iter()
|
||||
.map(|field| (field.name(self.ctx.db()), field.signature_ty(self.ctx.db())));
|
||||
|
||||
match self.variant_kind {
|
||||
StructKind::Tuple | StructKind::Unit => format!(
|
||||
"({})",
|
||||
detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ")
|
||||
),
|
||||
StructKind::Record => format!(
|
||||
"{{ {} }}",
|
||||
detail_types
|
||||
.map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string()))
|
||||
.format(", ")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::test_utils::check_edit;
|
||||
|
||||
#[test]
|
||||
fn inserts_parens_for_tuple_enums() {
|
||||
mark::check!(inserts_parens_for_tuple_enums);
|
||||
check_edit(
|
||||
"Some",
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
use Option::*;
|
||||
fn main() -> Option<i32> {
|
||||
Som$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
use Option::*;
|
||||
fn main() -> Option<i32> {
|
||||
Some($0)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
345
crates/ide_completion/src/render/function.rs
Normal file
345
crates/ide_completion/src/render/function.rs
Normal file
|
@ -0,0 +1,345 @@
|
|||
//! Renderer for function calls.
|
||||
|
||||
use hir::{HasSource, HirDisplay, Type};
|
||||
use ide_db::SymbolKind;
|
||||
use syntax::ast::Fn;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
|
||||
render::{builder_ext::Params, RenderContext},
|
||||
};
|
||||
|
||||
pub(crate) fn render_fn<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
import_to_add: Option<ImportEdit>,
|
||||
local_name: Option<String>,
|
||||
fn_: hir::Function,
|
||||
) -> Option<CompletionItem> {
|
||||
let _p = profile::span("render_fn");
|
||||
Some(FunctionRender::new(ctx, local_name, fn_)?.render(import_to_add))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FunctionRender<'a> {
|
||||
ctx: RenderContext<'a>,
|
||||
name: String,
|
||||
func: hir::Function,
|
||||
ast_node: Fn,
|
||||
}
|
||||
|
||||
impl<'a> FunctionRender<'a> {
|
||||
fn new(
|
||||
ctx: RenderContext<'a>,
|
||||
local_name: Option<String>,
|
||||
fn_: hir::Function,
|
||||
) -> Option<FunctionRender<'a>> {
|
||||
let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string());
|
||||
let ast_node = fn_.source(ctx.db())?.value;
|
||||
|
||||
Some(FunctionRender { ctx, name, func: fn_, ast_node })
|
||||
}
|
||||
|
||||
fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
|
||||
let params = self.params();
|
||||
CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
|
||||
.kind(self.kind())
|
||||
.set_documentation(self.ctx.docs(self.func))
|
||||
.set_deprecated(
|
||||
self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func),
|
||||
)
|
||||
.detail(self.detail())
|
||||
.add_call_parens(self.ctx.completion, self.name, params)
|
||||
.add_import(import_to_add)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn detail(&self) -> String {
|
||||
let ty = self.func.ret_type(self.ctx.db());
|
||||
format!("-> {}", ty.display(self.ctx.db()))
|
||||
}
|
||||
|
||||
fn add_arg(&self, arg: &str, ty: &Type) -> String {
|
||||
if let Some(derefed_ty) = ty.remove_ref() {
|
||||
for (name, local) in self.ctx.completion.locals.iter() {
|
||||
if name == arg && local.ty(self.ctx.db()) == derefed_ty {
|
||||
let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" };
|
||||
return format!("{}{}", mutability, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
arg.to_string()
|
||||
}
|
||||
|
||||
fn params(&self) -> Params {
|
||||
let ast_params = match self.ast_node.param_list() {
|
||||
Some(it) => it,
|
||||
None => return Params::Named(Vec::new()),
|
||||
};
|
||||
|
||||
let mut params_pats = Vec::new();
|
||||
let params_ty = if self.ctx.completion.dot_receiver.is_some() {
|
||||
self.func.method_params(self.ctx.db()).unwrap_or_default()
|
||||
} else {
|
||||
if let Some(s) = ast_params.self_param() {
|
||||
mark::hit!(parens_for_method_call_as_assoc_fn);
|
||||
params_pats.push(Some(s.to_string()));
|
||||
}
|
||||
self.func.assoc_fn_params(self.ctx.db())
|
||||
};
|
||||
params_pats
|
||||
.extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string())));
|
||||
|
||||
let params = params_pats
|
||||
.into_iter()
|
||||
.zip(params_ty)
|
||||
.flat_map(|(pat, param_ty)| {
|
||||
let pat = pat?;
|
||||
let name = pat;
|
||||
let arg = name.trim_start_matches("mut ").trim_start_matches('_');
|
||||
Some(self.add_arg(arg, param_ty.ty()))
|
||||
})
|
||||
.collect();
|
||||
Params::Named(params)
|
||||
}
|
||||
|
||||
fn kind(&self) -> CompletionItemKind {
|
||||
if self.func.self_param(self.ctx.db()).is_some() {
|
||||
CompletionItemKind::Method
|
||||
} else {
|
||||
SymbolKind::Function.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
test_utils::{check_edit, check_edit_with_config, TEST_CONFIG},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn inserts_parens_for_function_calls() {
|
||||
mark::check!(inserts_parens_for_function_calls);
|
||||
check_edit(
|
||||
"no_args",
|
||||
r#"
|
||||
fn no_args() {}
|
||||
fn main() { no_$0 }
|
||||
"#,
|
||||
r#"
|
||||
fn no_args() {}
|
||||
fn main() { no_args()$0 }
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"with_args",
|
||||
r#"
|
||||
fn with_args(x: i32, y: String) {}
|
||||
fn main() { with_$0 }
|
||||
"#,
|
||||
r#"
|
||||
fn with_args(x: i32, y: String) {}
|
||||
fn main() { with_args(${1:x}, ${2:y})$0 }
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
fn bar(s: &S) { s.f$0 }
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
fn bar(s: &S) { s.foo()$0 }
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
struct S {}
|
||||
impl S {
|
||||
fn foo(&self, x: i32) {}
|
||||
}
|
||||
fn bar(s: &S) {
|
||||
s.f$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S {}
|
||||
impl S {
|
||||
fn foo(&self, x: i32) {}
|
||||
}
|
||||
fn bar(s: &S) {
|
||||
s.foo(${1:x})$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_for_method_call_as_assoc_fn() {
|
||||
mark::check!(parens_for_method_call_as_assoc_fn);
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
fn main() { S::f$0 }
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn foo(&self) {}
|
||||
}
|
||||
fn main() { S::foo(${1:&self})$0 }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suppress_arg_snippets() {
|
||||
mark::check!(suppress_arg_snippets);
|
||||
check_edit_with_config(
|
||||
CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG },
|
||||
"with_args",
|
||||
r#"
|
||||
fn with_args(x: i32, y: String) {}
|
||||
fn main() { with_$0 }
|
||||
"#,
|
||||
r#"
|
||||
fn with_args(x: i32, y: String) {}
|
||||
fn main() { with_args($0) }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_underscores_from_args() {
|
||||
check_edit(
|
||||
"foo",
|
||||
r#"
|
||||
fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
|
||||
fn main() { f$0 }
|
||||
"#,
|
||||
r#"
|
||||
fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
|
||||
fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_ref_when_matching_local_in_scope() {
|
||||
check_edit(
|
||||
"ref_arg",
|
||||
r#"
|
||||
struct Foo {}
|
||||
fn ref_arg(x: &Foo) {}
|
||||
fn main() {
|
||||
let x = Foo {};
|
||||
ref_ar$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo {}
|
||||
fn ref_arg(x: &Foo) {}
|
||||
fn main() {
|
||||
let x = Foo {};
|
||||
ref_arg(${1:&x})$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_mut_ref_when_matching_local_in_scope() {
|
||||
check_edit(
|
||||
"ref_arg",
|
||||
r#"
|
||||
struct Foo {}
|
||||
fn ref_arg(x: &mut Foo) {}
|
||||
fn main() {
|
||||
let x = Foo {};
|
||||
ref_ar$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo {}
|
||||
fn ref_arg(x: &mut Foo) {}
|
||||
fn main() {
|
||||
let x = Foo {};
|
||||
ref_arg(${1:&mut x})$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_ref_when_matching_local_in_scope_for_method() {
|
||||
check_edit(
|
||||
"apply_foo",
|
||||
r#"
|
||||
struct Foo {}
|
||||
struct Bar {}
|
||||
impl Bar {
|
||||
fn apply_foo(&self, x: &Foo) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = Foo {};
|
||||
let y = Bar {};
|
||||
y.$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo {}
|
||||
struct Bar {}
|
||||
impl Bar {
|
||||
fn apply_foo(&self, x: &Foo) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = Foo {};
|
||||
let y = Bar {};
|
||||
y.apply_foo(${1:&x})$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_mut_keyword_in_func_completion() {
|
||||
check_edit(
|
||||
"take_mutably",
|
||||
r#"
|
||||
fn take_mutably(mut x: &i32) {}
|
||||
|
||||
fn main() {
|
||||
take_m$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn take_mutably(mut x: &i32) {}
|
||||
|
||||
fn main() {
|
||||
take_mutably(${1:x})$0
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
214
crates/ide_completion/src/render/macro_.rs
Normal file
214
crates/ide_completion/src/render/macro_.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
//! Renderer for macro invocations.
|
||||
|
||||
use hir::{Documentation, HasSource};
|
||||
use ide_db::SymbolKind;
|
||||
use syntax::display::macro_label;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
item::{CompletionItem, CompletionKind, ImportEdit},
|
||||
render::RenderContext,
|
||||
};
|
||||
|
||||
pub(crate) fn render_macro<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
import_to_add: Option<ImportEdit>,
|
||||
name: String,
|
||||
macro_: hir::MacroDef,
|
||||
) -> Option<CompletionItem> {
|
||||
let _p = profile::span("render_macro");
|
||||
MacroRender::new(ctx, name, macro_).render(import_to_add)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacroRender<'a> {
|
||||
ctx: RenderContext<'a>,
|
||||
name: String,
|
||||
macro_: hir::MacroDef,
|
||||
docs: Option<Documentation>,
|
||||
bra: &'static str,
|
||||
ket: &'static str,
|
||||
}
|
||||
|
||||
impl<'a> MacroRender<'a> {
|
||||
fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> {
|
||||
let docs = ctx.docs(macro_);
|
||||
let docs_str = docs.as_ref().map_or("", |s| s.as_str());
|
||||
let (bra, ket) = guess_macro_braces(&name, docs_str);
|
||||
|
||||
MacroRender { ctx, name, macro_, docs, bra, ket }
|
||||
}
|
||||
|
||||
fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
|
||||
let mut builder =
|
||||
CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label())
|
||||
.kind(SymbolKind::Macro)
|
||||
.set_documentation(self.docs.clone())
|
||||
.set_deprecated(self.ctx.is_deprecated(self.macro_))
|
||||
.add_import(import_to_add)
|
||||
.set_detail(self.detail());
|
||||
|
||||
let needs_bang = self.needs_bang();
|
||||
builder = match self.ctx.snippet_cap() {
|
||||
Some(cap) if needs_bang => {
|
||||
let snippet = self.snippet();
|
||||
let lookup = self.lookup();
|
||||
builder.insert_snippet(cap, snippet).lookup_by(lookup)
|
||||
}
|
||||
None if needs_bang => builder.insert_text(self.banged_name()),
|
||||
_ => {
|
||||
mark::hit!(dont_insert_macro_call_parens_unncessary);
|
||||
builder.insert_text(&self.name)
|
||||
}
|
||||
};
|
||||
|
||||
Some(builder.build())
|
||||
}
|
||||
|
||||
fn needs_bang(&self) -> bool {
|
||||
self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
if self.needs_bang() && self.ctx.snippet_cap().is_some() {
|
||||
format!("{}!{}…{}", self.name, self.bra, self.ket)
|
||||
} else {
|
||||
self.banged_name()
|
||||
}
|
||||
}
|
||||
|
||||
fn snippet(&self) -> String {
|
||||
format!("{}!{}$0{}", self.name, self.bra, self.ket)
|
||||
}
|
||||
|
||||
fn lookup(&self) -> String {
|
||||
self.banged_name()
|
||||
}
|
||||
|
||||
fn banged_name(&self) -> String {
|
||||
format!("{}!", self.name)
|
||||
}
|
||||
|
||||
fn detail(&self) -> Option<String> {
|
||||
let ast_node = self.macro_.source(self.ctx.db())?.value;
|
||||
Some(macro_label(&ast_node))
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
|
||||
let mut votes = [0, 0, 0];
|
||||
for (idx, s) in docs.match_indices(¯o_name) {
|
||||
let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
|
||||
// Ensure to match the full word
|
||||
if after.starts_with('!')
|
||||
&& !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
|
||||
{
|
||||
// It may have spaces before the braces like `foo! {}`
|
||||
match after[1..].chars().find(|&c| !c.is_whitespace()) {
|
||||
Some('{') => votes[0] += 1,
|
||||
Some('[') => votes[1] += 1,
|
||||
Some('(') => votes[2] += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a space before `{}`.
|
||||
// We prefer the last one when some votes equal.
|
||||
let (_vote, (bra, ket)) = votes
|
||||
.iter()
|
||||
.zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
|
||||
.max_by_key(|&(&vote, _)| vote)
|
||||
.unwrap();
|
||||
(*bra, *ket)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::test_utils::check_edit;
|
||||
|
||||
#[test]
|
||||
fn dont_insert_macro_call_parens_unncessary() {
|
||||
mark::check!(dont_insert_macro_call_parens_unncessary);
|
||||
check_edit(
|
||||
"frobnicate!",
|
||||
r#"
|
||||
//- /main.rs crate:main deps:foo
|
||||
use foo::$0;
|
||||
//- /foo/lib.rs crate:foo
|
||||
#[macro_export]
|
||||
macro_rules! frobnicate { () => () }
|
||||
"#,
|
||||
r#"
|
||||
use foo::frobnicate;
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"frobnicate!",
|
||||
r#"
|
||||
macro_rules! frobnicate { () => () }
|
||||
fn main() { frob$0!(); }
|
||||
"#,
|
||||
r#"
|
||||
macro_rules! frobnicate { () => () }
|
||||
fn main() { frobnicate!(); }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guesses_macro_braces() {
|
||||
check_edit(
|
||||
"vec!",
|
||||
r#"
|
||||
/// Creates a [`Vec`] containing the arguments.
|
||||
///
|
||||
/// ```
|
||||
/// let v = vec![1, 2, 3];
|
||||
/// assert_eq!(v[0], 1);
|
||||
/// assert_eq!(v[1], 2);
|
||||
/// assert_eq!(v[2], 3);
|
||||
/// ```
|
||||
macro_rules! vec { () => {} }
|
||||
|
||||
fn fn main() { v$0 }
|
||||
"#,
|
||||
r#"
|
||||
/// Creates a [`Vec`] containing the arguments.
|
||||
///
|
||||
/// ```
|
||||
/// let v = vec![1, 2, 3];
|
||||
/// assert_eq!(v[0], 1);
|
||||
/// assert_eq!(v[1], 2);
|
||||
/// assert_eq!(v[2], 3);
|
||||
/// ```
|
||||
macro_rules! vec { () => {} }
|
||||
|
||||
fn fn main() { vec![$0] }
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"foo!",
|
||||
r#"
|
||||
/// Foo
|
||||
///
|
||||
/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
|
||||
/// call as `let _=foo! { hello world };`
|
||||
macro_rules! foo { () => {} }
|
||||
fn main() { $0 }
|
||||
"#,
|
||||
r#"
|
||||
/// Foo
|
||||
///
|
||||
/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
|
||||
/// call as `let _=foo! { hello world };`
|
||||
macro_rules! foo { () => {} }
|
||||
fn main() { foo! {$0} }
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
150
crates/ide_completion/src/render/pattern.rs
Normal file
150
crates/ide_completion/src/render/pattern.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
//! Renderer for patterns.
|
||||
|
||||
use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
|
||||
use ide_db::helpers::SnippetCap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
|
||||
|
||||
fn visible_fields(
|
||||
ctx: &RenderContext<'_>,
|
||||
fields: &[hir::Field],
|
||||
item: impl HasAttrs,
|
||||
) -> Option<(Vec<hir::Field>, bool)> {
|
||||
let module = ctx.completion.scope.module()?;
|
||||
let n_fields = fields.len();
|
||||
let fields = fields
|
||||
.into_iter()
|
||||
.filter(|field| field.is_visible_from(ctx.db(), module))
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let fields_omitted =
|
||||
n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
|
||||
Some((fields, fields_omitted))
|
||||
}
|
||||
|
||||
pub(crate) fn render_struct_pat(
|
||||
ctx: RenderContext<'_>,
|
||||
strukt: hir::Struct,
|
||||
local_name: Option<Name>,
|
||||
) -> Option<CompletionItem> {
|
||||
let _p = profile::span("render_struct_pat");
|
||||
|
||||
let fields = strukt.fields(ctx.db());
|
||||
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
|
||||
|
||||
if visible_fields.is_empty() {
|
||||
// Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
|
||||
let pat = render_pat(&ctx, &name, strukt.kind(ctx.db()), &visible_fields, fields_omitted)?;
|
||||
|
||||
Some(build_completion(ctx, name, pat, strukt))
|
||||
}
|
||||
|
||||
pub(crate) fn render_variant_pat(
|
||||
ctx: RenderContext<'_>,
|
||||
variant: hir::Variant,
|
||||
local_name: Option<Name>,
|
||||
path: Option<hir::ModPath>,
|
||||
) -> Option<CompletionItem> {
|
||||
let _p = profile::span("render_variant_pat");
|
||||
|
||||
let fields = variant.fields(ctx.db());
|
||||
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, variant)?;
|
||||
|
||||
let name = match &path {
|
||||
Some(path) => path.to_string(),
|
||||
None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(),
|
||||
};
|
||||
let pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?;
|
||||
|
||||
Some(build_completion(ctx, name, pat, variant))
|
||||
}
|
||||
|
||||
fn build_completion(
|
||||
ctx: RenderContext<'_>,
|
||||
name: String,
|
||||
pat: String,
|
||||
item: impl HasAttrs + Copy,
|
||||
) -> CompletionItem {
|
||||
let completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
|
||||
.kind(CompletionItemKind::Binding)
|
||||
.set_documentation(ctx.docs(item))
|
||||
.set_deprecated(ctx.is_deprecated(item))
|
||||
.detail(&pat);
|
||||
let completion = if let Some(snippet_cap) = ctx.snippet_cap() {
|
||||
completion.insert_snippet(snippet_cap, pat)
|
||||
} else {
|
||||
completion.insert_text(pat)
|
||||
};
|
||||
completion.build()
|
||||
}
|
||||
|
||||
fn render_pat(
|
||||
ctx: &RenderContext<'_>,
|
||||
name: &str,
|
||||
kind: StructKind,
|
||||
fields: &[hir::Field],
|
||||
fields_omitted: bool,
|
||||
) -> Option<String> {
|
||||
let mut pat = match kind {
|
||||
StructKind::Tuple if ctx.snippet_cap().is_some() => {
|
||||
render_tuple_as_pat(&fields, &name, fields_omitted)
|
||||
}
|
||||
StructKind::Record => {
|
||||
render_record_as_pat(ctx.db(), ctx.snippet_cap(), &fields, &name, fields_omitted)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if ctx.completion.is_param {
|
||||
pat.push(':');
|
||||
pat.push(' ');
|
||||
pat.push_str(&name);
|
||||
}
|
||||
if ctx.snippet_cap().is_some() {
|
||||
pat.push_str("$0");
|
||||
}
|
||||
Some(pat)
|
||||
}
|
||||
|
||||
fn render_record_as_pat(
|
||||
db: &dyn HirDatabase,
|
||||
snippet_cap: Option<SnippetCap>,
|
||||
fields: &[hir::Field],
|
||||
name: &str,
|
||||
fields_omitted: bool,
|
||||
) -> String {
|
||||
let fields = fields.iter();
|
||||
if snippet_cap.is_some() {
|
||||
format!(
|
||||
"{name} {{ {}{} }}",
|
||||
fields
|
||||
.enumerate()
|
||||
.map(|(idx, field)| format!("{}${}", field.name(db), idx + 1))
|
||||
.format(", "),
|
||||
if fields_omitted { ", .." } else { "" },
|
||||
name = name
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{name} {{ {}{} }}",
|
||||
fields.map(|field| field.name(db)).format(", "),
|
||||
if fields_omitted { ", .." } else { "" },
|
||||
name = name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String {
|
||||
format!(
|
||||
"{name}({}{})",
|
||||
fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
|
||||
if fields_omitted { ", .." } else { "" },
|
||||
name = name
|
||||
)
|
||||
}
|
59
crates/ide_completion/src/render/type_alias.rs
Normal file
59
crates/ide_completion/src/render/type_alias.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
//! Renderer for type aliases.
|
||||
|
||||
use hir::HasSource;
|
||||
use ide_db::SymbolKind;
|
||||
use syntax::{
|
||||
ast::{NameOwner, TypeAlias},
|
||||
display::type_label,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
item::{CompletionItem, CompletionKind},
|
||||
render::RenderContext,
|
||||
};
|
||||
|
||||
pub(crate) fn render_type_alias<'a>(
|
||||
ctx: RenderContext<'a>,
|
||||
type_alias: hir::TypeAlias,
|
||||
) -> Option<CompletionItem> {
|
||||
TypeAliasRender::new(ctx, type_alias)?.render()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TypeAliasRender<'a> {
|
||||
ctx: RenderContext<'a>,
|
||||
type_alias: hir::TypeAlias,
|
||||
ast_node: TypeAlias,
|
||||
}
|
||||
|
||||
impl<'a> TypeAliasRender<'a> {
|
||||
fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> Option<TypeAliasRender<'a>> {
|
||||
let ast_node = type_alias.source(ctx.db())?.value;
|
||||
Some(TypeAliasRender { ctx, type_alias, ast_node })
|
||||
}
|
||||
|
||||
fn render(self) -> Option<CompletionItem> {
|
||||
let name = self.name()?;
|
||||
let detail = self.detail();
|
||||
|
||||
let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name)
|
||||
.kind(SymbolKind::TypeAlias)
|
||||
.set_documentation(self.ctx.docs(self.type_alias))
|
||||
.set_deprecated(
|
||||
self.ctx.is_deprecated(self.type_alias)
|
||||
|| self.ctx.is_deprecated_assoc_item(self.type_alias),
|
||||
)
|
||||
.detail(detail)
|
||||
.build();
|
||||
|
||||
Some(item)
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<String> {
|
||||
self.ast_node.name().map(|name| name.text().to_string())
|
||||
}
|
||||
|
||||
fn detail(&self) -> String {
|
||||
type_label(&self.ast_node)
|
||||
}
|
||||
}
|
153
crates/ide_completion/src/test_utils.rs
Normal file
153
crates/ide_completion/src/test_utils.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
//! Runs completion for testing purposes.
|
||||
|
||||
use hir::{PrefixKind, Semantics};
|
||||
use ide_db::{
|
||||
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
|
||||
helpers::{
|
||||
insert_use::{InsertUseConfig, MergeBehavior},
|
||||
SnippetCap,
|
||||
},
|
||||
RootDatabase,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use stdx::{format_to, trim_indent};
|
||||
use syntax::{AstNode, NodeOrToken, SyntaxElement};
|
||||
use test_utils::{assert_eq_text, RangeOrOffset};
|
||||
|
||||
use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
|
||||
|
||||
pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
||||
enable_postfix_completions: true,
|
||||
enable_imports_on_the_fly: true,
|
||||
add_call_parenthesis: true,
|
||||
add_call_argument_snippets: true,
|
||||
snippet_cap: SnippetCap::new(true),
|
||||
insert_use: InsertUseConfig {
|
||||
merge: Some(MergeBehavior::Full),
|
||||
prefix_kind: PrefixKind::Plain,
|
||||
},
|
||||
};
|
||||
|
||||
/// Creates analysis from a multi-file fixture, returns positions marked with $0.
|
||||
pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
||||
let change_fixture = ChangeFixture::parse(ra_fixture);
|
||||
let mut database = RootDatabase::default();
|
||||
database.apply_change(change_fixture.change);
|
||||
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
(database, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
|
||||
do_completion_with_config(TEST_CONFIG, code, kind)
|
||||
}
|
||||
|
||||
pub(crate) fn do_completion_with_config(
|
||||
config: CompletionConfig,
|
||||
code: &str,
|
||||
kind: CompletionKind,
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut kind_completions: Vec<CompletionItem> =
|
||||
get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
|
||||
kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
|
||||
kind_completions
|
||||
}
|
||||
|
||||
pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
|
||||
completion_list_with_config(TEST_CONFIG, code, kind)
|
||||
}
|
||||
|
||||
pub(crate) fn completion_list_with_config(
|
||||
config: CompletionConfig,
|
||||
code: &str,
|
||||
kind: CompletionKind,
|
||||
) -> String {
|
||||
let kind_completions: Vec<CompletionItem> =
|
||||
get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
|
||||
let label_width = kind_completions
|
||||
.iter()
|
||||
.map(|it| monospace_width(it.label()))
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
.min(16);
|
||||
kind_completions
|
||||
.into_iter()
|
||||
.map(|it| {
|
||||
let tag = it.kind().unwrap().tag();
|
||||
let var_name = format!("{} {}", tag, it.label());
|
||||
let mut buf = var_name;
|
||||
if let Some(detail) = it.detail() {
|
||||
let width = label_width.saturating_sub(monospace_width(it.label()));
|
||||
format_to!(buf, "{:width$} {}", "", detail, width = width);
|
||||
}
|
||||
if it.deprecated() {
|
||||
format_to!(buf, " DEPRECATED");
|
||||
}
|
||||
format_to!(buf, "\n");
|
||||
buf
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn monospace_width(s: &str) -> usize {
|
||||
s.chars().count()
|
||||
}
|
||||
|
||||
pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
|
||||
}
|
||||
|
||||
pub(crate) fn check_edit_with_config(
|
||||
config: CompletionConfig,
|
||||
what: &str,
|
||||
ra_fixture_before: &str,
|
||||
ra_fixture_after: &str,
|
||||
) {
|
||||
let ra_fixture_after = trim_indent(ra_fixture_after);
|
||||
let (db, position) = position(ra_fixture_before);
|
||||
let completions: Vec<CompletionItem> =
|
||||
crate::completions(&db, &config, position).unwrap().into();
|
||||
let (completion,) = completions
|
||||
.iter()
|
||||
.filter(|it| it.lookup() == what)
|
||||
.collect_tuple()
|
||||
.unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
|
||||
let mut actual = db.file_text(position.file_id).to_string();
|
||||
|
||||
let mut combined_edit = completion.text_edit().to_owned();
|
||||
if let Some(import_text_edit) =
|
||||
completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge))
|
||||
{
|
||||
combined_edit.union(import_text_edit).expect(
|
||||
"Failed to apply completion resolve changes: change ranges overlap, but should not",
|
||||
)
|
||||
}
|
||||
|
||||
combined_edit.apply(&mut actual);
|
||||
assert_eq_text!(&ra_fixture_after, &actual)
|
||||
}
|
||||
|
||||
pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
|
||||
let (db, pos) = position(code);
|
||||
|
||||
let sema = Semantics::new(&db);
|
||||
let original_file = sema.parse(pos.file_id);
|
||||
let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
|
||||
assert!(check(NodeOrToken::Token(token)));
|
||||
}
|
||||
|
||||
pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
|
||||
let (db, pos) = position(code);
|
||||
let sema = Semantics::new(&db);
|
||||
let original_file = sema.parse(pos.file_id);
|
||||
let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
|
||||
assert!(!check(NodeOrToken::Token(token)));
|
||||
}
|
||||
|
||||
pub(crate) fn get_all_items(config: CompletionConfig, code: &str) -> Vec<CompletionItem> {
|
||||
let (db, position) = position(code);
|
||||
crate::completions(&db, &config, position).unwrap().into()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue