diff --git a/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs b/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs index 6198dbc4ed99..1f4d6809f2c1 100644 --- a/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs +++ b/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs @@ -1,7 +1,7 @@ use ide_db::{RootDatabase, famous_defs::FamousDefs}; -use syntax::ast::{self, AstNode, HasName}; +use syntax::ast::{self, AstNode, HasName, edit::AstNodeEdit}; -use crate::{AssistContext, AssistId, Assists}; +use crate::{AssistContext, AssistId, Assists, utils::indent_string}; // Assist: generate_default_from_enum_variant // @@ -34,7 +34,8 @@ pub(crate) fn generate_default_from_enum_variant( ) -> Option<()> { let variant = ctx.find_node_at_offset::()?; let variant_name = variant.name()?; - let enum_name = variant.parent_enum().name()?; + let enum_adt = variant.parent_enum(); + let enum_name = enum_adt.name()?; if !matches!(variant.kind(), ast::StructKind::Unit) { cov_mark::hit!(test_gen_default_on_non_unit_variant_not_implemented); return None; @@ -61,7 +62,7 @@ impl Default for {enum_name} {{ }} }}"#, ); - edit.insert(start_offset, buf); + edit.insert(start_offset, indent_string(&buf, enum_adt.indent_level())); }, ) } @@ -114,6 +115,42 @@ impl Default for Variant { ); } + #[test] + fn test_generate_default_from_variant_with_indent() { + check_assist( + generate_default_from_enum_variant, + r#" +//- minicore: default +mod foo { + mod bar { + enum Variant { + Undefined, + Minor$0, + Major, + } + } +} +"#, + r#" +mod foo { + mod bar { + enum Variant { + Undefined, + Minor, + Major, + } + + impl Default for Variant { + fn default() -> Self { + Self::Minor + } + } + } +} +"#, + ); + } + #[test] fn test_generate_default_already_implemented() { cov_mark::check!(test_gen_default_impl_already_exists); diff --git a/crates/ide-assists/src/handlers/generate_default_from_new.rs b/crates/ide-assists/src/handlers/generate_default_from_new.rs index 79a78ab3698b..a5affd30af3f 100644 --- a/crates/ide-assists/src/handlers/generate_default_from_new.rs +++ b/crates/ide-assists/src/handlers/generate_default_from_new.rs @@ -2,12 +2,13 @@ use ide_db::famous_defs::FamousDefs; use stdx::format_to; use syntax::{ AstNode, - ast::{self, HasGenericParams, HasName, Impl, make}, + ast::{self, HasGenericParams, HasName, Impl, edit::AstNodeEdit, make}, }; use crate::{ AssistId, assist_context::{AssistContext, Assists}, + utils::indent_string, }; // Assist: generate_default_from_new @@ -72,8 +73,10 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<' let default_code = " fn default() -> Self { Self::new() }"; + let mut indent_level = fn_node.indent_level(); + indent_level.0 = indent_level.0.saturating_sub(1); let code = generate_trait_impl_text_from_impl(&impl_, self_ty, "Default", default_code); - builder.insert(insert_location.end(), code); + builder.insert(insert_location.end(), indent_string(&code, indent_level)); }, ) } @@ -119,6 +122,7 @@ fn generate_trait_impl_text_from_impl( match impl_.where_clause() { Some(where_clause) => { + let where_clause = where_clause.reset_indent(); format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}"); } None => { @@ -417,6 +421,61 @@ where ); } + #[test] + fn new_function_with_indent() { + check_assist( + generate_default_from_new, + r#" +//- minicore: default +mod foo { + mod bar { + pub struct Foo { + _tars: T, + _bar: B, + } + + impl, B: From> Foo + where + Option: Debug, Option: Debug, + { + pub fn ne$0w() -> Self { + unimplemented!() + } + } + } +} +"#, + r#" +mod foo { + mod bar { + pub struct Foo { + _tars: T, + _bar: B, + } + + impl, B: From> Foo + where + Option: Debug, Option: Debug, + { + pub fn new() -> Self { + unimplemented!() + } + } + + impl, B: From> Default for Foo + where + Option: Debug, Option: Debug, + { + fn default() -> Self { + Self::new() + } + } + } +} +"#, + ); + } + #[test] fn new_function_with_generics_and_where() { check_assist( @@ -629,12 +688,12 @@ mod test { } } -impl Default for Example { - fn default() -> Self { - Self::new() + impl Default for Example { + fn default() -> Self { + Self::new() + } } } -} "#, ); } diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs index c7b97dcd231d..3a59badb22d9 100644 --- a/crates/ide-assists/src/handlers/generate_deref.rs +++ b/crates/ide-assists/src/handlers/generate_deref.rs @@ -4,13 +4,13 @@ use hir::{ModPath, ModuleDef}; use ide_db::{RootDatabase, famous_defs::FamousDefs}; use syntax::{ AstNode, Edition, SyntaxNode, - ast::{self, HasName}, + ast::{self, HasName, edit::AstNodeEdit}, }; use crate::{ AssistId, assist_context::{AssistContext, Assists, SourceChangeBuilder}, - utils::generate_trait_impl_text, + utils::{generate_trait_impl_text, indent_string}, }; // Assist: generate_deref @@ -155,7 +155,7 @@ fn generate_edit( &trait_path.display(db, edition).to_string(), &impl_code, ); - edit.insert(start_offset, deref_impl); + edit.insert(start_offset, indent_string(&deref_impl, strukt_adt.indent_level())); } fn existing_deref_impl( @@ -294,6 +294,38 @@ impl core::ops::Deref for B { ); } + #[test] + fn test_generate_field_deref_with_indent() { + check_assist( + generate_deref, + r#" +//- minicore: deref +mod foo { + mod bar { + struct A { } + struct B(u8, $0A); + } +} +"#, + r#" +mod foo { + mod bar { + struct A { } + struct B(u8, A); + + impl core::ops::Deref for B { + type Target = A; + + fn deref(&self) -> &Self::Target { + &self.1 + } + } + } +} +"#, + ); + } + #[test] fn test_generates_derefmut_when_deref_present() { check_assist( diff --git a/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/crates/ide-assists/src/handlers/generate_enum_is_method.rs index 3e6d0bec68a6..2de77ed86dbc 100644 --- a/crates/ide-assists/src/handlers/generate_enum_is_method.rs +++ b/crates/ide-assists/src/handlers/generate_enum_is_method.rs @@ -111,6 +111,45 @@ impl Variant { ); } + #[test] + fn test_generate_enum_is_from_variant_with_indent() { + check_assist( + generate_enum_is_method, + r#" +mod foo { + mod bar { + enum Variant { + Undefined, + Minor$0, + Major, + } + } +} +"#, + r#" +mod foo { + mod bar { + enum Variant { + Undefined, + Minor, + Major, + } + + impl Variant { + /// Returns `true` if the variant is [`Minor`]. + /// + /// [`Minor`]: Variant::Minor + #[must_use] + fn is_minor(&self) -> bool { + matches!(self, Self::Minor) + } + } + } +} +"#, + ); + } + #[test] fn test_generate_enum_is_already_implemented() { check_assist_not_applicable( @@ -280,6 +319,63 @@ impl Variant { ); } + #[test] + fn test_multiple_generate_enum_is_from_variant_with_indent() { + check_assist( + generate_enum_is_method, + r#" +mod foo { + mod bar { + enum Variant { + Undefined, + Minor, + Major$0, + } + + impl Variant { + /// Returns `true` if the variant is [`Minor`]. + /// + /// [`Minor`]: Variant::Minor + #[must_use] + fn is_minor(&self) -> bool { + matches!(self, Self::Minor) + } + } + } +} +"#, + r#" +mod foo { + mod bar { + enum Variant { + Undefined, + Minor, + Major, + } + + impl Variant { + /// Returns `true` if the variant is [`Minor`]. + /// + /// [`Minor`]: Variant::Minor + #[must_use] + fn is_minor(&self) -> bool { + matches!(self, Self::Minor) + } + + /// Returns `true` if the variant is [`Major`]. + /// + /// [`Major`]: Variant::Major + #[must_use] + fn is_major(&self) -> bool { + matches!(self, Self::Major) + } + } + } +} +"#, + ); + } + #[test] fn test_generate_enum_is_variant_names() { check_assist( diff --git a/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/crates/ide-assists/src/handlers/generate_enum_projection_method.rs index 3974bcf61875..57527b70c1ec 100644 --- a/crates/ide-assists/src/handlers/generate_enum_projection_method.rs +++ b/crates/ide-assists/src/handlers/generate_enum_projection_method.rs @@ -214,6 +214,43 @@ impl Value { ); } + #[test] + fn test_generate_enum_try_into_tuple_variant_with_indent() { + check_assist( + generate_enum_try_into_method, + r#" +mod foo { + mod bar { + enum Value { + Number(i32), + Text(String)$0, + } + } +} +"#, + r#" +mod foo { + mod bar { + enum Value { + Number(i32), + Text(String), + } + + impl Value { + fn try_into_text(self) -> Result { + if let Self::Text(v) = self { + Ok(v) + } else { + Err(self) + } + } + } + } +} +"#, + ); + } + #[test] fn test_generate_enum_try_into_already_implemented() { check_assist_not_applicable( @@ -321,6 +358,43 @@ impl Value { ); } + #[test] + fn test_generate_enum_as_tuple_variant_with_indent() { + check_assist( + generate_enum_as_method, + r#" +mod foo { + mod bar { + enum Value { + Number(i32), + Text(String)$0, + } + } +} +"#, + r#" +mod foo { + mod bar { + enum Value { + Number(i32), + Text(String), + } + + impl Value { + fn as_text(&self) -> Option<&String> { + if let Self::Text(v) = self { + Some(v) + } else { + None + } + } + } + } +} +"#, + ); + } + #[test] fn test_generate_enum_as_record_variant() { check_assist( diff --git a/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs b/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs index af949a064989..1837ce1ae023 100644 --- a/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs +++ b/crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs @@ -1,7 +1,10 @@ use ide_db::{RootDatabase, famous_defs::FamousDefs}; -use syntax::ast::{self, AstNode, HasName}; +use syntax::ast::{self, AstNode, HasName, edit::AstNodeEdit}; -use crate::{AssistContext, AssistId, Assists, utils::generate_trait_impl_text_intransitive}; +use crate::{ + AssistContext, AssistId, Assists, + utils::{generate_trait_impl_text_intransitive, indent_string}, +}; // Assist: generate_from_impl_for_enum // @@ -71,7 +74,7 @@ pub(crate) fn generate_from_impl_for_enum( ) }; let from_impl = generate_trait_impl_text_intransitive(&enum_, &from_trait, &impl_code); - edit.insert(start_offset, from_impl); + edit.insert(start_offset, indent_string(&from_impl, enum_.indent_level())); }, ) } @@ -295,6 +298,54 @@ impl<'a> From<&'a i32> for Generic<'a> { Self::One(v) } } +"#, + ); + } + + #[test] + fn test_non_zero_indent() { + check_assist( + generate_from_impl_for_enum, + r#" +//- minicore: from +mod foo { + enum Generic<'a> { $0One(&'a i32) } +} +"#, + r#" +mod foo { + enum Generic<'a> { One(&'a i32) } + + impl<'a> From<&'a i32> for Generic<'a> { + fn from(v: &'a i32) -> Self { + Self::One(v) + } + } +} +"#, + ); + check_assist( + generate_from_impl_for_enum, + r#" +//- minicore: from +mod foo { + mod bar { + enum Generic<'a> { $0One(&'a i32) } + } +} +"#, + r#" +mod foo { + mod bar { + enum Generic<'a> { One(&'a i32) } + + impl<'a> From<&'a i32> for Generic<'a> { + fn from(v: &'a i32) -> Self { + Self::One(v) + } + } + } +} "#, ); } diff --git a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs index c7e5e41aac4c..d2beac5d6901 100644 --- a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs +++ b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs @@ -412,16 +412,21 @@ fn build_source_change( // Insert it after the adt let strukt = builder.make_mut(assist_info.strukt.clone()); + let indent = strukt.indent_level(); ted::insert_all_raw( ted::Position::after(strukt.syntax()), - vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()], + vec![ + make::tokens::whitespace(&format!("\n\n{indent}")).into(), + impl_def.syntax().clone().into(), + ], ); impl_def }; let assoc_item_list = impl_def.get_or_create_assoc_item_list(); + let indent = assoc_item_list.indent_level(); for (i, record_field_info) in info_of_record_fields.iter().enumerate() { // Make the new getter or setter fn @@ -430,7 +435,7 @@ fn build_source_change( _ => generate_getter_from_info(ctx, &assist_info, record_field_info), } .clone_for_update(); - new_fn.indent(1.into()); + new_fn.indent(indent + 1); // Insert a tabstop only for last method we generate if i == record_fields_count - 1 { @@ -494,6 +499,37 @@ impl Context { ); } + #[test] + fn test_generate_getter_from_field_with_indent() { + check_assist( + generate_getter, + r#" +mod foo { + mod bar { + struct Context { + dat$0a: Data, + } + } +} +"#, + r#" +mod foo { + mod bar { + struct Context { + data: Data, + } + + impl Context { + fn $0data(&self) -> &Data { + &self.data + } + } + } +} +"#, + ); + } + #[test] fn test_generate_getter_from_field_no_snippet_cap() { check_assist_no_snippet_cap( @@ -651,6 +687,49 @@ impl Context { ); } + #[test] + fn test_multiple_generate_getter_with_indent() { + check_assist( + generate_getter, + r#" +mod foo { + mod bar { + struct Context { + data: Data, + cou$0nt: usize, + } + + impl Context { + fn data(&self) -> &Data { + &self.data + } + } + } +} +"#, + r#" +mod foo { + mod bar { + struct Context { + data: Data, + count: usize, + } + + impl Context { + fn data(&self) -> &Data { + &self.data + } + + fn $0count(&self) -> &usize { + &self.count + } + } + } +} +"#, + ); + } + #[test] fn test_multiple_generate_getter_no_snippet_cap() { check_assist_no_snippet_cap( diff --git a/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs index af9c493b4804..160fe2d285ca 100644 --- a/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs +++ b/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs @@ -1,12 +1,13 @@ use hir::{HasSource, Name, sym}; use syntax::{ AstNode, - ast::{self, HasName}, + ast::{self, HasName, edit_in_place::Indent}, }; use crate::{ AssistId, assist_context::{AssistContext, Assists}, + utils::indent_string, }; // Assist: generate_is_empty_from_len @@ -75,12 +76,11 @@ pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext< |builder| { let code = r#" - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - }"# - .to_owned(); - builder.insert(range.end(), code) +#[must_use] +pub fn is_empty(&self) -> bool { + self.len() == 0 +}"#; + builder.insert(range.end(), indent_string(code, fn_node.indent_level())) }, ) } @@ -212,6 +212,46 @@ impl MyStruct { ); } + #[test] + fn generate_is_empty_with_indent() { + check_assist( + generate_is_empty_from_len, + r#" +mod foo { + mod bar { + struct MyStruct { data: Vec } + + impl MyStruct { + #[must_use] + p$0ub fn len(&self) -> usize { + self.data.len() + } + } + } +} +"#, + r#" +mod foo { + mod bar { + struct MyStruct { data: Vec } + + impl MyStruct { + #[must_use] + pub fn len(&self) -> usize { + self.data.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + } + } +} +"#, + ); + } + #[test] fn multiple_functions_in_impl() { check_assist( diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index ef6914fda1d5..2e15d431c551 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -1,5 +1,7 @@ //! Assorted functions shared by several assists. +use std::borrow::Cow; + pub(crate) use gen_trait_fn_body::gen_trait_fn_body; use hir::{ DisplayTarget, HasAttrs as HirHasAttrs, HirDisplay, InFile, ModuleDef, PathResolution, @@ -250,6 +252,41 @@ pub fn add_trait_assoc_items_to_impl( first_item.unwrap() } +pub(crate) fn indent_string<'a, S>(s: S, indent_level: IndentLevel) -> Cow<'a, str> +where + Cow<'a, str>: From, +{ + let s = Cow::from(s); + if indent_level.is_zero() || s.is_empty() { + return s; + } + let mut s = &*s; + let expect_indent_len = indent_level.0 as usize * 5 * 4; + + let indent = indent_level.to_string(); + let mut buf = String::with_capacity(s.len() + expect_indent_len); + + if !s.starts_with('\n') { + buf.push_str(&indent); + } + + while let Some((line, rest)) = s.split_once('\n') { + buf.push_str(line); + buf.push('\n'); + + let rest_line = rest.split_once('\n').map_or(rest, |s| s.0); + if !rest_line.is_empty() { + buf.push_str(&indent); + } + + s = rest; + } + + buf.push_str(s); + + buf.into() +} + pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { node.children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) @@ -544,12 +581,12 @@ fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool { // FIXME: this partially overlaps with `find_struct_impl` pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option { buf.push('\n'); - let end = impl_def - .assoc_item_list() - .and_then(|it| it.r_curly_token())? - .prev_sibling_or_token()? - .text_range() - .end(); + let mut end_token = + impl_def.assoc_item_list().and_then(|it| it.r_curly_token())?.prev_sibling_or_token()?; + while end_token.kind() == WHITESPACE { + end_token = end_token.prev_sibling_or_token()? + } + let end = end_token.text_range().end(); Some(end) } @@ -763,20 +800,20 @@ pub(crate) fn add_method_to_adt( impl_def: Option, method: &str, ) { - let mut buf = String::with_capacity(method.len() + 2); + let mut buf = Cow::from(String::with_capacity(method.len() + 2)); if impl_def.is_some() { - buf.push('\n'); + buf.to_mut().push('\n'); } - buf.push_str(method); - let start_offset = impl_def - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) - .unwrap_or_else(|| { - buf = generate_impl_text(adt, &buf); - adt.syntax().text_range().end() - }); + let found_offset = impl_def.and_then(|impl_def| find_impl_block_end(impl_def, buf.to_mut())); + + buf.to_mut().push_str(method); + let start_offset = found_offset.unwrap_or_else(|| { + buf = generate_impl_text(adt, &buf).into(); + adt.syntax().text_range().end() + }); - builder.insert(start_offset, buf); + builder.insert(start_offset, indent_string(buf, AstNodeEdit::indent_level(adt))); } #[derive(Debug)]