diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs index 5a6a9f158e..0ede55a6d7 100644 --- a/crates/ide_assists/src/handlers/reorder_impl.rs +++ b/crates/ide_assists/src/handlers/reorder_impl.rs @@ -8,7 +8,7 @@ use syntax::{ ted, AstNode, }; -use crate::{AssistContext, AssistId, AssistKind, Assists}; +use crate::{AssistContext, AssistId, AssistKind, Assists, utils::get_methods}; // Assist: reorder_impl // @@ -76,7 +76,7 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let target = items.syntax().text_range(); acc.add( AssistId("reorder_impl", AssistKind::RefactorRewrite), - "Sort methods", + "Sort methods by trait definition", target, |builder| { let methods = methods.into_iter().map(|fn_| builder.make_mut(fn_)).collect::>(); @@ -111,17 +111,6 @@ fn trait_definition(path: &ast::Path, sema: &Semantics) -> Option< } } -fn get_methods(items: &ast::AssocItemList) -> Vec { - items - .assoc_items() - .flat_map(|i| match i { - ast::AssocItem::Fn(f) => Some(f), - _ => None, - }) - .filter(|f| f.name().is_some()) - .collect() -} - #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; diff --git a/crates/ide_assists/src/handlers/sort_items.rs b/crates/ide_assists/src/handlers/sort_items.rs new file mode 100644 index 0000000000..d86b263c12 --- /dev/null +++ b/crates/ide_assists/src/handlers/sort_items.rs @@ -0,0 +1,149 @@ +use std::cmp::Ordering; + +use hir::known::Option; +use itertools::Itertools; + +use syntax::{ + ast::{self, NameOwner}, + ted, AstNode, +}; + +use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists}; + +// Assist: sort_items +// +pub(crate) fn sort_items(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + if let Some(trait_ast) = ctx.find_node_at_offset::() { + sort_methods_assist(acc, trait_ast.assoc_item_list()?) + } else if let Some(impl_ast) = ctx.find_node_at_offset::() { + sort_methods_assist(acc, impl_ast.assoc_item_list()?) + } else { + None + } +} + +fn sort_methods_assist(acc: &mut Assists, item_list: ast::AssocItemList) -> Option<()> { + let methods = get_methods(&item_list); + let sorted = sort_by_name(&methods); + + if methods == sorted { + cov_mark::hit!(not_applicable_if_sorted); + return None; + } + + acc.add( + AssistId("sort_items", AssistKind::RefactorRewrite), + "Sort methods alphabetically", + item_list.syntax().text_range(), + |builder| { + let methods = methods.into_iter().map(|fn_| builder.make_mut(fn_)).collect::>(); + methods + .into_iter() + .zip(sorted) + .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax())); + }, + ) +} + +fn sort_by_name(initial: &[T]) -> Vec { + initial + .iter() + .cloned() + .sorted_by(|a, b| match (a.name(), b.name()) { + (Some(a), Some(b)) => Ord::cmp(&a.to_string(), &b.to_string()), + + // unexpected, but just in case + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Less, + (Some(_), None) => Ordering::Greater, + }) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn not_applicable_if_trait_sorted() { + cov_mark::check!(not_applicable_if_sorted); + + check_assist_not_applicable( + sort_items, + r#" +t$0rait Bar { + fn a() {} + fn b() {} + fn c() {} +} + "#, + ) + } + + #[test] + fn not_applicable_if_impl_sorted() { + cov_mark::check!(not_applicable_if_sorted); + + check_assist_not_applicable( + sort_items, + r#" +struct Bar; +$0impl Bar { + fn a() {} + fn b() {} + fn c() {} +} + "#, + ) + } + + #[test] + fn sort_trait() { + check_assist( + sort_items, + r#" +$0trait Bar { + fn a() {} + fn c() {} + fn z() {} + fn b() {} +} + "#, + r#" +trait Bar { + fn a() {} + fn b() {} + fn c() {} + fn z() {} +} + "#, + ) + } + + #[test] + fn sort_impl() { + check_assist( + sort_items, + r#" +struct Bar; +$0impl Bar { + fn c() {} + fn a() {} + fn z() {} + fn d() {} +} + "#, + r#" +struct Bar; +impl Bar { + fn a() {} + fn c() {} + fn d() {} + fn z() {} +} + "#, + ) + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 14bf565e56..75cf268061 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -110,6 +110,7 @@ mod handlers { mod replace_qualified_name_with_use; mod replace_string_with_char; mod split_import; + mod sort_items; mod toggle_ignore; mod unmerge_use; mod unwrap_block; @@ -181,6 +182,7 @@ mod handlers { replace_impl_trait_with_generic::replace_impl_trait_with_generic, replace_let_with_if_let::replace_let_with_if_let, replace_qualified_name_with_use::replace_qualified_name_with_use, + sort_items::sort_items, split_import::split_import, toggle_ignore::toggle_ignore, unmerge_use::unmerge_use, diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 81463745a4..7ebe7df1d8 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs @@ -516,3 +516,14 @@ fn ty_ctor(ty: &String, ctor: &str) -> Option { let res = ty.to_string().strip_prefix(ctor)?.strip_prefix('<')?.strip_suffix('>')?.to_string(); Some(res) } + +pub(crate) fn get_methods(items: &ast::AssocItemList) -> Vec { + items + .assoc_items() + .flat_map(|i| match i { + ast::AssocItem::Fn(f) => Some(f), + _ => None, + }) + .filter(|f| f.name().is_some()) + .collect() +}