Skip to content

Commit 66bfa6f

Browse files
committed
Improve user snippet import performance
1 parent 1cca1fa commit 66bfa6f

File tree

1 file changed

+25
-20
lines changed

1 file changed

+25
-20
lines changed

crates/ide_completion/src/snippet.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use std::ops::Deref;
5656
// It does not act as a tabstop.
5757
use ide_db::helpers::{import_assets::LocatedImport, insert_use::ImportScope};
5858
use itertools::Itertools;
59-
use syntax::ast;
59+
use syntax::{ast, AstNode, GreenNode, SyntaxNode};
6060

6161
use crate::{context::CompletionContext, ImportEdit};
6262

@@ -75,9 +75,12 @@ pub struct Snippet {
7575
pub postfix_triggers: Box<[Box<str>]>,
7676
pub prefix_triggers: Box<[Box<str>]>,
7777
pub scope: SnippetScope,
78-
snippet: String,
7978
pub description: Option<Box<str>>,
80-
pub requires: Box<[Box<str>]>,
79+
snippet: String,
80+
// These are `ast::Path`'s but due to SyntaxNodes not being Send we store these
81+
// and reconstruct them on demand instead. This is cheaper than reparsing them
82+
// from strings
83+
requires: Box<[GreenNode]>,
8184
}
8285

8386
impl Snippet {
@@ -92,15 +95,15 @@ impl Snippet {
9295
if prefix_triggers.is_empty() && postfix_triggers.is_empty() {
9396
return None;
9497
}
95-
let (snippet, description) = validate_snippet(snippet, description, requires)?;
98+
let (requires, snippet, description) = validate_snippet(snippet, description, requires)?;
9699
Some(Snippet {
97100
// Box::into doesn't work as that has a Copy bound 😒
98101
postfix_triggers: postfix_triggers.iter().map(Deref::deref).map(Into::into).collect(),
99102
prefix_triggers: prefix_triggers.iter().map(Deref::deref).map(Into::into).collect(),
100103
scope,
101104
snippet,
102105
description,
103-
requires: requires.iter().map(Deref::deref).map(Into::into).collect(),
106+
requires,
104107
})
105108
}
106109

@@ -125,10 +128,10 @@ impl Snippet {
125128
fn import_edits(
126129
ctx: &CompletionContext,
127130
import_scope: &ImportScope,
128-
requires: &[Box<str>],
131+
requires: &[GreenNode],
129132
) -> Option<Vec<ImportEdit>> {
130-
let resolve = |import| {
131-
let path = ast::Path::parse(import).ok()?;
133+
let resolve = |import: &GreenNode| {
134+
let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?;
132135
let item = match ctx.scope.speculative_resolve(&path)? {
133136
hir::PathResolution::Macro(mac) => mac.into(),
134137
hir::PathResolution::Def(def) => def.into(),
@@ -158,19 +161,21 @@ fn validate_snippet(
158161
snippet: &[String],
159162
description: &str,
160163
requires: &[String],
161-
) -> Option<(String, Option<Box<str>>)> {
162-
// validate that these are indeed simple paths
163-
// we can't save the paths unfortunately due to them not being Send+Sync
164-
if requires.iter().any(|path| match ast::Path::parse(path) {
165-
Ok(path) => path.segments().any(|seg| {
166-
!matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
167-
|| seg.generic_arg_list().is_some()
168-
}),
169-
Err(_) => true,
170-
}) {
171-
return None;
164+
) -> Option<(Box<[GreenNode]>, String, Option<Box<str>>)> {
165+
let mut imports = Vec::with_capacity(requires.len());
166+
for path in requires.iter() {
167+
let path = ast::Path::parse(path).ok()?;
168+
let valid_use_path = path.segments().all(|seg| {
169+
matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
170+
|| seg.generic_arg_list().is_none()
171+
});
172+
if !valid_use_path {
173+
return None;
174+
}
175+
let green = path.syntax().green().into_owned();
176+
imports.push(green);
172177
}
173178
let snippet = snippet.iter().join("\n");
174179
let description = if description.is_empty() { None } else { Some(description.into()) };
175-
Some((snippet, description))
180+
Some((imports.into_boxed_slice(), snippet, description))
176181
}

0 commit comments

Comments
 (0)