diff --git a/Cargo.lock b/Cargo.lock index 755ae55eea46..f2737b7fa83f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1111,6 +1111,8 @@ dependencies = [ "rustc-hash 2.1.1", "salsa", "salsa-macros", + "serde", + "serde_derive", "smallvec", "span", "stdx", diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index a52bd74d146a..6ea291bc8c2a 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -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, @@ -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, @@ -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, @@ -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, diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index cb1adfcfb65e..e77138e29d49 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -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, diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml index fca06b69d1bb..c1d6a0ae3a20 100644 --- a/crates/ide-db/Cargo.toml +++ b/crates/ide-db/Cargo.toml @@ -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 diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs index db1d599d550d..62d2637dcc0d 100644 --- a/crates/ide-db/src/imports/insert_use.rs +++ b/crates/ide-db/src/imports/insert_use.rs @@ -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::{ @@ -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)] @@ -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> { @@ -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, @@ -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 { + 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, @@ -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))) @@ -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; @@ -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()); diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs index 3350e1c3d207..766785e3847f 100644 --- a/crates/ide-db/src/imports/insert_use/tests.rs +++ b/crates/ide-db/src/imports/insert_use/tests.rs @@ -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(), }, ); } @@ -461,6 +462,7 @@ fn insert_empty_file() { prefix_kind: PrefixKind::Plain, group: false, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, ); } @@ -496,6 +498,7 @@ mod x { prefix_kind: PrefixKind::Plain, group: false, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, ); } @@ -526,6 +529,7 @@ use foo::bar;", prefix_kind: PrefixKind::Plain, group: false, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, ); } @@ -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(), }, ) } @@ -969,6 +974,7 @@ fn merge_self_glob() { prefix_kind: PrefixKind::Plain, group: false, skip_glob_imports: false, + group_order: InsertUseConfig::default_group_order(), }, ) } @@ -1281,6 +1287,7 @@ use self::foo::Foo; enforce_granularity: true, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, ); } @@ -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(), }, ); } @@ -1321,6 +1329,7 @@ use ::ext::foo::Foo; enforce_granularity: true, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, ); } @@ -1378,6 +1387,7 @@ fn check( prefix_kind: PrefixKind::Plain, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, ) } diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 0c6953419f7d..6e3552534a99 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -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, diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 1995d3889891..eca75dd07402 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -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}; @@ -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, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 0dda7f3cc276..edc88238c79d 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -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}; @@ -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, @@ -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(), } } @@ -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})"), } diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index d16ca2fb48ac..c6893c44fd2b 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -176,6 +176,7 @@ fn integrated_completion_benchmark() { enforce_granularity: true, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, prefer_no_std: false, prefer_prelude: true, @@ -231,6 +232,7 @@ fn integrated_completion_benchmark() { enforce_granularity: true, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, prefer_no_std: false, prefer_prelude: true, @@ -284,6 +286,7 @@ fn integrated_completion_benchmark() { enforce_granularity: true, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, prefer_no_std: false, prefer_prelude: true, @@ -360,6 +363,7 @@ fn integrated_diagnostics_benchmark() { prefix_kind: hir::PrefixKind::ByCrate, group: true, skip_glob_imports: true, + group_order: InsertUseConfig::default_group_order(), }, prefer_no_std: false, prefer_prelude: false, diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index 8460c2c7d0cf..4a1f1e7d7982 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -879,6 +879,24 @@ order](https://rust-analyzer.github.io/book/features.html#auto-import). Groups a separated by newlines. +## rust-analyzer.imports.group.priority {#imports.group.priority} + +Default: +```json +[ + "Std", + "ExternCrate", + "ThisCrate", + "ThisModule", + "SuperModule", + "One" +] +``` + +Custom ordering of import groups. Available groups: "std", "extern_crate", "this_crate", +"this_module", "super_module", "one". + + ## rust-analyzer.imports.merge.glob {#imports.merge.glob} Default: `true` diff --git a/editors/code/package.json b/editors/code/package.json index fc20597e8876..e6b4b8d027d9 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2052,6 +2052,32 @@ } } }, + { + "title": "Imports", + "properties": { + "rust-analyzer.imports.group.priority": { + "markdownDescription": "Custom ordering of import groups. Available groups: \"std\", \"extern_crate\", \"this_crate\",\n\"this_module\", \"super_module\", \"one\".", + "default": [ + "Std", + "ExternCrate", + "ThisCrate", + "ThisModule", + "SuperModule", + "One" + ], + "type": "array", + "items": { + "type": "string", + "enum": [ + "impl_fn", + "rust_analyzer", + "with_id", + "hide" + ] + } + } + } + }, { "title": "Imports", "properties": {