mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-11-03 13:13:18 +00:00 
			
		
		
		
	Order auto-imports by relevance
This first attempt prefers items that are closer to the module they are imported in.
This commit is contained in:
		
							parent
							
								
									2a978e1404
								
							
						
					
					
						commit
						aef16300f7
					
				@ -1,7 +1,10 @@
 | 
				
			|||||||
 | 
					use std::cmp::Reverse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use hir::{db::HirDatabase, Module};
 | 
				
			||||||
use ide_db::{
 | 
					use ide_db::{
 | 
				
			||||||
    helpers::mod_path_to_ast,
 | 
					    helpers::mod_path_to_ast,
 | 
				
			||||||
    imports::{
 | 
					    imports::{
 | 
				
			||||||
        import_assets::{ImportAssets, ImportCandidate},
 | 
					        import_assets::{ImportAssets, ImportCandidate, LocatedImport},
 | 
				
			||||||
        insert_use::{insert_use, ImportScope},
 | 
					        insert_use::{insert_use, ImportScope},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -107,6 +110,10 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // we aren't interested in different namespaces
 | 
					    // we aren't interested in different namespaces
 | 
				
			||||||
    proposed_imports.dedup_by(|a, b| a.import_path == b.import_path);
 | 
					    proposed_imports.dedup_by(|a, b| a.import_path == b.import_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // prioritize more relevant imports
 | 
				
			||||||
 | 
					    proposed_imports.sort_by_key(|import| Reverse(relevance_score(ctx, import)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for import in proposed_imports {
 | 
					    for import in proposed_imports {
 | 
				
			||||||
        acc.add_group(
 | 
					        acc.add_group(
 | 
				
			||||||
            &group_label,
 | 
					            &group_label,
 | 
				
			||||||
@ -158,11 +165,142 @@ fn group_label(import_candidate: &ImportCandidate) -> GroupLabel {
 | 
				
			|||||||
    GroupLabel(name)
 | 
					    GroupLabel(name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Determine how relevant a given import is in the current context. Higher scores are more
 | 
				
			||||||
 | 
					/// relevant.
 | 
				
			||||||
 | 
					fn relevance_score(ctx: &AssistContext, import: &LocatedImport) -> i32 {
 | 
				
			||||||
 | 
					    let mut score = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let db = ctx.db();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let item_module = match import.item_to_import {
 | 
				
			||||||
 | 
					        hir::ItemInNs::Types(item) | hir::ItemInNs::Values(item) => item.module(db),
 | 
				
			||||||
 | 
					        hir::ItemInNs::Macros(makro) => Some(makro.module(db)),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let current_node = match ctx.covering_element() {
 | 
				
			||||||
 | 
					        NodeOrToken::Node(node) => Some(node),
 | 
				
			||||||
 | 
					        NodeOrToken::Token(token) => token.parent(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(module) = item_module.as_ref() {
 | 
				
			||||||
 | 
					        if module.krate().is_builtin(db) {
 | 
				
			||||||
 | 
					            // prefer items builtin to the language
 | 
				
			||||||
 | 
					            score += 5;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match item_module.zip(current_node) {
 | 
				
			||||||
 | 
					        // get the distance between the modules (prefer items that are more local)
 | 
				
			||||||
 | 
					        Some((item_module, current_node)) => {
 | 
				
			||||||
 | 
					            let current_module = ctx.sema.scope(¤t_node).unwrap().module();
 | 
				
			||||||
 | 
					            score -= module_distance_hueristic(¤t_module, &item_module, db) as i32;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // could not find relevant modules, so just use the length of the path as an estimate
 | 
				
			||||||
 | 
					        None => return -(2 * import.import_path.len() as i32),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    score
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A heuristic that gives a higher score to modules that are more separated.
 | 
				
			||||||
 | 
					fn module_distance_hueristic(current: &Module, item: &Module, db: &dyn HirDatabase) -> usize {
 | 
				
			||||||
 | 
					    let mut current_path = current.path_to_root(db);
 | 
				
			||||||
 | 
					    let mut item_path = item.path_to_root(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    current_path.reverse();
 | 
				
			||||||
 | 
					    item_path.reverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut i = 0;
 | 
				
			||||||
 | 
					    while i < current_path.len() && i < item_path.len() {
 | 
				
			||||||
 | 
					        if current_path[i] == item_path[i] {
 | 
				
			||||||
 | 
					            i += 1
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let distinct_distance = current_path.len() + item_path.len();
 | 
				
			||||||
 | 
					    let common_prefix = 2 * i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // prefer builtin crates and items within the same crate
 | 
				
			||||||
 | 
					    let crate_boundary_cost =
 | 
				
			||||||
 | 
					        if item.krate().is_builtin(db) || current.krate() == item.krate() { 0 } else { 4 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    distinct_distance - common_prefix + crate_boundary_cost
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
 | 
					    use hir::Semantics;
 | 
				
			||||||
 | 
					    use ide_db::{
 | 
				
			||||||
 | 
					        assists::AssistResolveStrategy,
 | 
				
			||||||
 | 
					        base_db::{fixture::WithFixture, FileRange},
 | 
				
			||||||
 | 
					        RootDatabase,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use crate::tests::{
 | 
				
			||||||
 | 
					        check_assist, check_assist_not_applicable, check_assist_target, TEST_CONFIG,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn check_auto_import_order(before: &str, order: &[&str]) {
 | 
				
			||||||
 | 
					        let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
 | 
				
			||||||
 | 
					        let frange = FileRange { file_id, range: range_or_offset.into() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sema = Semantics::new(&db);
 | 
				
			||||||
 | 
					        let config = TEST_CONFIG;
 | 
				
			||||||
 | 
					        let ctx = AssistContext::new(sema, &config, frange);
 | 
				
			||||||
 | 
					        let mut acc = Assists::new(&ctx, AssistResolveStrategy::All);
 | 
				
			||||||
 | 
					        auto_import(&mut acc, &ctx);
 | 
				
			||||||
 | 
					        let assists = acc.finish();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let labels = assists.iter().map(|assist| assist.label.to_string()).collect::<Vec<_>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(labels, order);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn prefer_shorter_paths() {
 | 
				
			||||||
 | 
					        let before = r"
 | 
				
			||||||
 | 
					            //- /main.rs crate:main deps:foo,bar
 | 
				
			||||||
 | 
					            HashMap$0::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //- /lib.rs crate:foo
 | 
				
			||||||
 | 
					            pub mod collections { pub struct HashMap; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //- /lib.rs crate:bar
 | 
				
			||||||
 | 
					            pub mod collections { pub mod hash_map { pub struct HashMap; } }
 | 
				
			||||||
 | 
					        ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        check_auto_import_order(
 | 
				
			||||||
 | 
					            before,
 | 
				
			||||||
 | 
					            &["Import `foo::collections::HashMap`", "Import `bar::collections::hash_map::HashMap`"],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn prefer_same_crate() {
 | 
				
			||||||
 | 
					        let before = r"
 | 
				
			||||||
 | 
					            //- /main.rs crate:main deps:foo
 | 
				
			||||||
 | 
					            HashMap$0::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mod collections {
 | 
				
			||||||
 | 
					                pub mod hash_map {
 | 
				
			||||||
 | 
					                    pub struct HashMap;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //- /lib.rs crate:foo
 | 
				
			||||||
 | 
					            pub struct HashMap;
 | 
				
			||||||
 | 
					        ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        check_auto_import_order(
 | 
				
			||||||
 | 
					            before,
 | 
				
			||||||
 | 
					            &["Import `collections::hash_map::HashMap`", "Import `foo::HashMap`"],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn not_applicable_if_scope_inside_macro() {
 | 
					    fn not_applicable_if_scope_inside_macro() {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user