Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/ide-assists/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand All @@ -50,6 +51,7 @@ pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig {
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand All @@ -72,6 +74,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand All @@ -94,6 +97,7 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig {
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand Down
1 change: 1 addition & 0 deletions crates/ide-completion/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig {
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand Down
2 changes: 2 additions & 0 deletions crates/ide-db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ triomphe.workspace = true
nohash-hasher.workspace = true
bitflags.workspace = true
smallvec.workspace = true
serde = { version = "1.0.219" }
serde_derive = { version = "1.0.219" }

# local deps
base-db.workspace = true
Expand Down
89 changes: 69 additions & 20 deletions crates/ide-db/src/imports/insert_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod tests;
use std::cmp::Ordering;

use hir::Semantics;
use serde_derive::{Deserialize, Serialize};
use syntax::{
Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo,
ast::{
Expand Down Expand Up @@ -54,6 +55,20 @@ pub struct InsertUseConfig {
pub prefix_kind: PrefixKind,
pub group: bool,
pub skip_glob_imports: bool,
pub group_order: [ImportGroupKind; 6],
}

impl InsertUseConfig {
pub const fn default_group_order() -> [ImportGroupKind; 6] {
[
ImportGroupKind::Std,
ImportGroupKind::ExternCrate,
ImportGroupKind::ThisCrate,
ImportGroupKind::ThisModule,
ImportGroupKind::SuperModule,
ImportGroupKind::One,
]
}
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -226,7 +241,7 @@ fn insert_use_with_alias_option(
}
// 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
insert_use_(scope, use_item, cfg.group);
insert_use_(scope, use_item, cfg.group, &cfg.group_order);
}

pub fn ast_to_remove_for_path_in_use_stmt(path: &ast::Path) -> Option<Box<dyn Removable>> {
Expand All @@ -250,9 +265,14 @@ pub fn remove_path_if_in_use_stmt(path: &ast::Path) {
}
}

#[derive(Eq, PartialEq, PartialOrd, Ord)]
enum ImportGroup {
// the order here defines the order of new group inserts
#[derive(Eq, PartialEq)]
struct ImportGroup {
priority: u8,
kind: ImportGroupKind,
}

#[derive(Eq, PartialEq, Clone, Copy, Debug, Deserialize, Serialize)]
pub enum ImportGroupKind {
Std,
ExternCrate,
ThisCrate,
Expand All @@ -262,33 +282,57 @@ enum ImportGroup {
}

impl ImportGroup {
fn new(use_tree: &ast::UseTree) -> ImportGroup {
fn new(use_tree: &ast::UseTree, priority_config: &[ImportGroupKind]) -> ImportGroup {
let kind = Self::determine_kind(use_tree);

let priority = priority_config
.iter()
.position(|group_name| *group_name == kind)
.map(|pos| 255 - (pos as u8)) // Invert priority: higher positions = lower priority
.unwrap_or(0); // Default to lowest priority if not found

ImportGroup { priority, kind }
}

fn determine_kind(use_tree: &ast::UseTree) -> ImportGroupKind {
if use_tree.path().is_none() && use_tree.use_tree_list().is_some() {
return ImportGroup::One;
return ImportGroupKind::One;
}

let Some(first_segment) = use_tree.path().as_ref().and_then(ast::Path::first_segment)
else {
return ImportGroup::ExternCrate;
return ImportGroupKind::ExternCrate;
};

let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
match kind {
PathSegmentKind::SelfKw => ImportGroup::ThisModule,
PathSegmentKind::SuperKw => ImportGroup::SuperModule,
PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
PathSegmentKind::SelfKw => ImportGroupKind::ThisModule,
PathSegmentKind::SuperKw => ImportGroupKind::SuperModule,
PathSegmentKind::CrateKw => ImportGroupKind::ThisCrate,
PathSegmentKind::Name(name) => match name.text().as_str() {
"std" => ImportGroup::Std,
"core" => ImportGroup::Std,
_ => ImportGroup::ExternCrate,
"std" => ImportGroupKind::Std,
"core" => ImportGroupKind::Std,
_ => ImportGroupKind::ExternCrate,
},
// these aren't valid use paths, so fall back to something random
PathSegmentKind::SelfTypeKw => ImportGroup::ExternCrate,
PathSegmentKind::Type { .. } => ImportGroup::ExternCrate,
PathSegmentKind::SelfTypeKw => ImportGroupKind::ExternCrate,
PathSegmentKind::Type { .. } => ImportGroupKind::ExternCrate,
}
}
}

impl PartialOrd for ImportGroup {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for ImportGroup {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.priority.cmp(&other.priority)
}
}

#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
enum ImportGranularityGuess {
Unknown,
Expand Down Expand Up @@ -385,11 +429,16 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
}
}

fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
fn insert_use_(
scope: &ImportScope,
use_item: ast::Use,
group_imports: bool,
group_priority: &[ImportGroupKind],
) {
let scope_syntax = scope.as_syntax_node();
let insert_use_tree =
use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
let group = ImportGroup::new(&insert_use_tree);
let group = ImportGroup::new(&insert_use_tree, group_priority);
let path_node_iter = scope_syntax
.children()
.filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
Expand All @@ -403,8 +452,8 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
// 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
.clone()
.skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
.take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
.skip_while(|(use_tree, ..)| ImportGroup::new(use_tree, group_priority) != group)
.take_while(|(use_tree, ..)| ImportGroup::new(use_tree, group_priority) == group);

// track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
let mut last = None;
Expand All @@ -430,7 +479,7 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
// find the group that comes after where we want to insert
let post_group = path_node_iter
.inspect(|(.., node)| last = Some(node.clone()))
.find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
.find(|(use_tree, ..)| ImportGroup::new(use_tree, group_priority) < group);
if let Some((.., node)) = post_group {
cov_mark::hit!(insert_group_new_group);
ted::insert(ted::Position::before(&node), use_item.syntax());
Expand Down
10 changes: 10 additions & 0 deletions crates/ide-db/src/imports/insert_use/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ use external_crate2::bar::A;",
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand Down Expand Up @@ -461,6 +462,7 @@ fn insert_empty_file() {
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand Down Expand Up @@ -496,6 +498,7 @@ mod x {
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand Down Expand Up @@ -526,6 +529,7 @@ use foo::bar;",
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand Down Expand Up @@ -953,6 +957,7 @@ fn merge_mod_into_glob() {
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: false,
group_order: InsertUseConfig::default_group_order(),
},
)
}
Expand All @@ -969,6 +974,7 @@ fn merge_self_glob() {
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: false,
group_order: InsertUseConfig::default_group_order(),
},
)
}
Expand Down Expand Up @@ -1281,6 +1287,7 @@ use self::foo::Foo;
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand All @@ -1301,6 +1308,7 @@ use self::foo::{self, Bar, Foo};
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand All @@ -1321,6 +1329,7 @@ use ::ext::foo::Foo;
enforce_granularity: true,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
);
}
Expand Down Expand Up @@ -1378,6 +1387,7 @@ fn check(
prefix_kind: PrefixKind::Plain,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
)
}
Expand Down
1 change: 1 addition & 0 deletions crates/ide-diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ impl DiagnosticsConfig {
prefix_kind: PrefixKind::Plain,
group: false,
skip_glob_imports: false,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand Down
2 changes: 2 additions & 0 deletions crates/rust-analyzer/src/cli/analysis_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use ide::{
use ide_db::{
EditionedFileId, LineIndexDatabase, MiniCore, SnippetCap,
base_db::{SourceDatabase, salsa::Database},
imports::insert_use::InsertUseConfig,
};
use itertools::Itertools;
use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace};
Expand Down Expand Up @@ -1187,6 +1188,7 @@ impl flags::AnalysisStats {
prefix_kind: hir::PrefixKind::ByCrate,
group: true,
skip_glob_imports: true,
group_order: InsertUseConfig::default_group_order(),
},
prefer_no_std: false,
prefer_prelude: true,
Expand Down
21 changes: 20 additions & 1 deletion crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use ide::{
use ide_db::{
MiniCore, SnippetCap,
assists::ExprFillDefaultMode,
imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
imports::insert_use::{ImportGranularity, ImportGroupKind, InsertUseConfig, PrefixKind},
};
use itertools::{Either, Itertools};
use paths::{Utf8Path, Utf8PathBuf};
Expand Down Expand Up @@ -739,6 +739,17 @@ config_data! {
/// separated by newlines.
imports_group_enable: bool = true,

/// Custom ordering of import groups. Available groups: "std", "extern_crate", "this_crate",
/// "this_module", "super_module", "one".
imports_group_priority: [ImportGroupKind; 6] = [
ImportGroupKind::Std,
ImportGroupKind::ExternCrate,
ImportGroupKind::ThisCrate,
ImportGroupKind::ThisModule,
ImportGroupKind::SuperModule,
ImportGroupKind::One,
],

/// Allow import insertion to merge new imports into single path glob imports like `use
/// std::fmt::*;`.
imports_merge_glob: bool = true,
Expand Down Expand Up @@ -2104,6 +2115,7 @@ impl Config {
},
group: self.imports_group_enable(source_root).to_owned(),
skip_glob_imports: !self.imports_merge_glob(source_root),
group_order: self.imports_group_priority(source_root).to_owned(),
}
}

Expand Down Expand Up @@ -4053,6 +4065,13 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
]
}
},
"[ImportGroupKind; 6]" => set! {
"type": "array",
"items": {
"type": "string",
"enum": ["impl_fn", "rust_analyzer", "with_id", "hide"],
},
},
_ => panic!("missing entry for {ty}: {default} (field {field})"),
}

Expand Down
Loading
Loading