1- use hir:: { EnumVariant , Module , ModuleDef , Name } ;
2- use ide_db:: base_db:: FileId ;
1+ use hir:: { AsName , EnumVariant , Module , ModuleDef , Name } ;
32use ide_db:: { defs:: Definition , search:: Reference , RootDatabase } ;
4- use itertools:: Itertools ;
5- use rustc_hash:: FxHashSet ;
3+ use rustc_hash:: { FxHashMap , FxHashSet } ;
64use syntax:: {
75 algo:: find_node_at_offset,
8- ast:: { self , edit:: IndentLevel , ArgListOwner , AstNode , NameOwner , VisibilityOwner } ,
9- SourceFile , TextRange , TextSize ,
6+ algo:: SyntaxRewriter ,
7+ ast:: { self , edit:: IndentLevel , make, ArgListOwner , AstNode , NameOwner , VisibilityOwner } ,
8+ SourceFile , SyntaxElement ,
109} ;
1110
1211use crate :: {
13- assist_context:: AssistBuilder ,
1412 utils:: { insert_use, mod_path_to_ast, ImportScope } ,
1513 AssistContext , AssistId , AssistKind , Assists ,
1614} ;
@@ -43,7 +41,7 @@ pub(crate) fn extract_struct_from_enum_variant(
4341 return None ;
4442 }
4543
46- let variant_name = variant. name ( ) ?. to_string ( ) ;
44+ let variant_name = variant. name ( ) ?;
4745 let variant_hir = ctx. sema . to_def ( & variant) ?;
4846 if existing_struct_def ( ctx. db ( ) , & variant_name, & variant_hir) {
4947 return None ;
@@ -62,49 +60,58 @@ pub(crate) fn extract_struct_from_enum_variant(
6260 |builder| {
6361 let definition = Definition :: ModuleDef ( ModuleDef :: EnumVariant ( variant_hir) ) ;
6462 let res = definition. usages ( & ctx. sema ) . all ( ) ;
65- let start_offset = variant . parent_enum ( ) . syntax ( ) . text_range ( ) . start ( ) ;
63+
6664 let mut visited_modules_set = FxHashSet :: default ( ) ;
6765 visited_modules_set. insert ( current_module) ;
66+ let mut rewriters = FxHashMap :: default ( ) ;
6867 for reference in res {
68+ let rewriter = rewriters
69+ . entry ( reference. file_range . file_id )
70+ . or_insert_with ( SyntaxRewriter :: default) ;
6971 let source_file = ctx. sema . parse ( reference. file_range . file_id ) ;
7072 update_reference (
7173 ctx,
72- builder ,
74+ rewriter ,
7375 reference,
7476 & source_file,
7577 & enum_module_def,
7678 & variant_hir_name,
7779 & mut visited_modules_set,
7880 ) ;
7981 }
82+ let mut rewriter =
83+ rewriters. remove ( & ctx. frange . file_id ) . unwrap_or_else ( SyntaxRewriter :: default) ;
84+ for ( file_id, rewriter) in rewriters {
85+ builder. edit_file ( file_id) ;
86+ builder. rewrite ( rewriter) ;
87+ }
88+ builder. edit_file ( ctx. frange . file_id ) ;
89+ update_variant ( & mut rewriter, & variant_name, & field_list) ;
8090 extract_struct_def (
81- builder ,
91+ & mut rewriter ,
8292 & enum_ast,
83- & variant_name,
84- & field_list. to_string ( ) ,
85- start_offset,
86- ctx. frange . file_id ,
87- & visibility,
93+ variant_name. clone ( ) ,
94+ & field_list,
95+ & variant. parent_enum ( ) . syntax ( ) . clone ( ) . into ( ) ,
96+ visibility,
8897 ) ;
89- let list_range = field_list. syntax ( ) . text_range ( ) ;
90- update_variant ( builder, & variant_name, ctx. frange . file_id , list_range) ;
98+ builder. rewrite ( rewriter) ;
9199 } ,
92100 )
93101}
94102
95- fn existing_struct_def ( db : & RootDatabase , variant_name : & str , variant : & EnumVariant ) -> bool {
103+ fn existing_struct_def ( db : & RootDatabase , variant_name : & ast :: Name , variant : & EnumVariant ) -> bool {
96104 variant
97105 . parent_enum ( db)
98106 . module ( db)
99107 . scope ( db, None )
100108 . into_iter ( )
101- . any ( |( name, _) | name. to_string ( ) == variant_name)
109+ . any ( |( name, _) | name == variant_name. as_name ( ) )
102110}
103111
104- #[ allow( dead_code) ]
105112fn insert_import (
106113 ctx : & AssistContext ,
107- builder : & mut AssistBuilder ,
114+ rewriter : & mut SyntaxRewriter ,
108115 path : & ast:: PathExpr ,
109116 module : & Module ,
110117 enum_module_def : & ModuleDef ,
@@ -116,69 +123,59 @@ fn insert_import(
116123 mod_path. segments . pop ( ) ;
117124 mod_path. segments . push ( variant_hir_name. clone ( ) ) ;
118125 let scope = ImportScope :: find_insert_use_container ( path. syntax ( ) , ctx) ?;
119- let syntax = scope. as_syntax_node ( ) ;
120126
121- let new_syntax =
122- insert_use ( & scope, mod_path_to_ast ( & mod_path) , ctx. config . insert_use . merge ) ;
123- // FIXME: this will currently panic as multiple imports will have overlapping text ranges
124- builder. replace ( syntax. text_range ( ) , new_syntax. to_string ( ) )
127+ * rewriter += insert_use ( & scope, mod_path_to_ast ( & mod_path) , ctx. config . insert_use . merge ) ;
125128 }
126129 Some ( ( ) )
127130}
128131
129- // FIXME: this should use strongly-typed `make`, rather than string manipulation.
130132fn extract_struct_def (
131- builder : & mut AssistBuilder ,
133+ rewriter : & mut SyntaxRewriter ,
132134 enum_ : & ast:: Enum ,
133- variant_name : & str ,
134- variant_list : & str ,
135- start_offset : TextSize ,
136- file_id : FileId ,
137- visibility : & Option < ast:: Visibility > ,
135+ variant_name : ast:: Name ,
136+ variant_list : & ast:: TupleFieldList ,
137+ start_offset : & SyntaxElement ,
138+ visibility : Option < ast:: Visibility > ,
138139) -> Option < ( ) > {
139- let visibility_string = if let Some ( visibility) = visibility {
140- format ! ( "{} " , visibility. to_string( ) )
141- } else {
142- "" . to_string ( )
143- } ;
144- let indent = IndentLevel :: from_node ( enum_. syntax ( ) ) ;
145- let struct_def = format ! (
146- r#"{}struct {}{};
147-
148- {}"# ,
149- visibility_string,
150- variant_name,
151- list_with_visibility( variant_list) ,
152- indent
140+ let variant_list = make:: tuple_field_list (
141+ variant_list
142+ . fields ( )
143+ . flat_map ( |field| Some ( make:: tuple_field ( Some ( make:: visibility_pub ( ) ) , field. ty ( ) ?) ) ) ,
153144 ) ;
154- builder. edit_file ( file_id) ;
155- builder. insert ( start_offset, struct_def) ;
145+
146+ rewriter. insert_before (
147+ start_offset,
148+ make:: struct_ ( visibility, variant_name, None , variant_list. into ( ) ) . syntax ( ) ,
149+ ) ;
150+ rewriter. insert_before ( start_offset, & make:: tokens:: blank_line ( ) ) ;
151+
152+ if let indent_level @ 1 ..=usize:: MAX = IndentLevel :: from_node ( enum_. syntax ( ) ) . 0 as usize {
153+ rewriter
154+ . insert_before ( start_offset, & make:: tokens:: whitespace ( & " " . repeat ( 4 * indent_level) ) ) ;
155+ }
156156 Some ( ( ) )
157157}
158158
159159fn update_variant (
160- builder : & mut AssistBuilder ,
161- variant_name : & str ,
162- file_id : FileId ,
163- list_range : TextRange ,
160+ rewriter : & mut SyntaxRewriter ,
161+ variant_name : & ast:: Name ,
162+ field_list : & ast:: TupleFieldList ,
164163) -> Option < ( ) > {
165- let inside_variant_range = TextRange :: new (
166- list_range. start ( ) . checked_add ( TextSize :: from ( 1 ) ) ?,
167- list_range. end ( ) . checked_sub ( TextSize :: from ( 1 ) ) ?,
168- ) ;
169- builder. edit_file ( file_id) ;
170- builder. replace ( inside_variant_range, variant_name) ;
164+ let ( l, r) : ( SyntaxElement , SyntaxElement ) =
165+ ( field_list. l_paren_token ( ) ?. into ( ) , field_list. r_paren_token ( ) ?. into ( ) ) ;
166+ let replacement = vec ! [ l, variant_name. syntax( ) . clone( ) . into( ) , r] ;
167+ rewriter. replace_with_many ( field_list. syntax ( ) , replacement) ;
171168 Some ( ( ) )
172169}
173170
174171fn update_reference (
175172 ctx : & AssistContext ,
176- builder : & mut AssistBuilder ,
173+ rewriter : & mut SyntaxRewriter ,
177174 reference : Reference ,
178175 source_file : & SourceFile ,
179- _enum_module_def : & ModuleDef ,
180- _variant_hir_name : & Name ,
181- _visited_modules_set : & mut FxHashSet < Module > ,
176+ enum_module_def : & ModuleDef ,
177+ variant_hir_name : & Name ,
178+ visited_modules_set : & mut FxHashSet < Module > ,
182179) -> Option < ( ) > {
183180 let path_expr: ast:: PathExpr = find_node_at_offset :: < ast:: PathExpr > (
184181 source_file. syntax ( ) ,
@@ -187,35 +184,21 @@ fn update_reference(
187184 let call = path_expr. syntax ( ) . parent ( ) . and_then ( ast:: CallExpr :: cast) ?;
188185 let list = call. arg_list ( ) ?;
189186 let segment = path_expr. path ( ) ?. segment ( ) ?;
190- let _module = ctx. sema . scope ( & path_expr. syntax ( ) ) . module ( ) ?;
191- let list_range = list. syntax ( ) . text_range ( ) ;
192- let inside_list_range = TextRange :: new (
193- list_range. start ( ) . checked_add ( TextSize :: from ( 1 ) ) ?,
194- list_range. end ( ) . checked_sub ( TextSize :: from ( 1 ) ) ?,
195- ) ;
196- builder. edit_file ( reference. file_range . file_id ) ;
197- /* FIXME: this most likely requires AST-based editing, see `insert_import`
187+ let module = ctx. sema . scope ( & path_expr. syntax ( ) ) . module ( ) ?;
198188 if !visited_modules_set. contains ( & module) {
199- if insert_import(ctx, builder , &path_expr, &module, enum_module_def, variant_hir_name)
189+ if insert_import ( ctx, rewriter , & path_expr, & module, enum_module_def, variant_hir_name)
200190 . is_some ( )
201191 {
202192 visited_modules_set. insert ( module) ;
203193 }
204194 }
205- */
206- builder. replace ( inside_list_range, format ! ( "{}{}" , segment, list) ) ;
207- Some ( ( ) )
208- }
209195
210- fn list_with_visibility ( list : & str ) -> String {
211- list. split ( ',' )
212- . map ( |part| {
213- let index = if part. chars ( ) . next ( ) . unwrap ( ) == '(' { 1usize } else { 0 } ;
214- let mut mod_part = part. trim ( ) . to_string ( ) ;
215- mod_part. insert_str ( index, "pub " ) ;
216- mod_part
217- } )
218- . join ( ", " )
196+ let lparen = syntax:: SyntaxElement :: from ( list. l_paren_token ( ) ?) ;
197+ let rparen = syntax:: SyntaxElement :: from ( list. r_paren_token ( ) ?) ;
198+ rewriter. insert_after ( & lparen, segment. syntax ( ) ) ;
199+ rewriter. insert_after ( & lparen, & lparen) ;
200+ rewriter. insert_before ( & rparen, & rparen) ;
201+ Some ( ( ) )
219202}
220203
221204#[ cfg( test) ]
@@ -250,7 +233,6 @@ pub enum A { One(One) }"#,
250233 }
251234
252235 #[ test]
253- #[ ignore] // FIXME: this currently panics if `insert_import` is used
254236 fn test_extract_struct_with_complex_imports ( ) {
255237 check_assist (
256238 extract_struct_from_enum_variant,
0 commit comments