Skip to content

Commit f610e2c

Browse files
committed
Simplify completion import insertion
1 parent 28251e4 commit f610e2c

File tree

10 files changed

+58
-92
lines changed

10 files changed

+58
-92
lines changed

crates/ide/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub use ide_assists::{
102102
Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
103103
};
104104
pub use ide_completion::{
105-
CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, Snippet,
105+
CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, Snippet,
106106
SnippetScope,
107107
};
108108
pub use ide_db::{

crates/ide_completion/src/completions/flyimport.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use syntax::{AstNode, SyntaxNode, T};
1010
use crate::{
1111
context::{CompletionContext, PathKind},
1212
render::{render_resolution_with_import, RenderContext},
13-
ImportEdit,
1413
};
1514

1615
use super::Completions;
@@ -136,10 +135,10 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
136135

137136
let user_input_lowercased = potential_import_name.to_lowercase();
138137
let import_assets = import_assets(ctx, potential_import_name)?;
139-
let import_scope = ImportScope::find_insert_use_container(
140-
&position_for_import(ctx, Some(import_assets.import_candidate()))?,
141-
&ctx.sema,
142-
)?;
138+
let position = position_for_import(ctx, Some(import_assets.import_candidate()))?;
139+
if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
140+
return None;
141+
}
143142

144143
let path_kind = match ctx.path_kind() {
145144
Some(kind) => Some(kind),
@@ -199,12 +198,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
199198
&user_input_lowercased,
200199
)
201200
})
202-
.filter_map(|import| {
203-
render_resolution_with_import(
204-
RenderContext::new(ctx),
205-
ImportEdit { import, scope: import_scope.clone() },
206-
)
207-
}),
201+
.filter_map(|import| render_resolution_with_import(RenderContext::new(ctx), import)),
208202
);
209203
Some(())
210204
}

crates/ide_completion/src/completions/postfix.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,12 @@ fn add_custom_postfix_completions(
261261
postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
262262
receiver_text: &str,
263263
) -> Option<()> {
264-
let import_scope = ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
264+
if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
265+
return None;
266+
}
265267
ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
266268
|(trigger, snippet)| {
267-
let imports = match snippet.imports(ctx, &import_scope) {
269+
let imports = match snippet.imports(ctx) {
268270
Some(imports) => imports,
269271
None => return,
270272
};

crates/ide_completion/src/completions/snippet.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ fn add_custom_completions(
104104
cap: SnippetCap,
105105
scope: SnippetScope,
106106
) -> Option<()> {
107-
let import_scope = ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
107+
if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
108+
return None;
109+
}
108110
ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
109111
|(trigger, snip)| {
110-
let imports = match snip.imports(ctx, &import_scope) {
112+
let imports = match snip.imports(ctx) {
111113
Some(imports) => imports,
112114
None => return,
113115
};

crates/ide_completion/src/item.rs

+7-37
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,10 @@
33
use std::fmt;
44

55
use hir::{Documentation, Mutability};
6-
use ide_db::{
7-
helpers::mod_path_to_ast,
8-
imports::{
9-
import_assets::LocatedImport,
10-
insert_use::{self, ImportScope, InsertUseConfig},
11-
},
12-
SnippetCap, SymbolKind,
13-
};
6+
use ide_db::{imports::import_assets::LocatedImport, SnippetCap, SymbolKind};
147
use smallvec::SmallVec;
158
use stdx::{impl_from, never};
16-
use syntax::{algo, SmolStr, TextRange};
9+
use syntax::{SmolStr, TextRange};
1710
use text_edit::TextEdit;
1811

1912
/// `CompletionItem` describes a single completion variant in the editor pop-up.
@@ -73,7 +66,7 @@ pub struct CompletionItem {
7366
ref_match: Option<Mutability>,
7467

7568
/// The import data to add to completion's edits.
76-
import_to_add: SmallVec<[ImportEdit; 1]>,
69+
import_to_add: SmallVec<[LocatedImport; 1]>,
7770
}
7871

7972
// We use custom debug for CompletionItem to make snapshot tests more readable.
@@ -380,40 +373,17 @@ impl CompletionItem {
380373
self.ref_match.map(|mutability| (mutability, relevance))
381374
}
382375

383-
pub fn imports_to_add(&self) -> &[ImportEdit] {
376+
pub fn imports_to_add(&self) -> &[LocatedImport] {
384377
&self.import_to_add
385378
}
386379
}
387380

388-
/// An extra import to add after the completion is applied.
389-
#[derive(Debug, Clone)]
390-
pub struct ImportEdit {
391-
pub import: LocatedImport,
392-
pub scope: ImportScope,
393-
}
394-
395-
impl ImportEdit {
396-
/// Attempts to insert the import to the given scope, producing a text edit.
397-
/// May return no edit in edge cases, such as scope already containing the import.
398-
pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
399-
let _p = profile::span("ImportEdit::to_text_edit");
400-
401-
let new_ast = self.scope.clone_for_update();
402-
insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), &cfg);
403-
let mut import_insert = TextEdit::builder();
404-
algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
405-
.into_text_edit(&mut import_insert);
406-
407-
Some(import_insert.finish())
408-
}
409-
}
410-
411381
/// A helper to make `CompletionItem`s.
412382
#[must_use]
413383
#[derive(Clone)]
414384
pub(crate) struct Builder {
415385
source_range: TextRange,
416-
imports_to_add: SmallVec<[ImportEdit; 1]>,
386+
imports_to_add: SmallVec<[LocatedImport; 1]>,
417387
trait_name: Option<SmolStr>,
418388
label: SmolStr,
419389
insert_text: Option<String>,
@@ -439,7 +409,7 @@ impl Builder {
439409

440410
if let [import_edit] = &*self.imports_to_add {
441411
// snippets can have multiple imports, but normal completions only have up to one
442-
if let Some(original_path) = import_edit.import.original_path.as_ref() {
412+
if let Some(original_path) = import_edit.original_path.as_ref() {
443413
lookup = lookup.or_else(|| Some(label.clone()));
444414
label = SmolStr::from(format!("{} (use {})", label, original_path));
445415
}
@@ -533,7 +503,7 @@ impl Builder {
533503
self.trigger_call_info = Some(true);
534504
self
535505
}
536-
pub(crate) fn add_import(&mut self, import_to_add: ImportEdit) -> &mut Builder {
506+
pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
537507
self.imports_to_add.push(import_to_add);
538508
self
539509
}

crates/ide_completion/src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ pub use crate::{
3030
config::CompletionConfig,
3131
item::{
3232
CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
33-
ImportEdit,
3433
},
3534
snippet::{Snippet, SnippetScope},
3635
};
@@ -193,7 +192,6 @@ pub fn resolve_completion_edits(
193192
let new_ast = scope.clone_for_update();
194193
let mut import_insert = TextEdit::builder();
195194

196-
// FIXME: lift out and make some tests here, this is ImportEdit::to_text_edit but changed to work with multiple edits
197195
imports.into_iter().for_each(|(full_import_path, imported_name)| {
198196
let items_with_name = items_locator::items_with_name(
199197
&ctx.sema,

crates/ide_completion/src/render.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ pub(crate) mod union_literal;
1111
pub(crate) mod literal;
1212

1313
use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
14-
use ide_db::{helpers::item_name, RootDatabase, SnippetCap, SymbolKind};
14+
use ide_db::{
15+
helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
16+
};
1517
use syntax::{SmolStr, SyntaxKind, TextRange};
1618

1719
use crate::{
1820
context::{PathCompletionCtx, PathKind},
19-
item::{CompletionRelevanceTypeMatch, ImportEdit},
21+
item::CompletionRelevanceTypeMatch,
2022
render::{function::render_fn, literal::render_variant_lit, macro_::render_macro},
2123
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
2224
};
@@ -25,7 +27,7 @@ use crate::{
2527
pub(crate) struct RenderContext<'a> {
2628
completion: &'a CompletionContext<'a>,
2729
is_private_editable: bool,
28-
import_to_add: Option<ImportEdit>,
30+
import_to_add: Option<LocatedImport>,
2931
}
3032

3133
impl<'a> RenderContext<'a> {
@@ -38,7 +40,7 @@ impl<'a> RenderContext<'a> {
3840
self
3941
}
4042

41-
pub(crate) fn import_to_add(mut self, import_to_add: Option<ImportEdit>) -> Self {
43+
pub(crate) fn import_to_add(mut self, import_to_add: Option<LocatedImport>) -> Self {
4244
self.import_to_add = import_to_add;
4345
self
4446
}
@@ -156,22 +158,22 @@ pub(crate) fn render_resolution_simple(
156158

157159
pub(crate) fn render_resolution_with_import(
158160
ctx: RenderContext<'_>,
159-
import_edit: ImportEdit,
161+
import_edit: LocatedImport,
160162
) -> Option<CompletionItem> {
161-
let resolution = ScopeDef::from(import_edit.import.original_item);
163+
let resolution = ScopeDef::from(import_edit.original_item);
162164
let local_name = match resolution {
163165
ScopeDef::ModuleDef(hir::ModuleDef::Function(f)) => f.name(ctx.completion.db),
164166
ScopeDef::ModuleDef(hir::ModuleDef::Const(c)) => c.name(ctx.completion.db)?,
165167
ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db),
166-
_ => item_name(ctx.db(), import_edit.import.original_item)?,
168+
_ => item_name(ctx.db(), import_edit.original_item)?,
167169
};
168170
Some(render_resolution_(ctx, local_name, Some(import_edit), resolution))
169171
}
170172

171173
fn render_resolution_(
172174
ctx: RenderContext<'_>,
173175
local_name: hir::Name,
174-
import_to_add: Option<ImportEdit>,
176+
import_to_add: Option<LocatedImport>,
175177
resolution: ScopeDef,
176178
) -> CompletionItem {
177179
let _p = profile::span("render_resolution");
@@ -200,7 +202,7 @@ fn render_resolution_(
200202
fn render_resolution_simple_(
201203
ctx: RenderContext<'_>,
202204
local_name: hir::Name,
203-
import_to_add: Option<ImportEdit>,
205+
import_to_add: Option<LocatedImport>,
204206
resolution: ScopeDef,
205207
) -> CompletionItem {
206208
let _p = profile::span("render_resolution");

crates/ide_completion/src/snippet.rs

+6-17
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ use std::ops::Deref;
102102
// }
103103
// ----
104104

105-
use ide_db::imports::{import_assets::LocatedImport, insert_use::ImportScope};
105+
use ide_db::imports::import_assets::LocatedImport;
106106
use itertools::Itertools;
107107
use syntax::{ast, AstNode, GreenNode, SyntaxNode};
108108

109-
use crate::{context::CompletionContext, ImportEdit};
109+
use crate::context::CompletionContext;
110110

111111
/// A snippet scope describing where a snippet may apply to.
112112
/// These may differ slightly in meaning depending on the snippet trigger.
@@ -156,12 +156,8 @@ impl Snippet {
156156
}
157157

158158
/// Returns [`None`] if the required items do not resolve.
159-
pub(crate) fn imports(
160-
&self,
161-
ctx: &CompletionContext,
162-
import_scope: &ImportScope,
163-
) -> Option<Vec<ImportEdit>> {
164-
import_edits(ctx, import_scope, &self.requires)
159+
pub(crate) fn imports(&self, ctx: &CompletionContext) -> Option<Vec<LocatedImport>> {
160+
import_edits(ctx, &self.requires)
165161
}
166162

167163
pub fn snippet(&self) -> String {
@@ -173,11 +169,7 @@ impl Snippet {
173169
}
174170
}
175171

176-
fn import_edits(
177-
ctx: &CompletionContext,
178-
import_scope: &ImportScope,
179-
requires: &[GreenNode],
180-
) -> Option<Vec<ImportEdit>> {
172+
fn import_edits(ctx: &CompletionContext, requires: &[GreenNode]) -> Option<Vec<LocatedImport>> {
181173
let resolve = |import: &GreenNode| {
182174
let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?;
183175
let item = match ctx.scope.speculative_resolve(&path)? {
@@ -186,10 +178,7 @@ fn import_edits(
186178
};
187179
let path =
188180
ctx.module.find_use_path_prefixed(ctx.db, item, ctx.config.insert_use.prefix_kind)?;
189-
Some((path.len() > 1).then(|| ImportEdit {
190-
import: LocatedImport::new(path.clone(), item, item, None),
191-
scope: import_scope.clone(),
192-
}))
181+
Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item, None)))
193182
};
194183
let mut res = Vec::with_capacity(requires.len());
195184
for import in requires {

crates/ide_completion/src/tests.rs

+19-10
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use stdx::{format_to, trim_indent};
3535
use syntax::{AstNode, NodeOrToken, SyntaxElement};
3636
use test_utils::assert_eq_text;
3737

38-
use crate::{CompletionConfig, CompletionItem, CompletionItemKind};
38+
use crate::{resolve_completion_edits, CompletionConfig, CompletionItem, CompletionItemKind};
3939

4040
/// Lots of basic item definitions
4141
const BASE_ITEMS_FIXTURE: &str = r#"
@@ -178,15 +178,24 @@ pub(crate) fn check_edit_with_config(
178178
let mut actual = db.file_text(position.file_id).to_string();
179179

180180
let mut combined_edit = completion.text_edit().to_owned();
181-
completion
182-
.imports_to_add()
183-
.iter()
184-
.filter_map(|edit| edit.to_text_edit(config.insert_use))
185-
.for_each(|text_edit| {
186-
combined_edit.union(text_edit).expect(
187-
"Failed to apply completion resolve changes: change ranges overlap, but should not",
188-
)
189-
});
181+
182+
resolve_completion_edits(
183+
&db,
184+
&config,
185+
position,
186+
completion.imports_to_add().iter().filter_map(|import_edit| {
187+
let import_path = &import_edit.import_path;
188+
let import_name = import_path.segments().last()?;
189+
Some((import_path.to_string(), import_name.to_string()))
190+
}),
191+
)
192+
.into_iter()
193+
.flatten()
194+
.for_each(|text_edit| {
195+
combined_edit.union(text_edit).expect(
196+
"Failed to apply completion resolve changes: change ranges overlap, but should not",
197+
)
198+
});
190199

191200
combined_edit.apply(&mut actual);
192201
assert_eq_text!(&ra_fixture_after, &actual)

crates/rust-analyzer/src/to_proto.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ fn completion_item(
283283
let imports: Vec<_> = imports
284284
.iter()
285285
.filter_map(|import_edit| {
286-
let import_path = &import_edit.import.import_path;
286+
let import_path = &import_edit.import_path;
287287
let import_name = import_path.segments().last()?;
288288
Some(lsp_ext::CompletionImport {
289289
full_import_path: import_path.to_string(),

0 commit comments

Comments
 (0)