@@ -23,6 +23,8 @@ module Requests
23
23
#
24
24
class CodeActionResolve < Request
25
25
extend T ::Sig
26
+ include Support ::Common
27
+
26
28
NEW_VARIABLE_NAME = "new_variable"
27
29
NEW_METHOD_NAME = "new_method"
28
30
@@ -45,20 +47,62 @@ def initialize(document, code_action)
45
47
46
48
sig { override . returns ( T . any ( Interface ::CodeAction , Error ) ) }
47
49
def perform
50
+ return Error ::EmptySelection if @document . source . empty?
51
+
48
52
case @code_action [ :title ]
49
53
when CodeActions ::EXTRACT_TO_VARIABLE_TITLE
50
54
refactor_variable
51
55
when CodeActions ::EXTRACT_TO_METHOD_TITLE
52
56
refactor_method
57
+ when CodeActions ::SWITCH_BLOCK_STYLE_TITLE
58
+ switch_block_style
53
59
else
54
60
Error ::UnknownCodeAction
55
61
end
56
62
end
57
63
64
+ private
65
+
58
66
sig { returns ( T . any ( Interface ::CodeAction , Error ) ) }
59
- def refactor_variable
60
- return Error ::EmptySelection if @document . source . empty?
67
+ def switch_block_style
68
+ source_range = @code_action . dig ( :data , :range )
69
+ return Error ::EmptySelection if source_range [ :start ] == source_range [ :end ]
70
+
71
+ target = @document . locate_first_within_range (
72
+ @code_action . dig ( :data , :range ) ,
73
+ node_types : [ Prism ::CallNode ] ,
74
+ )
75
+
76
+ return Error ::InvalidTargetRange unless target . is_a? ( Prism ::CallNode )
77
+
78
+ node = target . block
79
+ return Error ::InvalidTargetRange unless node . is_a? ( Prism ::BlockNode )
61
80
81
+ indentation = " " * target . location . start_column unless node . opening_loc . slice == "do"
82
+
83
+ Interface ::CodeAction . new (
84
+ title : CodeActions ::SWITCH_BLOCK_STYLE_TITLE ,
85
+ edit : Interface ::WorkspaceEdit . new (
86
+ document_changes : [
87
+ Interface ::TextDocumentEdit . new (
88
+ text_document : Interface ::OptionalVersionedTextDocumentIdentifier . new (
89
+ uri : @code_action . dig ( :data , :uri ) ,
90
+ version : nil ,
91
+ ) ,
92
+ edits : [
93
+ Interface ::TextEdit . new (
94
+ range : range_from_location ( node . location ) ,
95
+ new_text : recursively_switch_nested_block_styles ( node , indentation ) ,
96
+ ) ,
97
+ ] ,
98
+ ) ,
99
+ ] ,
100
+ ) ,
101
+ )
102
+ end
103
+
104
+ sig { returns ( T . any ( Interface ::CodeAction , Error ) ) }
105
+ def refactor_variable
62
106
source_range = @code_action . dig ( :data , :range )
63
107
return Error ::EmptySelection if source_range [ :start ] == source_range [ :end ]
64
108
@@ -153,8 +197,6 @@ def refactor_variable
153
197
154
198
sig { returns ( T . any ( Interface ::CodeAction , Error ) ) }
155
199
def refactor_method
156
- return Error ::EmptySelection if @document . source . empty?
157
-
158
200
source_range = @code_action . dig ( :data , :range )
159
201
return Error ::EmptySelection if source_range [ :start ] == source_range [ :end ]
160
202
@@ -206,8 +248,6 @@ def refactor_method
206
248
)
207
249
end
208
250
209
- private
210
-
211
251
sig { params ( range : T ::Hash [ Symbol , T . untyped ] , new_text : String ) . returns ( Interface ::TextEdit ) }
212
252
def create_text_edit ( range , new_text )
213
253
Interface ::TextEdit . new (
@@ -218,6 +258,64 @@ def create_text_edit(range, new_text)
218
258
new_text : new_text ,
219
259
)
220
260
end
261
+
262
+ sig { params ( node : Prism ::BlockNode , indentation : T . nilable ( String ) ) . returns ( String ) }
263
+ def recursively_switch_nested_block_styles ( node , indentation )
264
+ parameters = node . parameters
265
+ body = node . body
266
+
267
+ # We use the indentation to differentiate between do...end and brace style blocks because only the do...end
268
+ # style requires the indentation to build the edit.
269
+ #
270
+ # If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
271
+ # semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
272
+ # we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
273
+ source = +""
274
+
275
+ if indentation
276
+ source << "do"
277
+ source << " #{ parameters . slice } " if parameters
278
+ source << "\n #{ indentation } "
279
+ source << switch_block_body ( body , indentation ) if body
280
+ source << "\n #{ indentation } end"
281
+ else
282
+ source << "{ "
283
+ source << "#{ parameters . slice } " if parameters
284
+ source << switch_block_body ( body , nil ) if body
285
+ source << "}"
286
+ end
287
+
288
+ source
289
+ end
290
+
291
+ sig { params ( body : Prism ::Node , indentation : T . nilable ( String ) ) . returns ( String ) }
292
+ def switch_block_body ( body , indentation )
293
+ # Check if there are any nested blocks inside of the current block
294
+ body_loc = body . location
295
+ nested_block = @document . locate_first_within_range (
296
+ {
297
+ start : { line : body_loc . start_line - 1 , character : body_loc . start_column } ,
298
+ end : { line : body_loc . end_line - 1 , character : body_loc . end_column } ,
299
+ } ,
300
+ node_types : [ Prism ::BlockNode ] ,
301
+ )
302
+
303
+ body_content = body . slice . dup
304
+
305
+ # If there are nested blocks, then we change their style too and we have to mutate the string using the
306
+ # relative position in respect to the beginning of the body
307
+ if nested_block . is_a? ( Prism ::BlockNode )
308
+ location = nested_block . location
309
+ correction_start = location . start_offset - body_loc . start_offset
310
+ correction_end = location . end_offset - body_loc . start_offset
311
+ next_indentation = indentation ? "#{ indentation } " : nil
312
+
313
+ body_content [ correction_start ...correction_end ] =
314
+ recursively_switch_nested_block_styles ( nested_block , next_indentation )
315
+ end
316
+
317
+ indentation ? body_content . gsub ( ";" , "\n " ) : "#{ body_content . gsub ( "\n " , ";" ) } "
318
+ end
221
319
end
222
320
end
223
321
end
0 commit comments