@@ -10,6 +10,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
10
10
alias ElixirLS.LanguageServer.Protocol.TextEdit
11
11
alias ElixirLS.LanguageServer.SourceFile
12
12
import ElixirLS.LanguageServer.Protocol , only: [ range: 4 ]
13
+ alias ElixirSense.Providers.Suggestion.Matcher
13
14
14
15
@ enforce_keys [ :label , :kind , :insert_text , :priority , :tags ]
15
16
defstruct [
@@ -18,14 +19,16 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
18
19
:detail ,
19
20
:documentation ,
20
21
:insert_text ,
22
+ :insert_text_mode ,
21
23
:filter_text ,
22
24
# Lower priority is shown higher in the result list
23
25
:priority ,
24
26
:label_details ,
25
27
:tags ,
26
28
:command ,
27
29
{ :preselect , false } ,
28
- :additional_text_edit
30
+ :additional_text_edit ,
31
+ :text_edit
29
32
]
30
33
31
34
@ func_snippets % {
@@ -90,10 +93,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
90
93
end
91
94
92
95
def completion ( text , line , character , options ) do
93
- line_text =
94
- text
95
- |> SourceFile . lines ( )
96
- |> Enum . at ( line )
96
+ lines = SourceFile . lines ( text )
97
+ line_text = Enum . at ( lines , line )
97
98
98
99
# convert to 1 based utf8 position
99
100
line = line + 1
@@ -132,6 +133,18 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
132
133
nil
133
134
end
134
135
136
+ do_block_indent =
137
+ lines
138
+ |> Enum . slice ( 0 .. ( line - 1 ) )
139
+ |> Enum . reverse ( )
140
+ |> Enum . find_value ( 0 , fn line_text ->
141
+ if Regex . match? ( ~r/ (?<=\s |^)do\s *(#.*)?$/ , line_text ) do
142
+ String . length ( line_text ) - String . length ( String . trim_leading ( line_text ) )
143
+ end
144
+ end )
145
+
146
+ line_indent = String . length ( line_text ) - String . length ( String . trim_leading ( line_text ) )
147
+
135
148
context = % {
136
149
text_before_cursor: text_before_cursor ,
137
150
text_after_cursor: text_after_cursor ,
@@ -141,7 +154,11 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
141
154
pipe_before?: Regex . match? ( ~r/ \| >\s *#{ prefix } $/ , text_before_cursor ) ,
142
155
capture_before?: Regex . match? ( ~r/ &#{ prefix } $/ , text_before_cursor ) ,
143
156
scope: scope ,
144
- module: env . module
157
+ module: env . module ,
158
+ line: line ,
159
+ character: character ,
160
+ do_block_indent: do_block_indent ,
161
+ line_indent: line_indent
145
162
}
146
163
147
164
position_to_insert_alias =
@@ -160,7 +177,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
160
177
|> maybe_reject_derived_functions ( context , options )
161
178
|> Enum . map ( & from_completion_item ( & 1 , context , options ) )
162
179
|> maybe_add_do ( context )
163
- |> maybe_add_end ( context )
164
180
|> maybe_add_keywords ( context )
165
181
|> Enum . reject ( & is_nil / 1 )
166
182
|> sort_items ( )
@@ -204,33 +220,23 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
204
220
end
205
221
206
222
defp maybe_add_do ( completion_items , context ) do
207
- if String . ends_with? ( context . text_before_cursor , " do" ) && context . text_after_cursor == "" do
223
+ hint =
224
+ case Regex . scan ( ~r/ (?<=\s |^)[a-z]+$/ , context . text_before_cursor ) do
225
+ [ ] -> ""
226
+ [ [ match ] ] -> match
227
+ end
228
+
229
+ if hint in [ "d" , "do" ] do
208
230
item = % __MODULE__ {
209
231
label: "do" ,
210
232
kind: :keyword ,
211
- detail: "keyword" ,
212
- insert_text: "do\n $0\n end" ,
233
+ detail: "reserved word" ,
234
+ insert_text:
235
+ if ( String . trim ( context . text_after_cursor ) == "" , do: "do\n $0\n end" , else: "do: " ) ,
213
236
tags: [ ] ,
214
237
priority: 0 ,
215
238
# force selection over other longer not exact completions
216
- preselect: true
217
- }
218
-
219
- [ item | completion_items ]
220
- else
221
- completion_items
222
- end
223
- end
224
-
225
- defp maybe_add_end ( completion_items , context ) do
226
- if String . ends_with? ( context . text_before_cursor , "end" ) && context . text_after_cursor == "" do
227
- item = % __MODULE__ {
228
- label: "end" ,
229
- kind: :keyword ,
230
- detail: "keyword" ,
231
- insert_text: "end" ,
232
- tags: [ ] ,
233
- priority: 0
239
+ preselect: hint == "do"
234
240
}
235
241
236
242
[ item | completion_items ]
@@ -239,40 +245,84 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
239
245
end
240
246
end
241
247
242
- defp maybe_add_keywords ( completion_items , % { text_after_cursor: "" } = context ) do
243
- kw = Map . get ( context , :text_before_cursor ) |> String . trim_leading ( ) |> get_keyword ( )
248
+ defp maybe_add_keywords ( completion_items , context ) do
249
+ hint =
250
+ case Regex . scan ( ~r/ (?<=\s |^)[a-z]+$/ , context . text_before_cursor ) do
251
+ [ ] -> ""
252
+ [ [ match ] ] -> match
253
+ end
244
254
245
- if kw != "" do
246
- item = % __MODULE__ {
247
- label: kw ,
248
- kind: :keyword ,
249
- detail: "keyword" ,
250
- insert_text: kw ,
251
- tags: [ ] ,
252
- priority: 0
253
- }
255
+ if hint != "" do
256
+ keyword_items =
257
+ for keyword <- ~w( true false nil when end rescue catch else after) ,
258
+ Matcher . match? ( keyword , hint ) do
259
+ { insert_text , text_edit } =
260
+ cond do
261
+ keyword in ~w( rescue catch else after) ->
262
+ if String . trim ( context . text_after_cursor ) == "" do
263
+ { nil ,
264
+ % {
265
+ "range" => % {
266
+ "start" => % {
267
+ "line" => context . line - 1 ,
268
+ "character" =>
269
+ context . character - String . length ( hint ) - 1 -
270
+ ( context . line_indent - context . do_block_indent )
271
+ } ,
272
+ "end" => % {
273
+ "line" => context . line - 1 ,
274
+ "character" => context . character - 1
275
+ }
276
+ } ,
277
+ "newText" => "#{ keyword } \n "
278
+ } }
279
+ else
280
+ { "#{ keyword } : " , nil }
281
+ end
282
+
283
+ keyword == "when" ->
284
+ { "when " , nil }
285
+
286
+ keyword == "end" ->
287
+ { nil ,
288
+ % {
289
+ "range" => % {
290
+ "start" => % {
291
+ "line" => context . line - 1 ,
292
+ "character" =>
293
+ context . character - String . length ( hint ) - 1 -
294
+ ( context . line_indent - context . do_block_indent )
295
+ } ,
296
+ "end" => % { "line" => context . line - 1 , "character" => context . character - 1 }
297
+ } ,
298
+ "newText" => "end\n "
299
+ } }
300
+
301
+ true ->
302
+ { keyword , nil }
303
+ end
304
+
305
+ % __MODULE__ {
306
+ label: keyword ,
307
+ kind: :keyword ,
308
+ detail: "reserved word" ,
309
+ insert_text: insert_text ,
310
+ text_edit: text_edit ,
311
+ tags: [ ] ,
312
+ priority: 0 ,
313
+ insert_text_mode: 2 ,
314
+ preselect: hint == keyword
315
+ }
316
+ end
254
317
255
- [ item | completion_items ]
318
+ keyword_items ++ completion_items
256
319
else
257
320
completion_items
258
321
end
259
322
end
260
323
261
- defp maybe_add_keywords ( completion_items , _context ) do
262
- completion_items
263
- end
264
-
265
324
## Helpers
266
325
267
- defp get_keyword ( t ) do
268
- cond do
269
- Enum . member? ( [ "t" , "tr" , "tru" , "true" ] , t ) -> "true"
270
- Enum . member? ( [ "f" , "fa" , "fal" , "fals" , "false" ] , t ) -> "false"
271
- Enum . member? ( [ "n" , "ni" , "nil" ] , t ) -> "nil"
272
- true -> ""
273
- end
274
- end
275
-
276
326
defp is_incomplete ( items ) do
277
327
if Enum . empty? ( items ) do
278
328
false
@@ -1254,6 +1304,20 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
1254
1304
json
1255
1305
end
1256
1306
1307
+ json =
1308
+ if item . insert_text_mode do
1309
+ Map . put ( json , "insertTextMode" , item . insert_text_mode )
1310
+ else
1311
+ json
1312
+ end
1313
+
1314
+ json =
1315
+ if item . text_edit do
1316
+ Map . put ( json , "textEdit" , item . text_edit )
1317
+ else
1318
+ json
1319
+ end
1320
+
1257
1321
# deprecated as of Language Server Protocol Specification - 3.15
1258
1322
json =
1259
1323
if Keyword . get ( options , :deprecated_supported , false ) do
0 commit comments