aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs176
1 files changed, 91 insertions, 85 deletions
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index 26e1c66ab..1f800f82b 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -15,7 +15,7 @@ use rustc_hash::FxHashSet;
15use syntax::{ 15use syntax::{
16 algo::find_node_at_offset, 16 algo::find_node_at_offset,
17 ast::{self, make, AstNode, NameOwner, VisibilityOwner}, 17 ast::{self, make, AstNode, NameOwner, VisibilityOwner},
18 ted, SourceFile, SyntaxElement, SyntaxNode, T, 18 ted, SyntaxNode, T,
19}; 19};
20 20
21use crate::{AssistContext, AssistId, AssistKind, Assists}; 21use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,6 +62,7 @@ pub(crate) fn extract_struct_from_enum_variant(
62 let mut visited_modules_set = FxHashSet::default(); 62 let mut visited_modules_set = FxHashSet::default();
63 let current_module = enum_hir.module(ctx.db()); 63 let current_module = enum_hir.module(ctx.db());
64 visited_modules_set.insert(current_module); 64 visited_modules_set.insert(current_module);
65 // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
65 let mut def_file_references = None; 66 let mut def_file_references = None;
66 for (file_id, references) in usages { 67 for (file_id, references) in usages {
67 if file_id == ctx.frange.file_id { 68 if file_id == ctx.frange.file_id {
@@ -70,36 +71,57 @@ pub(crate) fn extract_struct_from_enum_variant(
70 } 71 }
71 builder.edit_file(file_id); 72 builder.edit_file(file_id);
72 let source_file = builder.make_ast_mut(ctx.sema.parse(file_id)); 73 let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
73 for reference in references { 74 let processed = process_references(
74 update_reference( 75 ctx,
75 ctx, 76 &mut visited_modules_set,
76 reference, 77 source_file.syntax(),
77 &source_file, 78 &enum_module_def,
78 &enum_module_def, 79 &variant_hir_name,
79 &variant_hir_name, 80 references,
80 &mut visited_modules_set, 81 );
82 processed.into_iter().for_each(|(segment, node, import)| {
83 if let Some((scope, path)) = import {
84 insert_use(&scope, mod_path_to_ast(&path), ctx.config.insert_use);
85 }
86 ted::insert_raw(
87 ted::Position::before(segment.syntax()),
88 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
81 ); 89 );
82 } 90 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
91 ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
92 });
83 } 93 }
84 builder.edit_file(ctx.frange.file_id); 94 builder.edit_file(ctx.frange.file_id);
85 let variant = builder.make_ast_mut(variant.clone());
86 let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id)); 95 let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
87 for reference in def_file_references.into_iter().flatten() { 96 let variant = builder.make_ast_mut(variant.clone());
88 update_reference( 97 if let Some(references) = def_file_references {
98 let processed = process_references(
89 ctx, 99 ctx,
90 reference, 100 &mut visited_modules_set,
91 &source_file, 101 source_file.syntax(),
92 &enum_module_def, 102 &enum_module_def,
93 &variant_hir_name, 103 &variant_hir_name,
94 &mut visited_modules_set, 104 references,
95 ); 105 );
106 processed.into_iter().for_each(|(segment, node, import)| {
107 if let Some((scope, path)) = import {
108 insert_use(&scope, mod_path_to_ast(&path), ctx.config.insert_use);
109 }
110 ted::insert_raw(
111 ted::Position::before(segment.syntax()),
112 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
113 );
114 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
115 ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
116 });
96 } 117 }
97 extract_struct_def( 118
98 variant_name.clone(), 119 let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility())
99 &field_list, 120 .unwrap();
100 &variant.parent_enum().syntax().clone().into(), 121 let start_offset = &variant.parent_enum().syntax().clone();
101 enum_ast.visibility(), 122 ted::insert_raw(ted::Position::before(start_offset), def.syntax());
102 ); 123 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
124
103 update_variant(&variant); 125 update_variant(&variant);
104 }, 126 },
105 ) 127 )
@@ -141,31 +163,11 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
141 .any(|(name, _)| name.to_string() == variant_name.to_string()) 163 .any(|(name, _)| name.to_string() == variant_name.to_string())
142} 164}
143 165
144fn insert_import( 166fn create_struct_def(
145 ctx: &AssistContext,
146 scope_node: &SyntaxNode,
147 module: &Module,
148 enum_module_def: &ModuleDef,
149 variant_hir_name: &Name,
150) -> Option<()> {
151 let db = ctx.db();
152 let mod_path =
153 module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind);
154 if let Some(mut mod_path) = mod_path {
155 mod_path.pop_segment();
156 mod_path.push_segment(variant_hir_name.clone());
157 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
158 insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
159 }
160 Some(())
161}
162
163fn extract_struct_def(
164 variant_name: ast::Name, 167 variant_name: ast::Name,
165 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, 168 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
166 start_offset: &SyntaxElement,
167 visibility: Option<ast::Visibility>, 169 visibility: Option<ast::Visibility>,
168) -> Option<()> { 170) -> Option<ast::Struct> {
169 let pub_vis = Some(make::visibility_pub()); 171 let pub_vis = Some(make::visibility_pub());
170 let field_list = match field_list { 172 let field_list = match field_list {
171 Either::Left(field_list) => { 173 Either::Left(field_list) => {
@@ -182,18 +184,7 @@ fn extract_struct_def(
182 .into(), 184 .into(),
183 }; 185 };
184 186
185 ted::insert_raw( 187 Some(make::struct_(visibility, variant_name, None, field_list).clone_for_update())
186 ted::Position::before(start_offset),
187 make::struct_(visibility, variant_name, None, field_list).clone_for_update().syntax(),
188 );
189 ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
190
191 // if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
192 // ted::insert(ted::Position::before(start_offset), &make::tokens::blank_line());
193 // rewriter
194 // .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
195 // }
196 Some(())
197} 188}
198 189
199fn update_variant(variant: &ast::Variant) -> Option<()> { 190fn update_variant(variant: &ast::Variant) -> Option<()> {
@@ -208,42 +199,57 @@ fn update_variant(variant: &ast::Variant) -> Option<()> {
208 Some(()) 199 Some(())
209} 200}
210 201
211fn update_reference( 202fn process_references(
212 ctx: &AssistContext, 203 ctx: &AssistContext,
213 reference: FileReference, 204 visited_modules: &mut FxHashSet<Module>,
214 source_file: &SourceFile, 205 source_file: &SyntaxNode,
215 enum_module_def: &ModuleDef, 206 enum_module_def: &ModuleDef,
216 variant_hir_name: &Name, 207 variant_hir_name: &Name,
217 visited_modules_set: &mut FxHashSet<Module>, 208 refs: Vec<FileReference>,
218) -> Option<()> { 209) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
210 refs.into_iter()
211 .flat_map(|reference| {
212 let (segment, scope_node, module) =
213 reference_to_node(&ctx.sema, source_file, reference)?;
214 if !visited_modules.contains(&module) {
215 let mod_path = module.find_use_path_prefixed(
216 ctx.sema.db,
217 *enum_module_def,
218 ctx.config.insert_use.prefix_kind,
219 );
220 if let Some(mut mod_path) = mod_path {
221 mod_path.pop_segment();
222 mod_path.push_segment(variant_hir_name.clone());
223 // uuuh this wont properly work, find_insert_use_container ascends macros so we might a get new syntax node???
224 let scope = ImportScope::find_insert_use_container(&scope_node, &ctx.sema)?;
225 visited_modules.insert(module);
226 return Some((segment, scope_node, Some((scope, mod_path))));
227 }
228 }
229 Some((segment, scope_node, None))
230 })
231 .collect()
232}
233
234fn reference_to_node(
235 sema: &hir::Semantics<RootDatabase>,
236 source_file: &SyntaxNode,
237 reference: FileReference,
238) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
219 let offset = reference.range.start(); 239 let offset = reference.range.start();
220 let (segment, expr) = if let Some(path_expr) = 240 if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) {
221 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
222 {
223 // tuple variant 241 // tuple variant
224 (path_expr.path()?.segment()?, path_expr.syntax().parent()?) 242 Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?))
225 } else if let Some(record_expr) = 243 } else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) {
226 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
227 {
228 // record variant 244 // record variant
229 (record_expr.path()?.segment()?, record_expr.syntax().clone()) 245 Some((record_expr.path()?.segment()?, record_expr.syntax().clone()))
230 } else { 246 } else {
231 return None; 247 None
232 };
233
234 let module = ctx.sema.scope(&expr).module()?;
235 if !visited_modules_set.contains(&module) {
236 if insert_import(ctx, &expr, &module, enum_module_def, variant_hir_name).is_some() {
237 visited_modules_set.insert(module);
238 }
239 } 248 }
240 ted::insert_raw( 249 .and_then(|(segment, expr)| {
241 ted::Position::before(segment.syntax()), 250 let module = sema.scope(&expr).module()?;
242 make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(), 251 Some((segment, expr, module))
243 ); 252 })
244 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
245 ted::insert_raw(ted::Position::after(&expr), make::token(T![')']));
246 Some(())
247} 253}
248 254
249#[cfg(test)] 255#[cfg(test)]
@@ -350,7 +356,7 @@ mod my_mod {
350 356
351 pub struct MyField(pub u8, pub u8); 357 pub struct MyField(pub u8, pub u8);
352 358
353 pub enum MyEnum { 359pub enum MyEnum {
354 MyField(MyField), 360 MyField(MyField),
355 } 361 }
356 } 362 }