@@ -8,6 +8,8 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
8
8
import ElixirLS.LanguageServer.Protocol
9
9
10
10
alias ElixirLS.LanguageServer . { JsonRpc , Server }
11
+ alias ElixirLS.LanguageServer.SourceFile
12
+ alias ElixirLS.LanguageServer.Protocol.TextEdit
11
13
12
14
alias __MODULE__ . AST
13
15
@@ -22,7 +24,13 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
22
24
# line and col are assumed to be 0-indexed
23
25
source_file = Server . get_source_file ( state , uri )
24
26
25
- { :ok , % { edited_text: edited_text , edit_range: edit_range } } =
27
+ label =
28
+ case operation do
29
+ "toPipe" -> "Convert function call to pipe operator"
30
+ "fromPipe" -> "Convert pipe operator to function call"
31
+ end
32
+
33
+ processing_result =
26
34
case operation do
27
35
"toPipe" ->
28
36
to_pipe_at_cursor ( source_file . text , line , col )
@@ -31,33 +39,29 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
31
39
from_pipe_at_cursor ( source_file . text , line , col )
32
40
end
33
41
34
- label =
35
- case operation do
36
- "toPipe" -> "Convert function call to pipe operator"
37
- "fromPipe" -> "Convert pipe operator to function call"
38
- end
42
+ with { :ok , % TextEdit { } = text_edit } <- processing_result ,
43
+ { :ok , % { "applied" => true } } <-
44
+ JsonRpc . send_request ( "workspace/applyEdit" , % {
45
+ "label" => label ,
46
+ "edit" => % {
47
+ "changes" => % {
48
+ uri => [ text_edit ]
49
+ }
50
+ }
51
+ } ) do
52
+ { :ok , nil }
53
+ else
54
+ { :error , reason } ->
55
+ { :error , reason }
39
56
40
- edit_result =
41
- JsonRpc . send_request ( "workspace/applyEdit" , % {
42
- "label" => label ,
43
- "edit" => % {
44
- "changes" => % {
45
- uri => [ % { "range" => edit_range , "newText" => edited_text } ]
46
- }
47
- }
48
- } )
49
-
50
- case edit_result do
51
- { :ok , % { "applied" => true } } ->
52
- { :ok , nil }
53
-
54
- other ->
57
+ error ->
55
58
{ :error , :server_error ,
56
- "cannot insert spec , workspace/applyEdit returned #{ inspect ( other ) } " }
59
+ "cannot execute pipe conversion , workspace/applyEdit returned #{ inspect ( error ) } " }
57
60
end
58
61
end
59
62
60
- defp to_pipe_at_cursor ( text , line , col ) do
63
+ @ doc false
64
+ def to_pipe_at_cursor ( text , line , col ) do
61
65
result =
62
66
ElixirSense.Core.Source . walk_text (
63
67
text ,
@@ -67,27 +71,40 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
67
71
{ :ok , function_call , call_range } =
68
72
get_function_call ( line , col , acc . walked_text , current_char , remaining_text )
69
73
70
- { remaining_text ,
71
- % {
72
- acc
73
- | walked_text: acc . walked_text <> current_char ,
74
- function_call: function_call ,
75
- range: call_range
76
- } }
74
+ if function_call_includes_cursor ( call_range , line , col ) do
75
+ { remaining_text ,
76
+ % {
77
+ acc
78
+ | walked_text: acc . walked_text <> current_char ,
79
+ function_call: function_call ,
80
+ range: call_range
81
+ } }
82
+ else
83
+ # The cursor was not inside a function call so we cannot
84
+ # manipulate the pipes
85
+ { remaining_text ,
86
+ % {
87
+ acc
88
+ | walked_text: acc . walked_text <> current_char
89
+ } }
90
+ end
77
91
else
78
92
{ remaining_text , % { acc | walked_text: acc . walked_text <> current_char } }
79
93
end
80
94
end
81
95
)
82
96
83
- case result do
84
- % { function_call: nil } ->
97
+ with { :result , % { function_call: function_call , range: range } }
98
+ when not is_nil ( function_call ) and not is_nil ( range ) <- { :result , result } ,
99
+ { :ok , piped_text } <- AST . to_pipe ( function_call ) do
100
+ text_edit = % TextEdit { newText: piped_text , range: range }
101
+ { :ok , text_edit }
102
+ else
103
+ { :result , % { function_call: nil } } ->
85
104
{ :error , :function_call_not_found }
86
105
87
- % { function_call: function_call , range: range } ->
88
- piped_text = AST . to_pipe ( function_call )
89
-
90
- { :ok , % { edited_text: piped_text , edit_range: range } }
106
+ { :error , :invalid_code } ->
107
+ { :error , :invalid_code }
91
108
end
92
109
end
93
110
@@ -98,33 +115,53 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
98
115
% { walked_text: "" , pipe_call: nil , range: nil } ,
99
116
fn current_char , remaining_text , current_line , current_col , acc ->
100
117
if current_line - 1 == line and current_col - 1 == col do
101
- { :ok , pipe_call , call_range } =
102
- get_pipe_call ( line , col , acc . walked_text , current_char , remaining_text )
103
-
104
- { remaining_text ,
105
- % {
106
- acc
107
- | walked_text: acc . walked_text <> current_char ,
108
- pipe_call: pipe_call ,
109
- range: call_range
110
- } }
118
+ case get_pipe_call ( line , col , acc . walked_text , current_char , remaining_text ) do
119
+ { :ok , pipe_call , call_range } ->
120
+ { remaining_text ,
121
+ % {
122
+ acc
123
+ | walked_text: acc . walked_text <> current_char ,
124
+ pipe_call: pipe_call ,
125
+ range: call_range
126
+ } }
127
+
128
+ { :error , :no_pipe_at_selection } ->
129
+ { remaining_text ,
130
+ % {
131
+ acc
132
+ | walked_text: acc . walked_text <> current_char
133
+ } }
134
+ end
111
135
else
112
136
{ remaining_text , % { acc | walked_text: acc . walked_text <> current_char } }
113
137
end
114
138
end
115
139
)
116
140
117
- case result do
118
- % { pipe_call: nil } ->
141
+ with { :result , % { pipe_call: pipe_call , range: range } }
142
+ when not is_nil ( pipe_call ) and not is_nil ( range ) <- { :result , result } ,
143
+ { :ok , unpiped_text } <- AST . from_pipe ( pipe_call ) do
144
+ text_edit = % TextEdit { newText: unpiped_text , range: range }
145
+ { :ok , text_edit }
146
+ else
147
+ { :result , % { pipe_call: nil } } ->
119
148
{ :error , :pipe_not_found }
120
149
121
- % { pipe_call: pipe_call , range: range } ->
122
- unpiped_text = AST . from_pipe ( pipe_call )
123
-
124
- { :ok , % { edited_text: unpiped_text , edit_range: range } }
150
+ { :error , :invalid_code } ->
151
+ { :error , :invalid_code }
125
152
end
126
153
end
127
154
155
+ defp get_function_call ( line , col , head , cur , original_tail ) when cur in [ "\n " , "\r " , "\r \n " ] do
156
+ { head , new_cur } = String . split_at ( head , - 1 )
157
+ get_function_call ( line , col - 1 , head , new_cur , cur <> original_tail )
158
+ end
159
+
160
+ defp get_function_call ( line , col , head , ")" , original_tail ) do
161
+ { head , cur } = String . split_at ( head , - 1 )
162
+ get_function_call ( line , col - 1 , head , cur , ")" <> original_tail )
163
+ end
164
+
128
165
defp get_function_call ( line , col , head , current , original_tail ) do
129
166
tail = do_get_function_call ( original_tail , "(" , ")" )
130
167
@@ -141,10 +178,15 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
141
178
text = head <> current <> tail
142
179
143
180
call = get_function_call_before ( text )
181
+ orig_head = head
144
182
145
- { head , _tail } = String . split_at ( call , - String . length ( tail ) )
183
+ { head , _new_tail } =
184
+ case String . length ( tail ) do
185
+ 0 -> { call , "" }
186
+ length -> String . split_at ( call , - length )
187
+ end
146
188
147
- col = if head == "" , do: col + 2 , else: col - String . length ( head ) + 1
189
+ { line , col } = fix_start_of_range ( orig_head , head , line , col )
148
190
149
191
{ :ok , call , range ( line , col , end_line , end_col ) }
150
192
end
@@ -180,6 +222,8 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
180
222
do_get_function_call ( tail , start_char , end_char , % { acc | text: [ acc . text | [ c ] ] } )
181
223
end
182
224
225
+ defp do_get_function_call ( _ , _ , _ , acc ) , do: acc
226
+
183
227
defp get_pipe_call ( line , col , head , current , tail ) do
184
228
pipe_right = do_get_function_call ( tail , "(" , ")" )
185
229
@@ -230,7 +274,11 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
230
274
col + tail_length
231
275
end
232
276
233
- { :ok , pipe_call , range ( start_line , start_col , end_line , end_col ) }
277
+ if String . contains? ( pipe_call , "|>" ) do
278
+ { :ok , pipe_call , range ( start_line , start_col , end_line , end_col ) }
279
+ else
280
+ { :error , :no_pipe_at_selection }
281
+ end
234
282
end
235
283
236
284
# do_get_pipe_call(text :: utf16 binary, {utf16 binary, has_passed_through_whitespace, should_halt})
@@ -267,20 +315,24 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
267
315
|> do_get_function_call ( ")" , "(" )
268
316
|> String . reverse ( )
269
317
270
- function_name =
318
+ if call_without_function_name == "" do
271
319
head
272
- |> String . trim_trailing ( call_without_function_name )
273
- |> get_function_name_from_tail ( )
320
+ else
321
+ function_name =
322
+ head
323
+ |> String . trim_trailing ( call_without_function_name )
324
+ |> get_function_name_from_tail ( )
274
325
275
- function_name <> call_without_function_name
326
+ function_name <> call_without_function_name
327
+ end
276
328
end
277
329
278
330
defp get_function_name_from_tail ( s ) do
279
331
s
280
332
|> String . reverse ( )
281
333
|> String . graphemes ( )
282
334
|> Enum . reduce_while ( [ ] , fn c , acc ->
283
- if String . match? ( c , ~r/ \s / ) do
335
+ if String . match? ( c , ~r/ [ \s \( \[ \{ ] / ) do
284
336
{ :halt , acc }
285
337
else
286
338
{ :cont , [ c | acc ] }
@@ -302,4 +354,52 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do
302
354
count_newlines_and_get_tail ( tail , { line_count , tail_length + 1 } )
303
355
end
304
356
end
357
+
358
+ # Fixes the line and column returned, finding the correct position on previous lines
359
+ defp fix_start_of_range ( orig_head , head , line , col )
360
+ defp fix_start_of_range ( _ , "" , line , col ) , do: { line , col + 2 }
361
+
362
+ defp fix_start_of_range ( orig_head , head , line , col ) do
363
+ new_col = col - String . length ( head ) + 1
364
+
365
+ if new_col < 0 do
366
+ lines =
367
+ SourceFile . lines ( orig_head )
368
+ |> Enum . take ( line )
369
+ |> Enum . reverse ( )
370
+
371
+ # Go back through previous lines to find the correctly adjusted line and
372
+ # column number for the start of head (where the function starts)
373
+ Enum . reduce_while ( lines , { line , new_col } , fn
374
+ _line_text , { cur_line , cur_col } when cur_col >= 0 ->
375
+ { :halt , { cur_line , cur_col } }
376
+
377
+ line_text , { cur_line , cur_col } ->
378
+ # The +1 is for the line separator
379
+ { :cont , { cur_line - 1 , cur_col + String . length ( line_text ) + 1 } }
380
+ end )
381
+ else
382
+ { line , new_col }
383
+ end
384
+ end
385
+
386
+ defp function_call_includes_cursor ( call_range , line , char ) do
387
+ range ( start_line , start_character , end_line , end_character ) = call_range
388
+
389
+ starts_before =
390
+ cond do
391
+ start_line < line -> true
392
+ start_line == line and start_character <= char -> true
393
+ true -> false
394
+ end
395
+
396
+ ends_after =
397
+ cond do
398
+ end_line > line -> true
399
+ end_line == line and end_character >= char -> true
400
+ true -> false
401
+ end
402
+
403
+ starts_before and ends_after
404
+ end
305
405
end
0 commit comments