@@ -10,6 +10,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
1010 alias ElixirLS.LanguageServer.Protocol.TextEdit
1111 alias ElixirLS.LanguageServer.SourceFile
1212 import ElixirLS.LanguageServer.Protocol , only: [ range: 4 ]
13+ alias ElixirSense.Providers.Suggestion.Matcher
1314
1415 @ enforce_keys [ :label , :kind , :insert_text , :priority , :tags ]
1516 defstruct [
@@ -18,14 +19,16 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
1819 :detail ,
1920 :documentation ,
2021 :insert_text ,
22+ :insert_text_mode ,
2123 :filter_text ,
2224 # Lower priority is shown higher in the result list
2325 :priority ,
2426 :label_details ,
2527 :tags ,
2628 :command ,
2729 { :preselect , false } ,
28- :additional_text_edit
30+ :additional_text_edit ,
31+ :text_edit
2932 ]
3033
3134 @ func_snippets % {
@@ -90,10 +93,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
9093 end
9194
9295 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 )
9798
9899 # convert to 1 based utf8 position
99100 line = line + 1
@@ -132,6 +133,18 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
132133 nil
133134 end
134135
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+
135148 context = % {
136149 text_before_cursor: text_before_cursor ,
137150 text_after_cursor: text_after_cursor ,
@@ -141,7 +154,11 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
141154 pipe_before?: Regex . match? ( ~r/ \| >\s *#{ prefix } $/ , text_before_cursor ) ,
142155 capture_before?: Regex . match? ( ~r/ &#{ prefix } $/ , text_before_cursor ) ,
143156 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
145162 }
146163
147164 position_to_insert_alias =
@@ -160,7 +177,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
160177 |> maybe_reject_derived_functions ( context , options )
161178 |> Enum . map ( & from_completion_item ( & 1 , context , options ) )
162179 |> maybe_add_do ( context )
163- |> maybe_add_end ( context )
164180 |> maybe_add_keywords ( context )
165181 |> Enum . reject ( & is_nil / 1 )
166182 |> sort_items ( )
@@ -204,33 +220,23 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
204220 end
205221
206222 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
208230 item = % __MODULE__ {
209231 label: "do" ,
210232 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: " ) ,
213236 tags: [ ] ,
214237 priority: 0 ,
215238 # 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"
234240 }
235241
236242 [ item | completion_items ]
@@ -239,40 +245,84 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
239245 end
240246 end
241247
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
244254
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
254317
255- [ item | completion_items ]
318+ keyword_items ++ completion_items
256319 else
257320 completion_items
258321 end
259322 end
260323
261- defp maybe_add_keywords ( completion_items , _context ) do
262- completion_items
263- end
264-
265324 ## Helpers
266325
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-
276326 defp is_incomplete ( items ) do
277327 if Enum . empty? ( items ) do
278328 false
@@ -1254,6 +1304,20 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
12541304 json
12551305 end
12561306
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+
12571321 # deprecated as of Language Server Protocol Specification - 3.15
12581322 json =
12591323 if Keyword . get ( options , :deprecated_supported , false ) do
0 commit comments