Resolve macros in snippet require items

This commit is contained in:
Lukas Wirth 2021-10-04 22:45:47 +02:00
parent ca1fdd75f1
commit 2b17da60db
3 changed files with 49 additions and 54 deletions

View file

@ -232,8 +232,8 @@ fn add_custom_postfix_completions(
ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?; ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
ctx.config.postfix_snippets.iter().for_each(|snippet| { ctx.config.postfix_snippets.iter().for_each(|snippet| {
let imports = match snippet.imports(ctx, &import_scope) { let imports = match snippet.imports(ctx, &import_scope) {
Ok(imports) => imports, Some(imports) => imports,
Err(_) => return, None => return,
}; };
let mut builder = postfix_snippet( let mut builder = postfix_snippet(
&snippet.label, &snippet.label,

View file

@ -105,8 +105,8 @@ fn add_custom_completions(
ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?; ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
ctx.config.snippets.iter().filter(|snip| snip.scope == scope).for_each(|snip| { ctx.config.snippets.iter().filter(|snip| snip.scope == scope).for_each(|snip| {
let imports = match snip.imports(ctx, &import_scope) { let imports = match snip.imports(ctx, &import_scope) {
Ok(imports) => imports, Some(imports) => imports,
Err(_) => return, None => return,
}; };
let mut builder = snippet(ctx, cap, &snip.label, &snip.snippet); let mut builder = snippet(ctx, cap, &snip.label, &snip.snippet);
for import in imports.into_iter() { for import in imports.into_iter() {

View file

@ -37,7 +37,6 @@ pub struct Snippet {
pub description: Option<String>, pub description: Option<String>,
pub requires: Box<[String]>, pub requires: Box<[String]>,
} }
impl Snippet { impl Snippet {
pub fn new( pub fn new(
label: String, label: String,
@ -46,19 +45,7 @@ impl Snippet {
requires: &[String], requires: &[String],
scope: SnippetScope, scope: SnippetScope,
) -> Option<Self> { ) -> Option<Self> {
// validate that these are indeed simple paths let (snippet, description) = validate_snippet(snippet, description, requires)?;
if requires.iter().any(|path| match ast::Path::parse(path) {
Ok(path) => path.segments().any(|seg| {
!matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
|| seg.generic_arg_list().is_some()
}),
Err(_) => true,
}) {
return None;
}
let snippet = snippet.iter().join("\n");
let description = description.iter().join("\n");
let description = if description.is_empty() { None } else { Some(description) };
Some(Snippet { Some(Snippet {
scope, scope,
label, label,
@ -68,12 +55,12 @@ impl Snippet {
}) })
} }
// FIXME: This shouldn't be fallible /// Returns None if the required items do not resolve.
pub(crate) fn imports( pub(crate) fn imports(
&self, &self,
ctx: &CompletionContext, ctx: &CompletionContext,
import_scope: &ImportScope, import_scope: &ImportScope,
) -> Result<Vec<ImportEdit>, ()> { ) -> Option<Vec<ImportEdit>> {
import_edits(ctx, import_scope, &self.requires) import_edits(ctx, import_scope, &self.requires)
} }
@ -94,19 +81,7 @@ impl PostfixSnippet {
requires: &[String], requires: &[String],
scope: PostfixSnippetScope, scope: PostfixSnippetScope,
) -> Option<Self> { ) -> Option<Self> {
// validate that these are indeed simple paths let (snippet, description) = validate_snippet(snippet, description, requires)?;
if requires.iter().any(|path| match ast::Path::parse(path) {
Ok(path) => path.segments().any(|seg| {
!matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
|| seg.generic_arg_list().is_some()
}),
Err(_) => true,
}) {
return None;
}
let snippet = snippet.iter().join("\n");
let description = description.iter().join("\n");
let description = if description.is_empty() { None } else { Some(description) };
Some(PostfixSnippet { Some(PostfixSnippet {
scope, scope,
label, label,
@ -116,12 +91,12 @@ impl PostfixSnippet {
}) })
} }
// FIXME: This shouldn't be fallible /// Returns None if the required items do not resolve.
pub(crate) fn imports( pub(crate) fn imports(
&self, &self,
ctx: &CompletionContext, ctx: &CompletionContext,
import_scope: &ImportScope, import_scope: &ImportScope,
) -> Result<Vec<ImportEdit>, ()> { ) -> Option<Vec<ImportEdit>> {
import_edits(ctx, import_scope, &self.requires) import_edits(ctx, import_scope, &self.requires)
} }
@ -142,13 +117,14 @@ fn import_edits(
ctx: &CompletionContext, ctx: &CompletionContext,
import_scope: &ImportScope, import_scope: &ImportScope,
requires: &[String], requires: &[String],
) -> Result<Vec<ImportEdit>, ()> { ) -> Option<Vec<ImportEdit>> {
let resolve = |import| { let resolve = |import| {
let path = ast::Path::parse(import).ok()?; let path = ast::Path::parse(import).ok()?;
match ctx.scope.speculative_resolve(&path)? { let item = match ctx.scope.speculative_resolve(&path)? {
hir::PathResolution::Macro(_) => None, hir::PathResolution::Macro(mac) => mac.into(),
hir::PathResolution::Def(def) => { hir::PathResolution::Def(def) => def.into(),
let item = def.into(); _ => return None,
};
let path = ctx.scope.module()?.find_use_path_prefixed( let path = ctx.scope.module()?.find_use_path_prefixed(
ctx.db, ctx.db,
item, item,
@ -158,16 +134,35 @@ fn import_edits(
import: LocatedImport::new(path.clone(), item, item, None), import: LocatedImport::new(path.clone(), item, item, None),
scope: import_scope.clone(), scope: import_scope.clone(),
})) }))
}
_ => None,
}
}; };
let mut res = Vec::with_capacity(requires.len()); let mut res = Vec::with_capacity(requires.len());
for import in requires { for import in requires {
match resolve(import) { match resolve(import) {
Some(first) => res.extend(first), Some(first) => res.extend(first),
None => return Err(()), None => return None,
} }
} }
Ok(res) Some(res)
}
fn validate_snippet(
snippet: &[String],
description: &[String],
requires: &[String],
) -> Option<(String, Option<String>)> {
// 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) {
Ok(path) => path.segments().any(|seg| {
!matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
|| seg.generic_arg_list().is_some()
}),
Err(_) => true,
}) {
return None;
}
let snippet = snippet.iter().join("\n");
let description = description.iter().join("\n");
let description = if description.is_empty() { None } else { Some(description) };
Some((snippet, description))
} }