From 5ccaff3c975302362a40e8b4df0594061bdd4345 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 11 Oct 2021 21:49:39 +0200 Subject: [PATCH] Simplify --- .../ide_completion/src/completions/postfix.rs | 4 +- crates/ide_completion/src/config.rs | 13 +++--- crates/ide_completion/src/context.rs | 10 +++-- crates/ide_completion/src/item.rs | 6 ++- crates/ide_completion/src/lib.rs | 1 + crates/ide_completion/src/patterns.rs | 33 +++++++++------ crates/ide_completion/src/snippet.rs | 40 +++++++++---------- 7 files changed, 63 insertions(+), 44 deletions(-) diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs index b74030c1735..b35d97e4252 100644 --- a/crates/ide_completion/src/completions/postfix.rs +++ b/crates/ide_completion/src/completions/postfix.rs @@ -19,7 +19,7 @@ use crate::{ context::CompletionContext, item::{Builder, CompletionKind}, patterns::ImmediateLocation, - CompletionItem, CompletionItemKind, CompletionRelevance, Completions, + CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope, }; pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { @@ -231,7 +231,7 @@ fn add_custom_postfix_completions( ) -> Option<()> { let import_scope = ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?; - ctx.config.postfix_snippets().filter(|(_, snip)| snip.is_expr()).for_each( + ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each( |(trigger, snippet)| { let imports = match snippet.imports(ctx, &import_scope) { Some(imports) => imports, diff --git a/crates/ide_completion/src/config.rs b/crates/ide_completion/src/config.rs index c659b4455a9..5e5c7efdfb9 100644 --- a/crates/ide_completion/src/config.rs +++ b/crates/ide_completion/src/config.rs @@ -22,13 +22,14 @@ pub struct CompletionConfig { impl CompletionConfig { pub fn postfix_snippets(&self) -> impl Iterator { - self.snippets.iter().flat_map(|snip| { - snip.postfix_triggers.iter().map(move |trigger| (trigger.as_str(), snip)) - }) + self.snippets + .iter() + .flat_map(|snip| snip.postfix_triggers.iter().map(move |trigger| (&**trigger, snip))) } + pub fn prefix_snippets(&self) -> impl Iterator { - self.snippets.iter().flat_map(|snip| { - snip.prefix_triggers.iter().map(move |trigger| (trigger.as_str(), snip)) - }) + self.snippets + .iter() + .flat_map(|snip| snip.prefix_triggers.iter().map(move |trigger| (&**trigger, snip))) } } diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index a34e529ea58..11ae139bb73 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -85,6 +85,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) original_token: SyntaxToken, /// The token before the cursor, in the macro-expanded file. pub(super) token: SyntaxToken, + /// The crate of the current file. pub(super) krate: Option, pub(super) expected_name: Option, pub(super) expected_type: Option, @@ -135,11 +136,11 @@ impl<'a> CompletionContext<'a> { 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, position.offset); + let krate = scope.krate(); let mut locals = vec![]; scope.process_all_names(&mut |name, scope| { if let ScopeDef::Local(local) = scope { @@ -182,6 +183,8 @@ impl<'a> CompletionContext<'a> { Some(ctx) } + /// Do the attribute expansion at the current cursor position for both original file and fake file + /// as long as possible. As soon as one of the two expansions fail we stop to stay in sync. fn expand_and_fill( &mut self, mut original_file: SyntaxNode, @@ -428,6 +431,7 @@ impl<'a> CompletionContext<'a> { false } + /// Check if an item is `#[doc(hidden)]`. pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool { let attrs = item.attrs(self.db); let krate = item.krate(self.db); @@ -474,11 +478,11 @@ impl<'a> CompletionContext<'a> { } fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool { - let module = match self.scope.module() { + let krate = match self.krate { Some(it) => it, None => return true, }; - if module.krate() != defining_crate && attrs.has_doc_hidden() { + if krate != defining_crate && attrs.has_doc_hidden() { // `doc(hidden)` items are only completed within the defining crate. return true; } diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 4c75bd69000..a9eec472caf 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs @@ -218,6 +218,7 @@ impl CompletionRelevance { } } +/// The type of the completion item. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CompletionItemKind { SymbolKind(SymbolKind), @@ -269,6 +270,8 @@ impl CompletionItemKind { } } +// FIXME remove this? +/// Like [`CompletionItemKind`] but solely used for filtering test results. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum CompletionKind { /// Parser-based keyword completion. @@ -477,9 +480,10 @@ impl Builder { } pub(crate) fn insert_snippet( &mut self, - _cap: SnippetCap, + cap: SnippetCap, snippet: impl Into, ) -> &mut Builder { + let _ = cap; self.is_snippet = true; self.insert_text(snippet) } diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 251ddfa2fc0..6680e66c606 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -173,6 +173,7 @@ pub fn completions( } /// Resolves additional completion data at the position given. +/// This is used for import insertion done via completions like flyimport and custom user snippets. pub fn resolve_completion_edits( db: &RootDatabase, config: &CompletionConfig, diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index d5d81bf2f16..4c2e704a7fb 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -28,6 +28,9 @@ pub(crate) enum ImmediatePrevSibling { } /// Direct parent "thing" of what we are currently completing. +/// +/// This may contain nodes of the fake file as well as the original, comments on the variants specify +/// from which file the nodes are. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediateLocation { Use, @@ -42,32 +45,35 @@ pub(crate) enum ImmediateLocation { StmtList, ItemList, TypeBound, - // Fake file ast node + /// Fake file ast node Attribute(ast::Attr), - // Fake file ast node + /// Fake file ast node ModDeclaration(ast::Module), Visibility(ast::Visibility), - // Original file ast node + /// Original file ast node MethodCall { receiver: Option, has_parens: bool, }, - // Original file ast node + /// Original file ast node FieldAccess { receiver: Option, receiver_is_ambiguous_float_literal: bool, }, - // Original file ast node // Only set from a type arg + /// Original file ast node GenericArgList(ast::GenericArgList), - // Original file ast node /// The record expr of the field name we are completing + /// + /// Original file ast node RecordExpr(ast::RecordExpr), - // Original file ast node /// The record expr of the functional update syntax we are completing + /// + /// Original file ast node RecordExprUpdate(ast::RecordExpr), - // Original file ast node /// The record pat of the field name we are completing + /// + /// Original file ast node RecordPat(ast::RecordPat), } @@ -268,17 +274,20 @@ pub(crate) fn determine_location( Some(res) } +/// Maximize a nameref to its enclosing path if its the last segment of said path. +/// That is, when completing a [`NameRef`] we actually handle it as the path it is part of when determining +/// its location. fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { - // Maximize a nameref to its enclosing path if its the last segment of said path if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { let p = segment.parent_path(); if p.parent_path().is_none() { - if let Some(it) = p + // Get rid of PathExpr, PathType, etc... + let path = p .syntax() .ancestors() .take_while(|it| it.text_range() == p.syntax().text_range()) - .last() - { + .last(); + if let Some(it) = path { return it; } } diff --git a/crates/ide_completion/src/snippet.rs b/crates/ide_completion/src/snippet.rs index d527f3aef6f..53ed7de2d6f 100644 --- a/crates/ide_completion/src/snippet.rs +++ b/crates/ide_completion/src/snippet.rs @@ -1,6 +1,8 @@ //! User (postfix)-snippet definitions. //! -//! Actual logic is implemented in [`crate::completions::postfix`] and [`crate::completions::snippet`]. +//! Actual logic is implemented in [`crate::completions::postfix`] and [`crate::completions::snippet`] respectively. + +use std::ops::Deref; // Feature: User Snippet Completions // @@ -58,6 +60,8 @@ use syntax::ast; use crate::{context::CompletionContext, ImportEdit}; +/// A snippet scope describing where a snippet may apply to. +/// These may differ slightly in meaning depending on the snippet trigger. #[derive(Clone, Debug, PartialEq, Eq)] pub enum SnippetScope { Item, @@ -65,14 +69,15 @@ pub enum SnippetScope { Type, } +/// A user supplied snippet. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Snippet { - pub postfix_triggers: Box<[String]>, - pub prefix_triggers: Box<[String]>, + pub postfix_triggers: Box<[Box]>, + pub prefix_triggers: Box<[Box]>, pub scope: SnippetScope, snippet: String, - pub description: Option, - pub requires: Box<[String]>, + pub description: Option>, + pub requires: Box<[Box]>, } impl Snippet { @@ -84,19 +89,22 @@ impl Snippet { requires: &[String], scope: SnippetScope, ) -> Option { + if prefix_triggers.is_empty() && postfix_triggers.is_empty() { + return None; + } let (snippet, description) = validate_snippet(snippet, description, requires)?; Some(Snippet { // Box::into doesn't work as that has a Copy bound 😒 - postfix_triggers: postfix_triggers.iter().cloned().collect(), - prefix_triggers: prefix_triggers.iter().cloned().collect(), + postfix_triggers: postfix_triggers.iter().map(Deref::deref).map(Into::into).collect(), + prefix_triggers: prefix_triggers.iter().map(Deref::deref).map(Into::into).collect(), scope, snippet, description, - requires: requires.iter().cloned().collect(), + requires: requires.iter().map(Deref::deref).map(Into::into).collect(), }) } - /// Returns None if the required items do not resolve. + /// Returns [`None`] if the required items do not resolve. pub(crate) fn imports( &self, ctx: &CompletionContext, @@ -112,20 +120,12 @@ impl Snippet { pub fn postfix_snippet(&self, receiver: &str) -> String { self.snippet.replace("${receiver}", receiver) } - - pub fn is_item(&self) -> bool { - self.scope == SnippetScope::Item - } - - pub fn is_expr(&self) -> bool { - self.scope == SnippetScope::Expr - } } fn import_edits( ctx: &CompletionContext, import_scope: &ImportScope, - requires: &[String], + requires: &[Box], ) -> Option> { let resolve = |import| { let path = ast::Path::parse(import).ok()?; @@ -158,7 +158,7 @@ fn validate_snippet( snippet: &[String], description: &str, requires: &[String], -) -> Option<(String, Option)> { +) -> Option<(String, Option>)> { // validate that these are indeed simple paths // we can't save the paths unfortunately due to them not being Send+Sync if requires.iter().any(|path| match ast::Path::parse(path) { @@ -171,6 +171,6 @@ fn validate_snippet( return None; } let snippet = snippet.iter().join("\n"); - let description = if description.is_empty() { None } else { Some(description.to_owned()) }; + let description = if description.is_empty() { None } else { Some(description.into()) }; Some((snippet, description)) }