mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 04:23:25 +00:00
Make group imports configurable
This commit is contained in:
parent
750d3cb846
commit
96fc01a30b
14 changed files with 79 additions and 23 deletions
|
@ -99,8 +99,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
||||||
format!("Import `{}`", &import),
|
format!("Import `{}`", &import),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let rewriter =
|
let rewriter = insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use);
|
||||||
insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
|
|
||||||
builder.rewrite(rewriter);
|
builder.rewrite(rewriter);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -154,7 +154,7 @@ fn insert_import(
|
||||||
mod_path.pop_segment();
|
mod_path.pop_segment();
|
||||||
mod_path.push_segment(variant_hir_name.clone());
|
mod_path.push_segment(variant_hir_name.clone());
|
||||||
let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
|
let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
|
||||||
*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
|
*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub(crate) fn replace_qualified_name_with_use(
|
||||||
let mut rewriter = SyntaxRewriter::default();
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
shorten_paths(&mut rewriter, syntax.clone(), &path);
|
shorten_paths(&mut rewriter, syntax.clone(), &path);
|
||||||
if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
|
if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
|
||||||
rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
|
rewriter += insert_use(import_scope, path, ctx.config.insert_use);
|
||||||
builder.rewrite(rewriter);
|
builder.rewrite(rewriter);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
|
||||||
insert_use: InsertUseConfig {
|
insert_use: InsertUseConfig {
|
||||||
merge: Some(MergeBehavior::Full),
|
merge: Some(MergeBehavior::Full),
|
||||||
prefix_kind: hir::PrefixKind::Plain,
|
prefix_kind: hir::PrefixKind::Plain,
|
||||||
|
group: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::fmt;
|
||||||
use hir::{Documentation, ModPath, Mutability};
|
use hir::{Documentation, ModPath, Mutability};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
helpers::{
|
helpers::{
|
||||||
insert_use::{self, ImportScope, MergeBehavior},
|
insert_use::{self, ImportScope, InsertUseConfig},
|
||||||
mod_path_to_ast, SnippetCap,
|
mod_path_to_ast, SnippetCap,
|
||||||
},
|
},
|
||||||
SymbolKind,
|
SymbolKind,
|
||||||
|
@ -280,14 +280,11 @@ pub struct ImportEdit {
|
||||||
impl ImportEdit {
|
impl ImportEdit {
|
||||||
/// Attempts to insert the import to the given scope, producing a text edit.
|
/// Attempts to insert the import to the given scope, producing a text edit.
|
||||||
/// May return no edit in edge cases, such as scope already containing the import.
|
/// May return no edit in edge cases, such as scope already containing the import.
|
||||||
pub fn to_text_edit(&self, merge_behavior: Option<MergeBehavior>) -> Option<TextEdit> {
|
pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
|
||||||
let _p = profile::span("ImportEdit::to_text_edit");
|
let _p = profile::span("ImportEdit::to_text_edit");
|
||||||
|
|
||||||
let rewriter = insert_use::insert_use(
|
let rewriter =
|
||||||
&self.import_scope,
|
insert_use::insert_use(&self.import_scope, mod_path_to_ast(&self.import_path), cfg);
|
||||||
mod_path_to_ast(&self.import_path),
|
|
||||||
merge_behavior,
|
|
||||||
);
|
|
||||||
let old_ast = rewriter.rewrite_root()?;
|
let old_ast = rewriter.rewrite_root()?;
|
||||||
let mut import_insert = TextEdit::builder();
|
let mut import_insert = TextEdit::builder();
|
||||||
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
|
algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
|
||||||
|
|
|
@ -156,7 +156,7 @@ pub fn resolve_completion_edits(
|
||||||
.find(|mod_path| mod_path.to_string() == full_import_path)?;
|
.find(|mod_path| mod_path.to_string() == full_import_path)?;
|
||||||
|
|
||||||
ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
|
ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
|
||||||
.to_text_edit(config.insert_use.merge)
|
.to_text_edit(config.insert_use)
|
||||||
.map(|edit| vec![edit])
|
.map(|edit| vec![edit])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
|
||||||
insert_use: InsertUseConfig {
|
insert_use: InsertUseConfig {
|
||||||
merge: Some(MergeBehavior::Full),
|
merge: Some(MergeBehavior::Full),
|
||||||
prefix_kind: PrefixKind::Plain,
|
prefix_kind: PrefixKind::Plain,
|
||||||
|
group: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ pub(crate) fn check_edit_with_config(
|
||||||
|
|
||||||
let mut combined_edit = completion.text_edit().to_owned();
|
let mut combined_edit = completion.text_edit().to_owned();
|
||||||
if let Some(import_text_edit) =
|
if let Some(import_text_edit) =
|
||||||
completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge))
|
completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use))
|
||||||
{
|
{
|
||||||
combined_edit.union(import_text_edit).expect(
|
combined_edit.union(import_text_edit).expect(
|
||||||
"Failed to apply completion resolve changes: change ranges overlap, but should not",
|
"Failed to apply completion resolve changes: change ranges overlap, but should not",
|
||||||
|
|
|
@ -19,6 +19,7 @@ use test_utils::mark;
|
||||||
pub struct InsertUseConfig {
|
pub struct InsertUseConfig {
|
||||||
pub merge: Option<MergeBehavior>,
|
pub merge: Option<MergeBehavior>,
|
||||||
pub prefix_kind: hir::PrefixKind,
|
pub prefix_kind: hir::PrefixKind,
|
||||||
|
pub group: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -99,13 +100,13 @@ fn is_inner_comment(token: SyntaxToken) -> bool {
|
||||||
pub fn insert_use<'a>(
|
pub fn insert_use<'a>(
|
||||||
scope: &ImportScope,
|
scope: &ImportScope,
|
||||||
path: ast::Path,
|
path: ast::Path,
|
||||||
merge: Option<MergeBehavior>,
|
cfg: InsertUseConfig,
|
||||||
) -> SyntaxRewriter<'a> {
|
) -> SyntaxRewriter<'a> {
|
||||||
let _p = profile::span("insert_use");
|
let _p = profile::span("insert_use");
|
||||||
let mut rewriter = SyntaxRewriter::default();
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
|
let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
|
||||||
// merge into existing imports if possible
|
// merge into existing imports if possible
|
||||||
if let Some(mb) = merge {
|
if let Some(mb) = cfg.merge {
|
||||||
for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
|
for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
|
||||||
if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
|
if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
|
||||||
rewriter.replace(existing_use.syntax(), merged.syntax());
|
rewriter.replace(existing_use.syntax(), merged.syntax());
|
||||||
|
@ -116,7 +117,7 @@ pub fn insert_use<'a>(
|
||||||
|
|
||||||
// either we weren't allowed to merge or there is no import that fits the merge conditions
|
// either we weren't allowed to merge or there is no import that fits the merge conditions
|
||||||
// so look for the place we have to insert to
|
// so look for the place we have to insert to
|
||||||
let (insert_position, add_blank) = find_insert_position(scope, path);
|
let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group);
|
||||||
|
|
||||||
let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
|
let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
|
||||||
Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
|
Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
|
||||||
|
@ -538,6 +539,7 @@ impl AddBlankLine {
|
||||||
fn find_insert_position(
|
fn find_insert_position(
|
||||||
scope: &ImportScope,
|
scope: &ImportScope,
|
||||||
insert_path: ast::Path,
|
insert_path: ast::Path,
|
||||||
|
group_imports: bool,
|
||||||
) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
|
) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
|
||||||
let group = ImportGroup::new(&insert_path);
|
let group = ImportGroup::new(&insert_path);
|
||||||
let path_node_iter = scope
|
let path_node_iter = scope
|
||||||
|
@ -550,6 +552,14 @@ fn find_insert_position(
|
||||||
let has_tl = tree.use_tree_list().is_some();
|
let has_tl = tree.use_tree_list().is_some();
|
||||||
Some((path, has_tl, node))
|
Some((path, has_tl, node))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if !group_imports {
|
||||||
|
if let Some((_, _, node)) = path_node_iter.last() {
|
||||||
|
return (InsertPosition::After(node.into()), AddBlankLine::Before);
|
||||||
|
}
|
||||||
|
return (InsertPosition::First, AddBlankLine::AfterTwice);
|
||||||
|
}
|
||||||
|
|
||||||
// Iterator that discards anything thats not in the required grouping
|
// Iterator that discards anything thats not in the required grouping
|
||||||
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
|
// This implementation allows the user to rearrange their import groups as this only takes the first group that fits
|
||||||
let group_iter = path_node_iter
|
let group_iter = path_node_iter
|
||||||
|
@ -565,6 +575,7 @@ fn find_insert_position(
|
||||||
use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
|
use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
match post_insert {
|
match post_insert {
|
||||||
// insert our import before that element
|
// insert our import before that element
|
||||||
Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
|
Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
|
||||||
|
|
|
@ -1,7 +1,31 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use hir::PrefixKind;
|
||||||
use test_utils::assert_eq_text;
|
use test_utils::assert_eq_text;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_not_group() {
|
||||||
|
check(
|
||||||
|
"use external_crate2::bar::A",
|
||||||
|
r"
|
||||||
|
use std::bar::B;
|
||||||
|
use external_crate::bar::A;
|
||||||
|
use crate::bar::A;
|
||||||
|
use self::bar::A;
|
||||||
|
use super::bar::A;",
|
||||||
|
r"
|
||||||
|
use std::bar::B;
|
||||||
|
use external_crate::bar::A;
|
||||||
|
use crate::bar::A;
|
||||||
|
use self::bar::A;
|
||||||
|
use super::bar::A;
|
||||||
|
use external_crate2::bar::A;",
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_existing() {
|
fn insert_existing() {
|
||||||
check_full("std::fs", "use std::fs;", "use std::fs;")
|
check_full("std::fs", "use std::fs;", "use std::fs;")
|
||||||
|
@ -240,6 +264,7 @@ fn insert_empty_module() {
|
||||||
}",
|
}",
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,6 +609,7 @@ fn check(
|
||||||
ra_fixture_after: &str,
|
ra_fixture_after: &str,
|
||||||
mb: Option<MergeBehavior>,
|
mb: Option<MergeBehavior>,
|
||||||
module: bool,
|
module: bool,
|
||||||
|
group: bool,
|
||||||
) {
|
) {
|
||||||
let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
|
let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
|
||||||
if module {
|
if module {
|
||||||
|
@ -597,21 +623,25 @@ fn check(
|
||||||
.find_map(ast::Path::cast)
|
.find_map(ast::Path::cast)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let rewriter = insert_use(&file, path, mb);
|
let rewriter = insert_use(
|
||||||
|
&file,
|
||||||
|
path,
|
||||||
|
InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group },
|
||||||
|
);
|
||||||
let result = rewriter.rewrite(file.as_syntax_node()).to_string();
|
let result = rewriter.rewrite(file.as_syntax_node()).to_string();
|
||||||
assert_eq_text!(ra_fixture_after, &result);
|
assert_eq_text!(ra_fixture_after, &result);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||||
check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Full), false)
|
check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Full), false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||||
check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Last), false)
|
check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Last), false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||||
check(path, ra_fixture_before, ra_fixture_after, None, false)
|
check(path, ra_fixture_before, ra_fixture_after, None, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) {
|
fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) {
|
||||||
|
|
|
@ -108,7 +108,11 @@ impl BenchCmd {
|
||||||
add_call_parenthesis: true,
|
add_call_parenthesis: true,
|
||||||
add_call_argument_snippets: true,
|
add_call_argument_snippets: true,
|
||||||
snippet_cap: SnippetCap::new(true),
|
snippet_cap: SnippetCap::new(true),
|
||||||
insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain },
|
insert_use: InsertUseConfig {
|
||||||
|
merge: None,
|
||||||
|
prefix_kind: PrefixKind::Plain,
|
||||||
|
group: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let res = do_work(&mut host, file_id, |analysis| {
|
let res = do_work(&mut host, file_id, |analysis| {
|
||||||
analysis.completions(&options, file_position)
|
analysis.completions(&options, file_position)
|
||||||
|
|
|
@ -35,7 +35,8 @@ config_data! {
|
||||||
assist_importMergeBehaviour: MergeBehaviorDef = "\"full\"",
|
assist_importMergeBehaviour: MergeBehaviorDef = "\"full\"",
|
||||||
/// The path structure for newly inserted paths to use.
|
/// The path structure for newly inserted paths to use.
|
||||||
assist_importPrefix: ImportPrefixDef = "\"plain\"",
|
assist_importPrefix: ImportPrefixDef = "\"plain\"",
|
||||||
|
/// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
|
||||||
|
assist_importGroup: bool = "true",
|
||||||
/// Show function name and docs in parameter hints.
|
/// Show function name and docs in parameter hints.
|
||||||
callInfo_full: bool = "true",
|
callInfo_full: bool = "true",
|
||||||
|
|
||||||
|
@ -574,6 +575,7 @@ impl Config {
|
||||||
ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
|
ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
|
||||||
ImportPrefixDef::BySelf => PrefixKind::BySelf,
|
ImportPrefixDef::BySelf => PrefixKind::BySelf,
|
||||||
},
|
},
|
||||||
|
group: self.data.assist_importGroup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn completion(&self) -> CompletionConfig {
|
pub fn completion(&self) -> CompletionConfig {
|
||||||
|
|
|
@ -1087,7 +1087,11 @@ mod tests {
|
||||||
add_call_parenthesis: true,
|
add_call_parenthesis: true,
|
||||||
add_call_argument_snippets: true,
|
add_call_argument_snippets: true,
|
||||||
snippet_cap: SnippetCap::new(true),
|
snippet_cap: SnippetCap::new(true),
|
||||||
insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain },
|
insert_use: InsertUseConfig {
|
||||||
|
merge: None,
|
||||||
|
prefix_kind: PrefixKind::Plain,
|
||||||
|
group: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ide_db::base_db::FilePosition { file_id, offset },
|
ide_db::base_db::FilePosition { file_id, offset },
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
The strategy to use when inserting new imports or merging imports.
|
The strategy to use when inserting new imports or merging imports.
|
||||||
[[rust-analyzer.assist.importPrefix]]rust-analyzer.assist.importPrefix (default: `"plain"`)::
|
[[rust-analyzer.assist.importPrefix]]rust-analyzer.assist.importPrefix (default: `"plain"`)::
|
||||||
The path structure for newly inserted paths to use.
|
The path structure for newly inserted paths to use.
|
||||||
|
[[rust-analyzer.assist.importGroup]]rust-analyzer.assist.importGroup (default: `true`)::
|
||||||
|
Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
|
||||||
[[rust-analyzer.callInfo.full]]rust-analyzer.callInfo.full (default: `true`)::
|
[[rust-analyzer.callInfo.full]]rust-analyzer.callInfo.full (default: `true`)::
|
||||||
Show function name and docs in parameter hints.
|
Show function name and docs in parameter hints.
|
||||||
[[rust-analyzer.cargo.autoreload]]rust-analyzer.cargo.autoreload (default: `true`)::
|
[[rust-analyzer.cargo.autoreload]]rust-analyzer.cargo.autoreload (default: `true`)::
|
||||||
|
|
|
@ -385,6 +385,11 @@
|
||||||
"Force import paths to be absolute by always starting them with `crate` or the crate name they refer to."
|
"Force import paths to be absolute by always starting them with `crate` or the crate name they refer to."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"rust-analyzer.assist.importGroup": {
|
||||||
|
"markdownDescription": "Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.",
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"rust-analyzer.callInfo.full": {
|
"rust-analyzer.callInfo.full": {
|
||||||
"markdownDescription": "Show function name and docs in parameter hints.",
|
"markdownDescription": "Show function name and docs in parameter hints.",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
|
Loading…
Reference in a new issue