|
1 | 1 | use ide_db::famous_defs::FamousDefs;
|
2 | 2 | use stdx::{format_to, to_lower_snake_case};
|
3 |
| -use syntax::ast::{self, AstNode, HasName, HasVisibility}; |
| 3 | +use syntax::{ |
| 4 | + ast::{self, AstNode, HasName, HasVisibility}, |
| 5 | + TextRange, |
| 6 | +}; |
4 | 7 |
|
5 | 8 | use crate::{
|
6 | 9 | utils::{convert_reference_type, find_impl_block_end, find_struct_impl, generate_impl_text},
|
@@ -72,88 +75,259 @@ pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
72 | 75 | generate_getter_impl(acc, ctx, true)
|
73 | 76 | }
|
74 | 77 |
|
| 78 | +#[derive(Clone, Debug)] |
| 79 | +struct RecordFieldInfo { |
| 80 | + field_name: syntax::ast::Name, |
| 81 | + field_ty: syntax::ast::Type, |
| 82 | + fn_name: String, |
| 83 | + target: TextRange, |
| 84 | +} |
| 85 | + |
| 86 | +struct GetterInfo { |
| 87 | + impl_def: Option<ast::Impl>, |
| 88 | + strukt: ast::Struct, |
| 89 | + mutable: bool, |
| 90 | +} |
| 91 | + |
75 | 92 | pub(crate) fn generate_getter_impl(
|
76 | 93 | acc: &mut Assists,
|
77 | 94 | ctx: &AssistContext<'_>,
|
78 | 95 | mutable: bool,
|
79 | 96 | ) -> Option<()> {
|
80 |
| - let strukt = ctx.find_node_at_offset::<ast::Struct>()?; |
81 |
| - let field = ctx.find_node_at_offset::<ast::RecordField>()?; |
| 97 | + // This if condition denotes two modes this assist can work in: |
| 98 | + // - First is acting upon selection of record fields |
| 99 | + // - Next is acting upon a single record field |
| 100 | + // |
| 101 | + // This is the only part where implementation diverges a bit, |
| 102 | + // subsequent code is generic for both of these modes |
82 | 103 |
|
83 |
| - let field_name = field.name()?; |
84 |
| - let field_ty = field.ty()?; |
| 104 | + let (strukt, info_of_record_fields, fn_names) = if !ctx.has_empty_selection() { |
| 105 | + // Selection Mode |
| 106 | + let node = ctx.covering_element(); |
85 | 107 |
|
86 |
| - // Return early if we've found an existing fn |
87 |
| - let mut fn_name = to_lower_snake_case(&field_name.to_string()); |
88 |
| - if mutable { |
89 |
| - format_to!(fn_name, "_mut"); |
| 108 | + let node = match node { |
| 109 | + syntax::NodeOrToken::Node(n) => n, |
| 110 | + syntax::NodeOrToken::Token(t) => t.parent()?, |
| 111 | + }; |
| 112 | + |
| 113 | + let parent_struct = node.ancestors().find_map(ast::Struct::cast)?; |
| 114 | + |
| 115 | + let (info_of_record_fields, field_names) = |
| 116 | + extract_and_parse_record_fields(&parent_struct, ctx.selection_trimmed(), mutable)?; |
| 117 | + |
| 118 | + (parent_struct, info_of_record_fields, field_names) |
| 119 | + } else { |
| 120 | + // Single Record Field mode |
| 121 | + let strukt = ctx.find_node_at_offset::<ast::Struct>()?; |
| 122 | + let field = ctx.find_node_at_offset::<ast::RecordField>()?; |
| 123 | + |
| 124 | + let record_field_info = parse_record_field(field, mutable)?; |
| 125 | + |
| 126 | + let fn_name = record_field_info.fn_name.clone(); |
| 127 | + |
| 128 | + (strukt, vec![record_field_info], vec![fn_name]) |
| 129 | + }; |
| 130 | + |
| 131 | + // No record fields to do work on :( |
| 132 | + if info_of_record_fields.len() == 0 { |
| 133 | + return None; |
90 | 134 | }
|
91 |
| - let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; |
| 135 | + |
| 136 | + let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &fn_names)?; |
92 | 137 |
|
93 | 138 | let (id, label) = if mutable {
|
94 | 139 | ("generate_getter_mut", "Generate a mut getter method")
|
95 | 140 | } else {
|
96 | 141 | ("generate_getter", "Generate a getter method")
|
97 | 142 | };
|
98 |
| - let target = field.syntax().text_range(); |
| 143 | + |
| 144 | + // Computing collective text range of all record fields in selected region |
| 145 | + let target: TextRange = info_of_record_fields |
| 146 | + .iter() |
| 147 | + .map(|record_field_info| record_field_info.target) |
| 148 | + .reduce(|acc, target| acc.cover(target))?; |
| 149 | + |
| 150 | + let getter_info = GetterInfo { impl_def, strukt, mutable }; |
| 151 | + |
99 | 152 | acc.add_group(
|
100 | 153 | &GroupLabel("Generate getter/setter".to_owned()),
|
101 | 154 | AssistId(id, AssistKind::Generate),
|
102 | 155 | label,
|
103 | 156 | target,
|
104 | 157 | |builder| {
|
| 158 | + let record_fields_count = info_of_record_fields.len(); |
| 159 | + |
105 | 160 | let mut buf = String::with_capacity(512);
|
106 | 161 |
|
107 |
| - if impl_def.is_some() { |
108 |
| - buf.push('\n'); |
| 162 | + // Check if an impl exists |
| 163 | + if let Some(impl_def) = &getter_info.impl_def { |
| 164 | + // Check if impl is empty |
| 165 | + if let Some(assoc_item_list) = impl_def.assoc_item_list() { |
| 166 | + if assoc_item_list.assoc_items().next().is_some() { |
| 167 | + // If not empty then only insert a new line |
| 168 | + buf.push('\n'); |
| 169 | + } |
| 170 | + } |
109 | 171 | }
|
110 | 172 |
|
111 |
| - let vis = strukt.visibility().map_or(String::new(), |v| format!("{v} ")); |
112 |
| - let (ty, body) = if mutable { |
113 |
| - (format!("&mut {field_ty}"), format!("&mut self.{field_name}")) |
114 |
| - } else { |
115 |
| - (|| { |
116 |
| - let krate = ctx.sema.scope(field_ty.syntax())?.krate(); |
117 |
| - let famous_defs = &FamousDefs(&ctx.sema, krate); |
118 |
| - ctx.sema |
119 |
| - .resolve_type(&field_ty) |
120 |
| - .and_then(|ty| convert_reference_type(ty, ctx.db(), famous_defs)) |
121 |
| - .map(|conversion| { |
122 |
| - cov_mark::hit!(convert_reference_type); |
123 |
| - ( |
124 |
| - conversion.convert_type(ctx.db()), |
125 |
| - conversion.getter(field_name.to_string()), |
126 |
| - ) |
127 |
| - }) |
128 |
| - })() |
129 |
| - .unwrap_or_else(|| (format!("&{field_ty}"), format!("&self.{field_name}"))) |
130 |
| - }; |
131 |
| - |
132 |
| - let mut_ = mutable.then(|| "mut ").unwrap_or_default(); |
133 |
| - format_to!( |
134 |
| - buf, |
135 |
| - " {vis}fn {fn_name}(&{mut_}self) -> {ty} {{ |
136 |
| - {body} |
137 |
| - }}" |
138 |
| - ); |
139 |
| - |
140 |
| - let start_offset = impl_def |
141 |
| - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) |
| 173 | + for (i, record_field_info) in info_of_record_fields.iter().enumerate() { |
| 174 | + // this buf inserts a newline at the end of a getter |
| 175 | + // automatically, if one wants to add one more newline |
| 176 | + // for separating it from other assoc items, that needs |
| 177 | + // to be handled spearately |
| 178 | + let mut getter_buf = |
| 179 | + generate_getter_from_info(ctx, &getter_info, &record_field_info); |
| 180 | + |
| 181 | + // Insert `$0` only for last getter we generate |
| 182 | + if i == record_fields_count - 1 { |
| 183 | + getter_buf = getter_buf.replacen("fn ", "fn $0", 1); |
| 184 | + } |
| 185 | + |
| 186 | + // For first element we do not merge with '\n', as |
| 187 | + // that can be inserted by impl_def check defined |
| 188 | + // above, for other cases which are: |
| 189 | + // |
| 190 | + // - impl exists but it empty, here we would ideally |
| 191 | + // not want to keep newline between impl <struct> { |
| 192 | + // and fn <fn-name>() { line |
| 193 | + // |
| 194 | + // - next if impl itself does not exist, in this |
| 195 | + // case we ourselves generate a new impl and that |
| 196 | + // again ends up with the same reasoning as above |
| 197 | + // for not keeping newline |
| 198 | + if i == 0 { |
| 199 | + buf = buf + &getter_buf; |
| 200 | + } else { |
| 201 | + buf = buf + "\n" + &getter_buf; |
| 202 | + } |
| 203 | + |
| 204 | + // We don't insert a new line at the end of |
| 205 | + // last getter as it will end up in the end |
| 206 | + // of an impl where we would not like to keep |
| 207 | + // getter and end of impl ( i.e. `}` ) with an |
| 208 | + // extra line for no reason |
| 209 | + if i < record_fields_count - 1 { |
| 210 | + buf = buf + "\n"; |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + let start_offset = getter_info |
| 215 | + .impl_def |
| 216 | + .as_ref() |
| 217 | + .and_then(|impl_def| find_impl_block_end(impl_def.to_owned(), &mut buf)) |
142 | 218 | .unwrap_or_else(|| {
|
143 |
| - buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); |
144 |
| - strukt.syntax().text_range().end() |
| 219 | + buf = generate_impl_text(&ast::Adt::Struct(getter_info.strukt.clone()), &buf); |
| 220 | + getter_info.strukt.syntax().text_range().end() |
145 | 221 | });
|
146 | 222 |
|
147 | 223 | match ctx.config.snippet_cap {
|
148 |
| - Some(cap) => { |
149 |
| - builder.insert_snippet(cap, start_offset, buf.replacen("fn ", "fn $0", 1)) |
150 |
| - } |
| 224 | + Some(cap) => builder.insert_snippet(cap, start_offset, buf), |
151 | 225 | None => builder.insert(start_offset, buf),
|
152 | 226 | }
|
153 | 227 | },
|
154 | 228 | )
|
155 | 229 | }
|
156 | 230 |
|
| 231 | +fn generate_getter_from_info( |
| 232 | + ctx: &AssistContext<'_>, |
| 233 | + info: &GetterInfo, |
| 234 | + record_field_info: &RecordFieldInfo, |
| 235 | +) -> String { |
| 236 | + let mut buf = String::with_capacity(512); |
| 237 | + |
| 238 | + let vis = info.strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); |
| 239 | + let (ty, body) = if info.mutable { |
| 240 | + ( |
| 241 | + format!("&mut {}", record_field_info.field_ty), |
| 242 | + format!("&mut self.{}", record_field_info.field_name), |
| 243 | + ) |
| 244 | + } else { |
| 245 | + (|| { |
| 246 | + let krate = ctx.sema.scope(record_field_info.field_ty.syntax())?.krate(); |
| 247 | + let famous_defs = &FamousDefs(&ctx.sema, krate); |
| 248 | + ctx.sema |
| 249 | + .resolve_type(&record_field_info.field_ty) |
| 250 | + .and_then(|ty| convert_reference_type(ty, ctx.db(), famous_defs)) |
| 251 | + .map(|conversion| { |
| 252 | + cov_mark::hit!(convert_reference_type); |
| 253 | + ( |
| 254 | + conversion.convert_type(ctx.db()), |
| 255 | + conversion.getter(record_field_info.field_name.to_string()), |
| 256 | + ) |
| 257 | + }) |
| 258 | + })() |
| 259 | + .unwrap_or_else(|| { |
| 260 | + ( |
| 261 | + format!("&{}", record_field_info.field_ty), |
| 262 | + format!("&self.{}", record_field_info.field_name), |
| 263 | + ) |
| 264 | + }) |
| 265 | + }; |
| 266 | + |
| 267 | + format_to!( |
| 268 | + buf, |
| 269 | + " {}fn {}(&{}self) -> {} {{ |
| 270 | + {} |
| 271 | + }}", |
| 272 | + vis, |
| 273 | + record_field_info.fn_name, |
| 274 | + info.mutable.then(|| "mut ").unwrap_or_default(), |
| 275 | + ty, |
| 276 | + body, |
| 277 | + ); |
| 278 | + |
| 279 | + buf |
| 280 | +} |
| 281 | + |
| 282 | +fn extract_and_parse_record_fields( |
| 283 | + node: &ast::Struct, |
| 284 | + selection_range: TextRange, |
| 285 | + mutable: bool, |
| 286 | +) -> Option<(Vec<RecordFieldInfo>, Vec<String>)> { |
| 287 | + let mut field_names: Vec<String> = vec![]; |
| 288 | + let field_list = node.field_list()?; |
| 289 | + |
| 290 | + match field_list { |
| 291 | + ast::FieldList::RecordFieldList(ele) => { |
| 292 | + let info_of_record_fields_in_selection = ele |
| 293 | + .fields() |
| 294 | + .filter_map(|record_field| { |
| 295 | + if selection_range.contains_range(record_field.syntax().text_range()) { |
| 296 | + let record_field_info = parse_record_field(record_field, mutable)?; |
| 297 | + field_names.push(record_field_info.fn_name.clone()); |
| 298 | + return Some(record_field_info); |
| 299 | + } |
| 300 | + |
| 301 | + None |
| 302 | + }) |
| 303 | + .collect::<Vec<RecordFieldInfo>>(); |
| 304 | + |
| 305 | + if info_of_record_fields_in_selection.len() == 0 { |
| 306 | + return None; |
| 307 | + } |
| 308 | + |
| 309 | + Some((info_of_record_fields_in_selection, field_names)) |
| 310 | + } |
| 311 | + ast::FieldList::TupleFieldList(_) => { |
| 312 | + return None; |
| 313 | + } |
| 314 | + } |
| 315 | +} |
| 316 | + |
| 317 | +fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option<RecordFieldInfo> { |
| 318 | + let field_name = record_field.name()?; |
| 319 | + let field_ty = record_field.ty()?; |
| 320 | + |
| 321 | + let mut fn_name = to_lower_snake_case(&field_name.to_string()); |
| 322 | + if mutable { |
| 323 | + format_to!(fn_name, "_mut"); |
| 324 | + } |
| 325 | + |
| 326 | + let target = record_field.syntax().text_range(); |
| 327 | + |
| 328 | + Some(RecordFieldInfo { field_name, field_ty, fn_name, target }) |
| 329 | +} |
| 330 | + |
157 | 331 | #[cfg(test)]
|
158 | 332 | mod tests {
|
159 | 333 | use crate::tests::{check_assist, check_assist_not_applicable};
|
@@ -485,4 +659,53 @@ impl Context {
|
485 | 659 | "#,
|
486 | 660 | );
|
487 | 661 | }
|
| 662 | + |
| 663 | + #[test] |
| 664 | + fn test_generate_multiple_getters_from_selection() { |
| 665 | + check_assist( |
| 666 | + generate_getter, |
| 667 | + r#" |
| 668 | +struct Context { |
| 669 | + $0data: Data, |
| 670 | + count: usize,$0 |
| 671 | +} |
| 672 | + "#, |
| 673 | + r#" |
| 674 | +struct Context { |
| 675 | + data: Data, |
| 676 | + count: usize, |
| 677 | +} |
| 678 | +
|
| 679 | +impl Context { |
| 680 | + fn data(&self) -> &Data { |
| 681 | + &self.data |
| 682 | + } |
| 683 | +
|
| 684 | + fn $0count(&self) -> &usize { |
| 685 | + &self.count |
| 686 | + } |
| 687 | +} |
| 688 | + "#, |
| 689 | + ); |
| 690 | + } |
| 691 | + |
| 692 | + #[test] |
| 693 | + fn test_generate_multiple_getters_from_selection_one_already_exists() { |
| 694 | + // As impl for one of the fields already exist, skip it |
| 695 | + check_assist_not_applicable( |
| 696 | + generate_getter, |
| 697 | + r#" |
| 698 | +struct Context { |
| 699 | + $0data: Data, |
| 700 | + count: usize,$0 |
| 701 | +} |
| 702 | +
|
| 703 | +impl Context { |
| 704 | + fn data(&self) -> &Data { |
| 705 | + &self.data |
| 706 | + } |
| 707 | +} |
| 708 | + "#, |
| 709 | + ); |
| 710 | + } |
488 | 711 | }
|
0 commit comments