Improve user snippet import performance

This commit is contained in:
Lukas Wirth 2021-10-12 11:47:22 +02:00
parent 1cca1fa5bf
commit 66bfa6fc88

View file

@ -56,7 +56,7 @@ use std::ops::Deref;
// It does not act as a tabstop. // It does not act as a tabstop.
use ide_db::helpers::{import_assets::LocatedImport, insert_use::ImportScope}; use ide_db::helpers::{import_assets::LocatedImport, insert_use::ImportScope};
use itertools::Itertools; use itertools::Itertools;
use syntax::ast; use syntax::{ast, AstNode, GreenNode, SyntaxNode};
use crate::{context::CompletionContext, ImportEdit}; use crate::{context::CompletionContext, ImportEdit};
@ -75,9 +75,12 @@ pub struct Snippet {
pub postfix_triggers: Box<[Box<str>]>, pub postfix_triggers: Box<[Box<str>]>,
pub prefix_triggers: Box<[Box<str>]>, pub prefix_triggers: Box<[Box<str>]>,
pub scope: SnippetScope, pub scope: SnippetScope,
snippet: String,
pub description: Option<Box<str>>, pub description: Option<Box<str>>,
pub requires: Box<[Box<str>]>, snippet: String,
// These are `ast::Path`'s but due to SyntaxNodes not being Send we store these
// and reconstruct them on demand instead. This is cheaper than reparsing them
// from strings
requires: Box<[GreenNode]>,
} }
impl Snippet { impl Snippet {
@ -92,7 +95,7 @@ impl Snippet {
if prefix_triggers.is_empty() && postfix_triggers.is_empty() { if prefix_triggers.is_empty() && postfix_triggers.is_empty() {
return None; return None;
} }
let (snippet, description) = validate_snippet(snippet, description, requires)?; let (requires, snippet, description) = validate_snippet(snippet, description, requires)?;
Some(Snippet { Some(Snippet {
// Box::into doesn't work as that has a Copy bound 😒 // Box::into doesn't work as that has a Copy bound 😒
postfix_triggers: postfix_triggers.iter().map(Deref::deref).map(Into::into).collect(), postfix_triggers: postfix_triggers.iter().map(Deref::deref).map(Into::into).collect(),
@ -100,7 +103,7 @@ impl Snippet {
scope, scope,
snippet, snippet,
description, description,
requires: requires.iter().map(Deref::deref).map(Into::into).collect(), requires,
}) })
} }
@ -125,10 +128,10 @@ impl Snippet {
fn import_edits( fn import_edits(
ctx: &CompletionContext, ctx: &CompletionContext,
import_scope: &ImportScope, import_scope: &ImportScope,
requires: &[Box<str>], requires: &[GreenNode],
) -> Option<Vec<ImportEdit>> { ) -> Option<Vec<ImportEdit>> {
let resolve = |import| { let resolve = |import: &GreenNode| {
let path = ast::Path::parse(import).ok()?; let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?;
let item = match ctx.scope.speculative_resolve(&path)? { let item = match ctx.scope.speculative_resolve(&path)? {
hir::PathResolution::Macro(mac) => mac.into(), hir::PathResolution::Macro(mac) => mac.into(),
hir::PathResolution::Def(def) => def.into(), hir::PathResolution::Def(def) => def.into(),
@ -158,19 +161,21 @@ fn validate_snippet(
snippet: &[String], snippet: &[String],
description: &str, description: &str,
requires: &[String], requires: &[String],
) -> Option<(String, Option<Box<str>>)> { ) -> Option<(Box<[GreenNode]>, String, Option<Box<str>>)> {
// validate that these are indeed simple paths let mut imports = Vec::with_capacity(requires.len());
// we can't save the paths unfortunately due to them not being Send+Sync for path in requires.iter() {
if requires.iter().any(|path| match ast::Path::parse(path) { let path = ast::Path::parse(path).ok()?;
Ok(path) => path.segments().any(|seg| { let valid_use_path = path.segments().all(|seg| {
!matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_))) matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
|| seg.generic_arg_list().is_some() || seg.generic_arg_list().is_none()
}), });
Err(_) => true, if !valid_use_path {
}) {
return None; return None;
} }
let green = path.syntax().green().into_owned();
imports.push(green);
}
let snippet = snippet.iter().join("\n"); let snippet = snippet.iter().join("\n");
let description = if description.is_empty() { None } else { Some(description.into()) }; let description = if description.is_empty() { None } else { Some(description.into()) };
Some((snippet, description)) Some((imports.into_boxed_slice(), snippet, description))
} }