@@ -4,14 +4,14 @@ use ide_db::{
44 syntax_helpers:: { LexedStr , suggest_name} ,
55} ;
66use syntax:: {
7- NodeOrToken , SyntaxKind , SyntaxNode , T ,
8- algo:: ancestors_at_offset,
7+ Direction , NodeOrToken , SyntaxKind , SyntaxNode , SyntaxToken , T , TextRange ,
8+ algo:: { ancestors_at_offset, skip_trivia_token } ,
99 ast:: {
1010 self , AstNode ,
1111 edit:: { AstNodeEdit , IndentLevel } ,
1212 syntax_factory:: SyntaxFactory ,
1313 } ,
14- syntax_editor:: Position ,
14+ syntax_editor:: { Element , Position } ,
1515} ;
1616
1717use crate :: { AssistContext , AssistId , Assists , utils:: is_body_const} ;
@@ -92,27 +92,54 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
9292 let node = node. ancestors ( ) . take_while ( |anc| anc. text_range ( ) == node. text_range ( ) ) . last ( ) ?;
9393 let range = node. text_range ( ) ;
9494
95- let to_extract = node
96- . descendants ( )
97- . take_while ( |it| range. contains_range ( it. text_range ( ) ) )
98- . find_map ( valid_target_expr ( ctx) ) ?;
95+ let ( to_replace, analysis) = if node. kind ( ) == SyntaxKind :: TOKEN_TREE {
96+ let ( first, last) = extract_token_range_of ( & node, ctx. selection_trimmed ( ) ) ?;
9997
100- let ty = ctx. sema . type_of_expr ( & to_extract) . map ( TypeInfo :: adjusted) ;
98+ let first_descend = ctx. sema . descend_into_macros_single_exact ( first. clone ( ) ) ;
99+ let last_descend = ctx. sema . descend_into_macros_single_exact ( last. clone ( ) ) ;
100+ let range = first_descend. text_range ( ) . cover ( last_descend. text_range ( ) ) ;
101+
102+ if first_descend. parent_ancestors ( ) . last ( ) != last_descend. parent_ancestors ( ) . last ( ) {
103+ return None ;
104+ }
105+
106+ let expr = first_descend
107+ . parent_ancestors ( )
108+ . skip_while ( |it| !it. text_range ( ) . contains_range ( range) )
109+ . find_map ( valid_target_expr ( ctx) ) ?;
110+ let original_range = ctx. sema . original_range ( expr. syntax ( ) ) ;
111+ let ( first, last) = extract_token_range_of ( & node, original_range. range ) ?;
112+ let to_extract = first. syntax_element ( ) ..=last. syntax_element ( ) ;
113+ ( to_extract, expr)
114+ } else {
115+ let expr = node
116+ . descendants ( )
117+ . take_while ( |it| range. contains_range ( it. text_range ( ) ) )
118+ . find_map ( valid_target_expr ( ctx) ) ?;
119+ let to_extract = expr. syntax ( ) . syntax_element ( ) ;
120+ ( to_extract. clone ( ) ..=to_extract, expr)
121+ } ;
122+ let place = match to_replace. start ( ) {
123+ NodeOrToken :: Node ( node) => node. clone ( ) ,
124+ NodeOrToken :: Token ( t) => t. parent ( ) ?,
125+ } ;
126+
127+ let ty = ctx. sema . type_of_expr ( & analysis) . map ( TypeInfo :: adjusted) ;
101128 if matches ! ( & ty, Some ( ty_info) if ty_info. is_unit( ) ) {
102129 return None ;
103130 }
104131
105- let parent = to_extract . syntax ( ) . parent ( ) . and_then ( ast:: Expr :: cast) ;
132+ let parent = analysis . syntax ( ) . parent ( ) . and_then ( ast:: Expr :: cast) ;
106133 // Any expression that autoderefs may need adjustment.
107134 let mut needs_adjust = parent. as_ref ( ) . is_some_and ( |it| match it {
108135 ast:: Expr :: FieldExpr ( _)
109136 | ast:: Expr :: MethodCallExpr ( _)
110137 | ast:: Expr :: CallExpr ( _)
111138 | ast:: Expr :: AwaitExpr ( _) => true ,
112- ast:: Expr :: IndexExpr ( index) if index. base ( ) . as_ref ( ) == Some ( & to_extract ) => true ,
139+ ast:: Expr :: IndexExpr ( index) if index. base ( ) . as_ref ( ) == Some ( & analysis ) => true ,
113140 _ => false ,
114141 } ) ;
115- let mut to_extract_no_ref = peel_parens ( to_extract . clone ( ) ) ;
142+ let mut to_extract_no_ref = peel_parens ( analysis . clone ( ) ) ;
116143 let needs_ref = needs_adjust
117144 && match & to_extract_no_ref {
118145 ast:: Expr :: FieldExpr ( _)
@@ -127,14 +154,14 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
127154 }
128155 _ => false ,
129156 } ;
130- let module = ctx. sema . scope ( to_extract . syntax ( ) ) ?. module ( ) ;
131- let target = to_extract . syntax ( ) . text_range ( ) ;
157+ let module = ctx. sema . scope ( analysis . syntax ( ) ) ?. module ( ) ;
158+ let target = to_replace . start ( ) . text_range ( ) . cover ( to_replace . end ( ) . text_range ( ) ) ;
132159 let needs_mut = match & parent {
133160 Some ( ast:: Expr :: RefExpr ( expr) ) => expr. mut_token ( ) . is_some ( ) ,
134161 _ => needs_adjust && !needs_ref && ty. as_ref ( ) . is_some_and ( |ty| ty. is_mutable_reference ( ) ) ,
135162 } ;
136163 for kind in ExtractionKind :: ALL {
137- let Some ( anchor) = Anchor :: from ( & to_extract , kind) else {
164+ let Some ( anchor) = Anchor :: from ( & place , kind) else {
138165 continue ;
139166 } ;
140167
@@ -169,10 +196,18 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
169196 kind. label ( ) ,
170197 target,
171198 |edit| {
172- let ( var_name, expr_replace) = kind. get_name_and_expr ( ctx, & to_extract) ;
199+ let ( var_name, expr_replace) = kind. get_name_and_expr ( ctx, & analysis) ;
200+
201+ let to_replace =
202+ if expr_replace. ancestors ( ) . last ( ) == to_replace. start ( ) . ancestors ( ) . last ( ) {
203+ let element = expr_replace. clone ( ) . syntax_element ( ) ;
204+ element. clone ( ) ..=element
205+ } else {
206+ to_replace. clone ( )
207+ } ;
173208
174209 let make = SyntaxFactory :: with_mappings ( ) ;
175- let mut editor = edit. make_editor ( & expr_replace ) ;
210+ let mut editor = edit. make_editor ( & place ) ;
176211
177212 let pat_name = make. name ( & var_name) ;
178213 let name_expr = make. expr_path ( make. ident_path ( & var_name) ) ;
@@ -236,7 +271,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
236271 ] ,
237272 ) ;
238273
239- editor. replace ( expr_replace , name_expr. syntax ( ) ) ;
274+ editor. replace_all ( to_replace , vec ! [ name_expr. syntax( ) . syntax_element ( ) ] ) ;
240275 }
241276 Anchor :: Replace ( stmt) => {
242277 cov_mark:: hit!( test_extract_var_expr_stmt) ;
@@ -252,7 +287,8 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
252287 make. block_expr ( [ new_stmt] , Some ( name_expr) )
253288 } else {
254289 // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`.
255- editor. replace ( expr_replace, name_expr. syntax ( ) ) ;
290+ editor
291+ . replace_all ( to_replace, vec ! [ name_expr. syntax( ) . syntax_element( ) ] ) ;
256292 make. block_expr ( [ new_stmt] , Some ( to_wrap. clone ( ) ) )
257293 }
258294 // fixup indentation of block
@@ -272,6 +308,23 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
272308 Some ( ( ) )
273309}
274310
311+ fn extract_token_range_of (
312+ node : & SyntaxNode ,
313+ range : TextRange ,
314+ ) -> Option < ( SyntaxToken , SyntaxToken ) > {
315+ let first = node. token_at_offset ( range. start ( ) ) . right_biased ( ) ?;
316+ let last = node. token_at_offset ( range. end ( ) ) . left_biased ( ) ?;
317+
318+ let first = skip_trivia_token ( first, Direction :: Next ) ?;
319+ let last = skip_trivia_token ( last, Direction :: Next ) ?;
320+
321+ if first. text_range ( ) . ordering ( last. text_range ( ) ) . is_gt ( ) {
322+ return None ;
323+ }
324+
325+ Some ( ( first, last) )
326+ }
327+
275328fn peel_parens ( mut expr : ast:: Expr ) -> ast:: Expr {
276329 while let ast:: Expr :: ParenExpr ( parens) = & expr {
277330 let Some ( expr_inside) = parens. expr ( ) else { break } ;
@@ -401,9 +454,8 @@ enum Anchor {
401454}
402455
403456impl Anchor {
404- fn from ( to_extract : & ast:: Expr , kind : & ExtractionKind ) -> Option < Anchor > {
405- let result = to_extract
406- . syntax ( )
457+ fn from ( place : & SyntaxNode , kind : & ExtractionKind ) -> Option < Anchor > {
458+ let result = place
407459 . ancestors ( )
408460 . take_while ( |it| !ast:: Item :: can_cast ( it. kind ( ) ) || ast:: MacroCall :: can_cast ( it. kind ( ) ) )
409461 . find_map ( |node| {
@@ -435,7 +487,7 @@ impl Anchor {
435487
436488 if let Some ( stmt) = ast:: Stmt :: cast ( node. clone ( ) ) {
437489 if let ast:: Stmt :: ExprStmt ( stmt) = stmt
438- && stmt. expr ( ) . as_ref ( ) == Some ( to_extract )
490+ && stmt. expr ( ) . is_some_and ( |it| it . syntax ( ) == place )
439491 {
440492 return Some ( Anchor :: Replace ( stmt) ) ;
441493 }
@@ -446,7 +498,7 @@ impl Anchor {
446498
447499 match kind {
448500 ExtractionKind :: Constant | ExtractionKind :: Static if result. is_none ( ) => {
449- to_extract . syntax ( ) . ancestors ( ) . find_map ( |node| {
501+ place . ancestors ( ) . find_map ( |node| {
450502 let item = ast:: Item :: cast ( node. clone ( ) ) ?;
451503 let parent = item. syntax ( ) . parent ( ) ?;
452504 match parent. kind ( ) {
@@ -2771,6 +2823,186 @@ fn main() {
27712823 let t2 = t;
27722824 let x = s;
27732825}
2826+ "# ,
2827+ "Extract into variable" ,
2828+ ) ;
2829+ }
2830+
2831+ #[ test]
2832+ fn extract_variable_in_token_tree ( ) {
2833+ // FIXME: Keep the original trivia instead of extracting macro expanded?
2834+ check_assist_by_label (
2835+ extract_variable,
2836+ r#"
2837+ macro_rules! foo {
2838+ (= $($t:tt)*) => {
2839+ $($t)*
2840+ };
2841+ }
2842+
2843+ fn main() {
2844+ let x = foo!(= $02 + 3$0 + 4);
2845+ }
2846+ "# ,
2847+ r#"
2848+ macro_rules! foo {
2849+ (= $($t:tt)*) => {
2850+ $($t)*
2851+ };
2852+ }
2853+
2854+ fn main() {
2855+ let $0var_name = 2+3;
2856+ let x = foo!(= var_name + 4);
2857+ }
2858+ "# ,
2859+ "Extract into variable" ,
2860+ ) ;
2861+
2862+ check_assist_by_label (
2863+ extract_variable,
2864+ r#"
2865+ macro_rules! foo {
2866+ (= $($t:tt)*) => {
2867+ $($t)*
2868+ };
2869+ }
2870+
2871+ fn main() {
2872+ let x = foo!(= $02 +$0 3 + 4);
2873+ }
2874+ "# ,
2875+ r#"
2876+ macro_rules! foo {
2877+ (= $($t:tt)*) => {
2878+ $($t)*
2879+ };
2880+ }
2881+
2882+ fn main() {
2883+ let $0var_name = 2+3;
2884+ let x = foo!(= var_name + 4);
2885+ }
2886+ "# ,
2887+ "Extract into variable" ,
2888+ ) ;
2889+
2890+ check_assist_by_label (
2891+ extract_variable,
2892+ r#"
2893+ macro_rules! foo {
2894+ (= $($t:tt)*) => {
2895+ $($t)*
2896+ };
2897+ }
2898+
2899+ fn main() {
2900+ let x = foo!(= $02 + 3 + 4$0);
2901+ }
2902+ "# ,
2903+ r#"
2904+ macro_rules! foo {
2905+ (= $($t:tt)*) => {
2906+ $($t)*
2907+ };
2908+ }
2909+
2910+ fn main() {
2911+ let $0var_name = 2+3+4;
2912+ let x = foo!(= var_name);
2913+ }
2914+ "# ,
2915+ "Extract into variable" ,
2916+ ) ;
2917+
2918+ // FIXME: Extract to inside the macro instead of outside the macro
2919+ check_assist_by_label (
2920+ extract_variable,
2921+ r#"
2922+ macro_rules! foo {
2923+ (= $($t:tt)*) => {
2924+ $($t)*
2925+ };
2926+ }
2927+
2928+ fn main() {
2929+ let x = foo!(= {
2930+ $02 + 3 + 4$0
2931+ });
2932+ }
2933+ "# ,
2934+ r#"
2935+ macro_rules! foo {
2936+ (= $($t:tt)*) => {
2937+ $($t)*
2938+ };
2939+ }
2940+
2941+ fn main() {
2942+ let $0var_name = 2+3+4;
2943+ let x = foo!(= {
2944+ var_name
2945+ });
2946+ }
2947+ "# ,
2948+ "Extract into variable" ,
2949+ ) ;
2950+ }
2951+
2952+ #[ test]
2953+ fn extract_variable_in_token_tree_record_expr ( ) {
2954+ check_assist_by_label (
2955+ extract_variable,
2956+ r#"
2957+ macro_rules! foo {
2958+ (= $($t:tt)*) => {
2959+ $($t)*
2960+ };
2961+ }
2962+
2963+ fn main() {
2964+ let x = foo!(= Foo { x: $02 + 3$0 });
2965+ }
2966+ "# ,
2967+ r#"
2968+ macro_rules! foo {
2969+ (= $($t:tt)*) => {
2970+ $($t)*
2971+ };
2972+ }
2973+
2974+ fn main() {
2975+ let $0x = 2+3;
2976+ let x = foo!(= Foo { x: x });
2977+ }
2978+ "# ,
2979+ "Extract into variable" ,
2980+ ) ;
2981+
2982+ check_assist_by_label (
2983+ extract_variable,
2984+ r#"
2985+ macro_rules! foo {
2986+ (= $($t:tt)*) => {
2987+ $($t)*
2988+ };
2989+ }
2990+
2991+ fn main() {
2992+ let x = foo!(= Foo { x: $02 + 3$0 + 4 });
2993+ }
2994+ "# ,
2995+ r#"
2996+ macro_rules! foo {
2997+ (= $($t:tt)*) => {
2998+ $($t)*
2999+ };
3000+ }
3001+
3002+ fn main() {
3003+ let $0var_name = 2+3;
3004+ let x = foo!(= Foo { x: var_name + 4 });
3005+ }
27743006"# ,
27753007 "Extract into variable" ,
27763008 ) ;
0 commit comments