Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs')
| -rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 115 |
1 files changed, 85 insertions, 30 deletions
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index 5ba045d3c8..3e33c62144 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs @@ -1,10 +1,13 @@ use hir::ModuleDef; -use ide_db::helpers::{import_assets::NameToImport, mod_path_to_ast}; +use ide_db::helpers::{ + get_path_at_cursor_in_tt, import_assets::NameToImport, mod_path_to_ast, + parse_tt_as_comma_sep_paths, +}; use ide_db::items_locator; use itertools::Itertools; use syntax::{ - ast::{self, make, AstNode, HasName}, - SyntaxKind::{IDENT, WHITESPACE}, + ast::{self, AstNode, AstToken, HasName}, + SyntaxKind::WHITESPACE, }; use crate::{ @@ -52,9 +55,8 @@ pub(crate) fn replace_derive_with_manual_impl( return None; } - let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; - let trait_name = trait_token.text(); - + let ident = args.syntax().token_at_offset(ctx.offset()).find_map(ast::Ident::cast)?; + let trait_path = get_path_at_cursor_in_tt(&ident)?; let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; let current_module = ctx.sema.scope(adt.syntax()).module()?; @@ -63,7 +65,7 @@ pub(crate) fn replace_derive_with_manual_impl( let found_traits = items_locator::items_with_name( &ctx.sema, current_crate, - NameToImport::Exact(trait_name.to_string()), + NameToImport::Exact(trait_path.segments().last()?.to_string()), items_locator::AssocItemSearch::Exclude, Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()), ) @@ -80,12 +82,23 @@ pub(crate) fn replace_derive_with_manual_impl( }); let mut no_traits_found = true; - for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { - add_assist(acc, ctx, &attr, &args, &trait_path, Some(trait_), &adt)?; + let current_derives = parse_tt_as_comma_sep_paths(args.clone())?; + let current_derives = current_derives.as_slice(); + for (replace_trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { + add_assist( + acc, + ctx, + &attr, + ¤t_derives, + &args, + &trait_path, + &replace_trait_path, + Some(trait_), + &adt, + )?; } if no_traits_found { - let trait_path = make::ext::ident_path(trait_name); - add_assist(acc, ctx, &attr, &args, &trait_path, None, &adt)?; + add_assist(acc, ctx, &attr, ¤t_derives, &args, &trait_path, &trait_path, None, &adt)?; } Some(()) } @@ -94,15 +107,16 @@ fn add_assist( acc: &mut Assists, ctx: &AssistContext, attr: &ast::Attr, - input: &ast::TokenTree, - trait_path: &ast::Path, + old_derives: &[ast::Path], + old_tree: &ast::TokenTree, + old_trait_path: &ast::Path, + replace_trait_path: &ast::Path, trait_: Option<hir::Trait>, adt: &ast::Adt, ) -> Option<()> { let target = attr.syntax().text_range(); let annotated_name = adt.name()?; - let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); - let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; + let label = format!("Convert to manual `impl {} for {}`", replace_trait_path, annotated_name); acc.add( AssistId("replace_derive_with_manual_impl", AssistKind::Refactor), @@ -111,9 +125,9 @@ fn add_assist( |builder| { let insert_pos = adt.syntax().text_range().end(); let impl_def_with_items = - impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, trait_path); - update_attribute(builder, input, &trait_name, attr); - let trait_path = format!("{}", trait_path); + impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, replace_trait_path); + update_attribute(builder, old_derives, old_tree, old_trait_path, attr); + let trait_path = format!("{}", replace_trait_path); match (ctx.config.snippet_cap, impl_def_with_items) { (None, _) => { builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, "")) @@ -192,23 +206,20 @@ fn impl_def_from_trait( fn update_attribute( builder: &mut AssistBuilder, - input: &ast::TokenTree, - trait_name: &ast::NameRef, + old_derives: &[ast::Path], + old_tree: &ast::TokenTree, + old_trait_path: &ast::Path, attr: &ast::Attr, ) { - let trait_name = trait_name.text(); - let new_attr_input = input - .syntax() - .descendants_with_tokens() - .filter(|t| t.kind() == IDENT) - .filter_map(|t| t.into_token().map(|t| t.text().to_string())) - .filter(|t| t != &trait_name) + let new_derives = old_derives + .iter() + .filter(|t| t.to_string() != old_trait_path.to_string()) .collect::<Vec<_>>(); - let has_more_derives = !new_attr_input.is_empty(); + let has_more_derives = !new_derives.is_empty(); if has_more_derives { - let new_attr_input = format!("({})", new_attr_input.iter().format(", ")); - builder.replace(input.syntax().text_range(), new_attr_input); + let new_derives = format!("({})", new_derives.iter().format(", ")); + builder.replace(old_tree.syntax().text_range(), new_derives); } else { let attr_range = attr.syntax().text_range(); builder.delete(attr_range); @@ -1165,4 +1176,48 @@ struct S; "#, ); } + + #[test] + fn add_custom_impl_keep_path() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: clone +#[derive(std::fmt::Debug, Clo$0ne)] +pub struct Foo; +"#, + r#" +#[derive(std::fmt::Debug)] +pub struct Foo; + +impl Clone for Foo { + $0fn clone(&self) -> Self { + Self { } + } +} +"#, + ) + } + + #[test] + fn add_custom_impl_replace_path() { + check_assist( + replace_derive_with_manual_impl, + r#" +//- minicore: fmt +#[derive(core::fmt::Deb$0ug, Clone)] +pub struct Foo; +"#, + r#" +#[derive(Clone)] +pub struct Foo; + +impl core::fmt::Debug for Foo { + $0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Foo").finish() + } +} +"#, + ) + } } |