]> git.madduck.net Git - etc/vim.git/blob - autoload/ale/completion.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/ale/' content from commit 22185c4c
[etc/vim.git] / autoload / ale / completion.vim
1 " Author: w0rp <devw0rp@gmail.com>
2 " Description: Completion support for LSP linters
3 scriptencoding utf-8
4
5 " The omnicompletion menu is shown through a special Plug mapping which is
6 " only valid in Insert mode. This way, feedkeys() won't send these keys if you
7 " quit Insert mode quickly enough.
8 inoremap <silent> <Plug>(ale_show_completion_menu) <C-x><C-o><C-p>
9 " If we hit the key sequence in normal mode, then we won't show the menu, so
10 " we should restore the old settings right away.
11 nnoremap <silent> <Plug>(ale_show_completion_menu) :call ale#completion#RestoreCompletionOptions()<CR>
12 cnoremap <silent> <Plug>(ale_show_completion_menu) <Nop>
13 vnoremap <silent> <Plug>(ale_show_completion_menu) <Nop>
14 onoremap <silent> <Plug>(ale_show_completion_menu) <Nop>
15
16 let g:ale_completion_delay = get(g:, 'ale_completion_delay', 100)
17 let g:ale_completion_excluded_words = get(g:, 'ale_completion_excluded_words', [])
18 let g:ale_completion_max_suggestions = get(g:, 'ale_completion_max_suggestions', 50)
19 let g:ale_completion_autoimport = get(g:, 'ale_completion_autoimport', v:true)
20 let g:ale_completion_tsserver_remove_warnings = get(g:, 'ale_completion_tsserver_remove_warnings', 0)
21
22 let s:timer_id = -1
23 let s:last_done_pos = []
24
25 " CompletionItemKind values from the LSP protocol.
26 let g:ale_lsp_types = {
27 \ 1: 'text',
28 \ 2: 'method',
29 \ 3: 'function',
30 \ 4: 'constructor',
31 \ 5: 'field',
32 \ 6: 'variable',
33 \ 7: 'class',
34 \ 8: 'interface',
35 \ 9: 'module',
36 \ 10: 'property',
37 \ 11: 'unit',
38 \ 12: 'value',
39 \ 13: 'enum',
40 \ 14: 'keyword',
41 \ 15: 'snippet',
42 \ 16: 'color',
43 \ 17: 'file',
44 \ 18: 'reference',
45 \ 19: 'folder',
46 \ 20: 'enum_member',
47 \ 21: 'constant',
48 \ 22: 'struct',
49 \ 23: 'event',
50 \ 24: 'operator',
51 \ 25: 'type_parameter',
52 \ }
53
54 " from https://github.com/microsoft/TypeScript/blob/29becf05012bfa7ba20d50b0d16813971e46b8a6/lib/protocol.d.ts#L2472
55 let g:ale_tsserver_types = {
56 \ 'warning': 'text',
57 \ 'keyword': 'keyword',
58 \ 'script': 'file',
59 \ 'module': 'module',
60 \ 'class': 'class',
61 \ 'local class': 'class',
62 \ 'interface': 'interface',
63 \ 'type': 'class',
64 \ 'enum': 'enum',
65 \ 'enum member': 'enum_member',
66 \ 'var': 'variable',
67 \ 'local var': 'variable',
68 \ 'function': 'function',
69 \ 'local function': 'function',
70 \ 'method': 'method',
71 \ 'getter': 'property',
72 \ 'setter': 'method',
73 \ 'property': 'property',
74 \ 'constructor': 'constructor',
75 \ 'call': 'method',
76 \ 'index': 'index',
77 \ 'construct': 'constructor',
78 \ 'parameter': 'parameter',
79 \ 'type parameter': 'type_parameter',
80 \ 'primitive type': 'unit',
81 \ 'label': 'text',
82 \ 'alias': 'class',
83 \ 'const': 'constant',
84 \ 'let': 'variable',
85 \ 'directory': 'folder',
86 \ 'external module name': 'text',
87 \ 'JSX attribute': 'parameter',
88 \ 'string': 'text'
89 \ }
90
91 " For compatibility reasons, we only use built in VIM completion kinds
92 " See :help complete-items for Vim completion kinds
93 let g:ale_completion_symbols = get(g:, 'ale_completion_symbols', {
94 \ 'text': 'v',
95 \ 'method': 'f',
96 \ 'function': 'f',
97 \ 'constructor': 'f',
98 \ 'field': 'm',
99 \ 'variable': 'v',
100 \ 'class': 't',
101 \ 'interface': 't',
102 \ 'module': 'd',
103 \ 'property': 'm',
104 \ 'unit': 'v',
105 \ 'value': 'v',
106 \ 'enum': 't',
107 \ 'keyword': 'v',
108 \ 'snippet': 'v',
109 \ 'color': 'v',
110 \ 'file': 'v',
111 \ 'reference': 'v',
112 \ 'folder': 'v',
113 \ 'enum_member': 'm',
114 \ 'constant': 'm',
115 \ 'struct': 't',
116 \ 'event': 'v',
117 \ 'operator': 'f',
118 \ 'type_parameter': 'p',
119 \ '<default>': 'v'
120 \ })
121
122 let s:LSP_INSERT_TEXT_FORMAT_PLAIN = 1
123 let s:LSP_INSERT_TEXT_FORMAT_SNIPPET = 2
124
125 let s:lisp_regex = '\v[a-zA-Z_\-][a-zA-Z_\-0-9]*$'
126
127 " Regular expressions for checking the characters in the line before where
128 " the insert cursor is. If one of these matches, we'll check for completions.
129 let s:should_complete_map = {
130 \   '<default>': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$',
131 \   'clojure': s:lisp_regex,
132 \   'lisp': s:lisp_regex,
133 \   'racket': '\k\+$',
134 \   'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|''$|"$',
135 \   'rust': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$',
136 \   'cpp': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$|-\>$',
137 \   'c': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|-\>$',
138 \}
139
140 " Regular expressions for finding the start column to replace with completion.
141 let s:omni_start_map = {
142 \   '<default>': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$',
143 \   'racket': '\k\+$',
144 \}
145
146 " A map of exact characters for triggering LSP completions. Do not forget to
147 " update self.input_patterns in ale.py in updating entries in this map.
148 let s:trigger_character_map = {
149 \   '<default>': ['.'],
150 \   'typescript': ['.', '''', '"'],
151 \   'rust': ['.', '::'],
152 \   'cpp': ['.', '::', '->'],
153 \   'c': ['.', '->'],
154 \}
155
156 function! s:GetFiletypeValue(map, filetype) abort
157     for l:part in reverse(split(a:filetype, '\.'))
158         let l:regex = get(a:map, l:part, [])
159
160         if !empty(l:regex)
161             return l:regex
162         endif
163     endfor
164
165     " Use the default regex for other files.
166     return a:map['<default>']
167 endfunction
168
169 " Check if we should look for completions for a language.
170 function! ale#completion#GetPrefix(filetype, line, column) abort
171     let l:regex = s:GetFiletypeValue(s:should_complete_map, a:filetype)
172
173     " The column we're using completions for is where we are inserting text,
174     " like so:
175     "   abc
176     "      ^
177     " So we need check the text in the column before that position.
178     return matchstr(getline(a:line)[: a:column - 2], l:regex)
179 endfunction
180
181 function! ale#completion#GetTriggerCharacter(filetype, prefix) abort
182     if empty(a:prefix)
183         return ''
184     endif
185
186     let l:char_list = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
187
188     if index(l:char_list, a:prefix) >= 0
189         return a:prefix
190     endif
191
192     return ''
193 endfunction
194
195 function! ale#completion#Filter(
196 \   buffer,
197 \   filetype,
198 \   suggestions,
199 \   prefix,
200 \   exact_prefix_match,
201 \) abort
202     let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words')
203
204     if empty(a:prefix)
205         let l:filtered_suggestions = a:suggestions
206     else
207         let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
208
209         " For completing...
210         "   foo.
211         "       ^
212         " We need to include all of the given suggestions.
213         if index(l:triggers, a:prefix) >= 0 || empty(a:prefix)
214             let l:filtered_suggestions = a:suggestions
215         else
216             let l:filtered_suggestions = []
217
218             " Filter suggestions down to those starting with the prefix we
219             " used for finding suggestions in the first place.
220             "
221             " Some completion tools will include suggestions which don't even
222             " start with the characters we have already typed.
223             for l:item in a:suggestions
224                 " A List of String values or a List of completion item
225                 " Dictionaries is accepted here.
226                 let l:word = type(l:item) is v:t_string ? l:item : l:item.word
227
228                 if a:exact_prefix_match
229                     " Add suggestions if the word is an exact match.
230                     if l:word is# a:prefix
231                         call add(l:filtered_suggestions, l:item)
232                     endif
233                 else
234                     " Add suggestions if the suggestion starts with a
235                     " case-insensitive match for the prefix.
236                     if l:word[: len(a:prefix) - 1] is? a:prefix
237                         call add(l:filtered_suggestions, l:item)
238                     endif
239                 endif
240             endfor
241         endif
242     endif
243
244     if !empty(l:excluded_words)
245         " Copy the List if needed. We don't want to modify the argument.
246         " We shouldn't make a copy if we don't need to.
247         if l:filtered_suggestions is a:suggestions
248             let l:filtered_suggestions = copy(a:suggestions)
249         endif
250
251         " Remove suggestions with words in the exclusion List.
252         call filter(
253         \   l:filtered_suggestions,
254         \   'index(l:excluded_words, type(v:val) is v:t_string ? v:val : v:val.word) < 0',
255         \)
256     endif
257
258     return l:filtered_suggestions
259 endfunction
260
261 function! s:ReplaceCompletionOptions(source) abort
262     " Remember the old omnifunc value, if there is one.
263     " If we don't store an old one, we'll just never reset the option.
264     " This will stop some random exceptions from appearing.
265     if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc)
266         let b:ale_old_omnifunc = &l:omnifunc
267     endif
268
269     let &l:omnifunc = 'ale#completion#AutomaticOmniFunc'
270
271     if a:source is# 'ale-automatic'
272         if !exists('b:ale_old_completeopt')
273             let b:ale_old_completeopt = &l:completeopt
274         endif
275
276         let l:opt_list = split(&l:completeopt, ',')
277         " The menu and noinsert options must be set, or automatic completion
278         " will be annoying.
279         let l:new_opt_list = ['menu', 'menuone', 'noinsert']
280
281         " Permit some other completion options, provided users have set them.
282         for l:opt in ['preview', 'popup', 'noselect']
283             if index(l:opt_list, l:opt) >= 0
284                 call add(l:new_opt_list, l:opt)
285             endif
286         endfor
287
288         let &l:completeopt = join(l:new_opt_list, ',')
289     endif
290 endfunction
291
292 function! ale#completion#RestoreCompletionOptions() abort
293     " Reset settings when completion is done.
294     if exists('b:ale_old_omnifunc')
295         if b:ale_old_omnifunc isnot# 'pythoncomplete#Complete'
296             let &l:omnifunc = b:ale_old_omnifunc
297         endif
298
299         unlet b:ale_old_omnifunc
300     endif
301
302     if exists('b:ale_old_completeopt')
303         let &l:completeopt = b:ale_old_completeopt
304         unlet b:ale_old_completeopt
305     endif
306 endfunction
307
308 function! ale#completion#GetCompletionPosition() abort
309     if !exists('b:ale_completion_info')
310         return 0
311     endif
312
313     let l:line = b:ale_completion_info.line
314     let l:column = b:ale_completion_info.column
315     let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype)
316     let l:up_to_column = getline(l:line)[: l:column - 2]
317     let l:match = matchstr(l:up_to_column, l:regex)
318
319     return l:column - len(l:match) - 1
320 endfunction
321
322 function! ale#completion#GetCompletionPositionForDeoplete(input) abort
323     return match(a:input, '\k*$')
324 endfunction
325
326 function! ale#completion#GetCompletionResult() abort
327     if exists('b:ale_completion_result')
328         return b:ale_completion_result
329     endif
330
331     return v:null
332 endfunction
333
334 function! ale#completion#AutomaticOmniFunc(findstart, base) abort
335     if a:findstart
336         return ale#completion#GetCompletionPosition()
337     else
338         let l:result = ale#completion#GetCompletionResult()
339
340         let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
341
342         if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
343             call s:ReplaceCompletionOptions(l:source)
344         endif
345
346         return l:result isnot v:null ? l:result : []
347     endif
348 endfunction
349
350 function! s:OpenCompletionMenu(...) abort
351     if !&l:paste
352         call ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")
353     endif
354 endfunction
355
356 function! ale#completion#Show(result) abort
357     let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
358
359     if ale#util#Mode() isnot# 'i' && l:source isnot# 'ale-import'
360         return
361     endif
362
363     " Set the list in the buffer.
364     let b:ale_completion_result = a:result
365
366     " Don't try to open the completion menu if there's nothing to show.
367     if empty(b:ale_completion_result)
368         if l:source is# 'ale-import'
369             " If we ran completion from :ALEImport,
370             " tell the user that nothing is going to happen.
371             call s:message('No possible imports found.')
372         endif
373
374         return
375     endif
376
377     " Replace completion options shortly before opening the menu.
378     if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
379         call s:ReplaceCompletionOptions(l:source)
380
381         call timer_start(0, function('s:OpenCompletionMenu'))
382     endif
383
384     if l:source is# 'ale-callback'
385         call b:CompleteCallback(b:ale_completion_result)
386     endif
387
388     if l:source is# 'ale-import'
389         call ale#completion#HandleUserData(b:ale_completion_result[0])
390
391         let l:text_changed = '' . g:ale_lint_on_text_changed
392
393         " Check the buffer again right away, if linting is enabled.
394         if g:ale_enabled
395         \&& (
396         \   l:text_changed is# '1'
397         \   || g:ale_lint_on_text_changed is v:true
398         \   || l:text_changed is# 'always'
399         \   || l:text_changed is# 'normal'
400         \   || l:text_changed is# 'insert'
401         \)
402             call ale#Queue(0, '')
403         endif
404     endif
405 endfunction
406
407 function! ale#completion#GetAllTriggers() abort
408     return deepcopy(s:trigger_character_map)
409 endfunction
410
411 function! ale#completion#GetCompletionKind(kind) abort
412     let l:lsp_symbol = get(g:ale_lsp_types, a:kind, '')
413
414     if !empty(l:lsp_symbol)
415         return l:lsp_symbol
416     endif
417
418     return get(g:ale_tsserver_types, a:kind, '')
419 endfunction
420
421 function! ale#completion#GetCompletionSymbols(kind) abort
422     let l:kind = ale#completion#GetCompletionKind(a:kind)
423     let l:symbol = get(g:ale_completion_symbols, l:kind, '')
424
425     if !empty(l:symbol)
426         return l:symbol
427     endif
428
429     return get(g:ale_completion_symbols, '<default>', 'v')
430 endfunction
431
432 function! s:CompletionStillValid(request_id) abort
433     let [l:line, l:column] = getpos('.')[1:2]
434
435     return has_key(b:, 'ale_completion_info')
436     \&& (
437     \   ale#util#Mode() is# 'i'
438     \   || b:ale_completion_info.source is# 'ale-import'
439     \)
440     \&& b:ale_completion_info.request_id == a:request_id
441     \&& b:ale_completion_info.line == l:line
442     \&& (
443     \   b:ale_completion_info.column == l:column
444     \   || b:ale_completion_info.source is# 'ale-omnifunc'
445     \   || b:ale_completion_info.source is# 'ale-callback'
446     \   || b:ale_completion_info.source is# 'ale-import'
447     \)
448 endfunction
449
450 function! ale#completion#ParseTSServerCompletions(response) abort
451     let l:names = []
452
453     for l:suggestion in a:response.body
454         let l:kind = get(l:suggestion, 'kind', '')
455
456         if g:ale_completion_tsserver_remove_warnings == 0 || l:kind isnot# 'warning'
457             call add(l:names, {
458             \ 'word': l:suggestion.name,
459             \ 'source': get(l:suggestion, 'source', ''),
460             \})
461         endif
462     endfor
463
464     return l:names
465 endfunction
466
467 function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
468     let l:buffer = bufnr('')
469     let l:results = []
470     let l:names_with_details = []
471     let l:info = get(b:, 'ale_completion_info', {})
472
473     for l:suggestion in a:response.body
474         let l:displayParts = []
475         let l:local_name = v:null
476
477         for l:action in get(l:suggestion, 'codeActions', [])
478             call add(l:displayParts, l:action.description . ' ')
479         endfor
480
481         for l:part in l:suggestion.displayParts
482             " Stop on stop on line breaks for the menu.
483             if get(l:part, 'kind') is# 'lineBreak'
484                 break
485             endif
486
487             if get(l:part, 'kind') is# 'localName'
488                 let l:local_name = l:part.text
489             endif
490
491             call add(l:displayParts, l:part.text)
492         endfor
493
494         " Each one of these parts has 'kind' properties
495         let l:documentationParts = []
496
497         for l:part in get(l:suggestion, 'documentation', [])
498             call add(l:documentationParts, l:part.text)
499         endfor
500
501         " See :help complete-items
502         let l:result = {
503         \   'word': (
504         \       l:suggestion.name is# 'default'
505         \       && l:suggestion.kind is# 'alias'
506         \       && !empty(l:local_name)
507         \           ? l:local_name
508         \           : l:suggestion.name
509         \   ),
510         \   'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind),
511         \   'icase': 1,
512         \   'menu': join(l:displayParts, ''),
513         \   'dup': get(l:info, 'additional_edits_only', 0)
514         \       || (g:ale_completion_autoimport + 0),
515         \   'info': join(l:documentationParts, ''),
516         \}
517         " This flag is used to tell if this completion came from ALE or not.
518         let l:user_data = {'_ale_completion_item': 1}
519
520         if has_key(l:suggestion, 'codeActions')
521             let l:user_data.code_actions = l:suggestion.codeActions
522         endif
523
524         let l:result.user_data = json_encode(l:user_data)
525
526         " Include this item if we'll accept any items,
527         " or if we only want items with additional edits, and this has them.
528         if !get(l:info, 'additional_edits_only', 0)
529         \|| has_key(l:user_data, 'code_actions')
530             call add(l:results, l:result)
531         endif
532     endfor
533
534     let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', [])
535
536     if !empty(l:names) && len(l:names) != len(l:results)
537         let l:names_with_details = map(copy(l:results), 'v:val.word')
538         let l:missing_names = filter(
539         \   copy(l:names),
540         \   'index(l:names_with_details, v:val.word) < 0',
541         \)
542
543         for l:name in l:missing_names
544             call add(l:results, {
545             \   'word': l:name.word,
546             \   'kind': 'v',
547             \   'icase': 1,
548             \   'menu': '',
549             \   'info': '',
550             \   'user_data': json_encode({'_ale_completion_item': 1}),
551             \})
552         endfor
553     endif
554
555     return l:results
556 endfunction
557
558 function! ale#completion#NullFilter(buffer, item) abort
559     return 1
560 endfunction
561
562 function! ale#completion#ParseLSPCompletions(response) abort
563     let l:buffer = bufnr('')
564     let l:info = get(b:, 'ale_completion_info', {})
565     let l:Filter = get(l:info, 'completion_filter', v:null)
566
567     if l:Filter is v:null
568         let l:Filter = function('ale#completion#NullFilter')
569     else
570         let l:Filter = ale#util#GetFunction(l:Filter)
571     endif
572
573     let l:item_list = []
574
575     if type(get(a:response, 'result')) is v:t_list
576         let l:item_list = a:response.result
577     elseif type(get(a:response, 'result')) is v:t_dict
578     \&& type(get(a:response.result, 'items')) is v:t_list
579         let l:item_list = a:response.result.items
580     endif
581
582     let l:results = []
583
584     for l:item in l:item_list
585         if !call(l:Filter, [l:buffer, l:item])
586             continue
587         endif
588
589         if get(l:item, 'insertTextFormat', s:LSP_INSERT_TEXT_FORMAT_PLAIN) is s:LSP_INSERT_TEXT_FORMAT_PLAIN
590         \&& type(get(l:item, 'textEdit')) is v:t_dict
591             let l:text = l:item.textEdit.newText
592         elseif type(get(l:item, 'insertText')) is v:t_string
593             let l:text = l:item.insertText
594         else
595             let l:text = l:item.label
596         endif
597
598         let l:word = matchstr(l:text, '\v^[^(]+')
599
600         if empty(l:word)
601             continue
602         endif
603
604         " Don't use LSP items with additional text edits when autoimport for
605         " completions is turned off.
606         if !empty(get(l:item, 'additionalTextEdits'))
607         \&& !(
608         \   get(l:info, 'additional_edits_only', 0)
609         \   || g:ale_completion_autoimport
610         \)
611             continue
612         endif
613
614         let l:doc = get(l:item, 'documentation', '')
615
616         if type(l:doc) is v:t_dict && has_key(l:doc, 'value')
617             let l:doc = l:doc.value
618         endif
619
620         " Collapse whitespaces and line breaks into a single space.
621         let l:detail = substitute(get(l:item, 'detail', ''), '\_s\+', ' ', 'g')
622
623         let l:result = {
624         \   'word': l:word,
625         \   'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')),
626         \   'icase': 1,
627         \   'menu': l:detail,
628         \   'dup': get(l:info, 'additional_edits_only', 0)
629         \       || (g:ale_completion_autoimport + 0),
630         \   'info': (type(l:doc) is v:t_string ? l:doc : ''),
631         \}
632         " This flag is used to tell if this completion came from ALE or not.
633         let l:user_data = {'_ale_completion_item': 1}
634
635         if has_key(l:item, 'additionalTextEdits')
636         \ && l:item.additionalTextEdits isnot v:null
637             let l:text_changes = []
638
639             for l:edit in l:item.additionalTextEdits
640                 call add(l:text_changes, {
641                 \ 'start': {
642                 \   'line': l:edit.range.start.line + 1,
643                 \   'offset': l:edit.range.start.character + 1,
644                 \ },
645                 \ 'end': {
646                 \   'line': l:edit.range.end.line + 1,
647                 \   'offset': l:edit.range.end.character + 1,
648                 \ },
649                 \ 'newText': l:edit.newText,
650                 \})
651             endfor
652
653             if !empty(l:text_changes)
654                 let l:user_data.code_actions = [{
655                 \   'description': 'completion',
656                 \   'changes': [
657                 \       {
658                 \           'fileName': expand('#' . l:buffer . ':p'),
659                 \           'textChanges': l:text_changes,
660                 \       },
661                 \   ],
662                 \}]
663             endif
664         endif
665
666         let l:result.user_data = json_encode(l:user_data)
667
668         " Include this item if we'll accept any items,
669         " or if we only want items with additional edits, and this has them.
670         if !get(l:info, 'additional_edits_only', 0)
671         \|| has_key(l:user_data, 'code_actions')
672             call add(l:results, l:result)
673         endif
674     endfor
675
676     if has_key(l:info, 'prefix')
677         let l:results = ale#completion#Filter(
678         \   l:buffer,
679         \   &filetype,
680         \   l:results,
681         \   l:info.prefix,
682         \   get(l:info, 'additional_edits_only', 0),
683         \)
684     endif
685
686     return l:results[: g:ale_completion_max_suggestions - 1]
687 endfunction
688
689 function! ale#completion#HandleTSServerResponse(conn_id, response) abort
690     if !s:CompletionStillValid(get(a:response, 'request_seq'))
691         return
692     endif
693
694     if !has_key(a:response, 'body')
695         return
696     endif
697
698     let l:buffer = bufnr('')
699     let l:command = get(a:response, 'command', '')
700
701     if l:command is# 'completions'
702         let l:names = ale#completion#Filter(
703         \   l:buffer,
704         \   &filetype,
705         \   ale#completion#ParseTSServerCompletions(a:response),
706         \   b:ale_completion_info.prefix,
707         \   get(b:ale_completion_info, 'additional_edits_only', 0),
708         \)[: g:ale_completion_max_suggestions - 1]
709
710         " We need to remember some names for tsserver, as it doesn't send
711         " details back for everything we send.
712         call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names)
713
714         if empty(l:names)
715             " Response with no results now and skip making a redundant request
716             " for nothing.
717             call ale#completion#Show([])
718         else
719             let l:identifiers = []
720
721             for l:name in l:names
722                 let l:identifier = {
723                 \   'name': l:name.word,
724                 \}
725                 let l:source = get(l:name, 'source', '')
726
727                 " Empty source results in no details for the completed item
728                 if !empty(l:source)
729                     call extend(l:identifier, { 'source': l:source })
730                 endif
731
732                 call add(l:identifiers, l:identifier)
733             endfor
734
735             let b:ale_completion_info.request_id = ale#lsp#Send(
736             \   b:ale_completion_info.conn_id,
737             \   ale#lsp#tsserver_message#CompletionEntryDetails(
738             \       l:buffer,
739             \       b:ale_completion_info.line,
740             \       b:ale_completion_info.column,
741             \       l:identifiers,
742             \   ),
743             \)
744         endif
745     elseif l:command is# 'completionEntryDetails'
746         call ale#completion#Show(
747         \   ale#completion#ParseTSServerCompletionEntryDetails(a:response),
748         \)
749     endif
750 endfunction
751
752
753 function! ale#completion#HandleLSPResponse(conn_id, response) abort
754     if !s:CompletionStillValid(get(a:response, 'id'))
755         return
756     endif
757
758     call ale#completion#Show(
759     \   ale#completion#ParseLSPCompletions(a:response),
760     \)
761 endfunction
762
763 function! s:OnReady(linter, lsp_details) abort
764     let l:id = a:lsp_details.connection_id
765
766     if !ale#lsp#HasCapability(l:id, 'completion')
767         return
768     endif
769
770     let l:buffer = a:lsp_details.buffer
771
772     " If we have sent a completion request already, don't send another.
773     if b:ale_completion_info.request_id
774         return
775     endif
776
777     let l:Callback = a:linter.lsp is# 'tsserver'
778     \   ? function('ale#completion#HandleTSServerResponse')
779     \   : function('ale#completion#HandleLSPResponse')
780     call ale#lsp#RegisterCallback(l:id, l:Callback)
781
782     if a:linter.lsp is# 'tsserver'
783         let l:message = ale#lsp#tsserver_message#Completions(
784         \   l:buffer,
785         \   b:ale_completion_info.line,
786         \   b:ale_completion_info.column,
787         \   b:ale_completion_info.prefix,
788         \   (
789         \       get(b:ale_completion_info, 'additional_edits_only', 0)
790         \       || g:ale_completion_autoimport
791         \   ) ? v:true : v:false,
792         \)
793     else
794         " Send a message saying the buffer has changed first, otherwise
795         " completions won't know what text is nearby.
796         call ale#lsp#NotifyForChanges(l:id, l:buffer)
797
798         " For LSP completions, we need to clamp the column to the length of
799         " the line. python-language-server and perhaps others do not implement
800         " this correctly.
801         let l:message = ale#lsp#message#Completion(
802         \   l:buffer,
803         \   b:ale_completion_info.line,
804         \   b:ale_completion_info.column,
805         \   ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
806         \)
807     endif
808
809     let l:request_id = ale#lsp#Send(l:id, l:message)
810
811     if l:request_id
812         let b:ale_completion_info.conn_id = l:id
813         let b:ale_completion_info.request_id = l:request_id
814
815         if has_key(a:linter, 'completion_filter')
816             let b:ale_completion_info.completion_filter = a:linter.completion_filter
817         endif
818     endif
819 endfunction
820
821 " This function can be called to check if ALE can provide completion data for
822 " the current buffer. 1 will be returned if there's a potential source of
823 " completion data ALE can use, and 0 will be returned otherwise.
824 function! ale#completion#CanProvideCompletions() abort
825     " NOTE: We can report that ALE can provide completions to Deoplete from
826     " here, and we might ignore linters still below.
827     for l:linter in ale#linter#Get(&filetype)
828         if !empty(l:linter.lsp)
829             return 1
830         endif
831     endfor
832
833     return 0
834 endfunction
835
836 " This function can be used to manually trigger autocomplete, even when
837 " g:ale_completion_enabled is set to false
838 function! ale#completion#GetCompletions(...) abort
839     let l:source = get(a:000, 0, '')
840     let l:options = get(a:000, 1, {})
841
842     if len(a:000) > 2
843         throw 'Too many arguments!'
844     endif
845
846     let l:CompleteCallback = get(l:options, 'callback', v:null)
847
848     if l:CompleteCallback isnot v:null
849         let b:CompleteCallback = l:CompleteCallback
850     endif
851
852     if has_key(l:options, 'line') && has_key(l:options, 'column')
853         " Use a provided line and column, if given.
854         let l:line = l:options.line
855         let l:column = l:options.column
856     else
857         let [l:line, l:column] = getpos('.')[1:2]
858     endif
859
860     if has_key(l:options, 'prefix')
861         let l:prefix = l:options.prefix
862     else
863         let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column)
864     endif
865
866     if l:source is# 'ale-automatic' && empty(l:prefix)
867         return 0
868     endif
869
870     let l:line_length = len(getline('.'))
871
872     let b:ale_completion_info = {
873     \   'line': l:line,
874     \   'line_length': l:line_length,
875     \   'column': l:column,
876     \   'prefix': l:prefix,
877     \   'conn_id': 0,
878     \   'request_id': 0,
879     \   'source': l:source,
880     \}
881     unlet! b:ale_completion_result
882
883     if has_key(l:options, 'additional_edits_only')
884         let b:ale_completion_info.additional_edits_only =
885         \   l:options.additional_edits_only
886     endif
887
888     let l:buffer = bufnr('')
889     let l:Callback = function('s:OnReady')
890
891     let l:started = 0
892
893     for l:linter in ale#lsp_linter#GetEnabled(l:buffer)
894         if ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback)
895             let l:started = 1
896         endif
897     endfor
898
899     return l:started
900 endfunction
901
902 function! s:message(message) abort
903     call ale#util#Execute('echom ' . string(a:message))
904 endfunction
905
906 " This function implements the :ALEImport command.
907 function! ale#completion#Import() abort
908     let l:word = expand('<cword>')
909
910     if empty(l:word)
911         call s:message('Nothing to complete at cursor!')
912
913         return
914     endif
915
916     let [l:line, l:column] = getpos('.')[1:2]
917     let l:column = searchpos('\V' . escape(l:word, '/\'), 'bnc', l:line)[1]
918     let l:column = l:column + len(l:word) - 1
919
920     if l:column isnot 0
921         let l:started = ale#completion#GetCompletions('ale-import', {
922         \   'line': l:line,
923         \   'column': l:column,
924         \   'prefix': l:word,
925         \   'additional_edits_only': 1,
926         \})
927
928         if !l:started
929             call s:message('No completion providers are available.')
930         endif
931     endif
932 endfunction
933
934 function! ale#completion#OmniFunc(findstart, base) abort
935     if a:findstart
936         let l:started = ale#completion#GetCompletions('ale-omnifunc')
937
938         if !l:started
939             " This is the special value for cancelling completions silently.
940             " See :help complete-functions
941             return -3
942         endif
943
944         return ale#completion#GetCompletionPosition()
945     else
946         let l:result = ale#completion#GetCompletionResult()
947
948         while l:result is v:null && !complete_check()
949             sleep 2ms
950             let l:result = ale#completion#GetCompletionResult()
951         endwhile
952
953         return l:result isnot v:null ? l:result : []
954     endif
955 endfunction
956
957 function! s:TimerHandler(...) abort
958     if !get(b:, 'ale_completion_enabled', g:ale_completion_enabled)
959         return
960     endif
961
962     let s:timer_id = -1
963
964     let [l:line, l:column] = getpos('.')[1:2]
965
966     " When running the timer callback, we have to be sure that the cursor
967     " hasn't moved from where it was when we requested completions by typing.
968     if s:timer_pos == [l:line, l:column] && ale#util#Mode() is# 'i'
969         call ale#completion#GetCompletions('ale-automatic')
970     endif
971 endfunction
972
973 " Stop any completion timer that is queued. This is useful for tests.
974 function! ale#completion#StopTimer() abort
975     if s:timer_id != -1
976         call timer_stop(s:timer_id)
977     endif
978
979     let s:timer_id = -1
980 endfunction
981
982 function! ale#completion#Queue() abort
983     if !get(b:, 'ale_completion_enabled', g:ale_completion_enabled)
984         return
985     endif
986
987     let s:timer_pos = getpos('.')[1:2]
988
989     if s:timer_pos == s:last_done_pos
990         " Do not ask for completions if the cursor rests on the position we
991         " last completed on.
992         return
993     endif
994
995     " If we changed the text again while we're still waiting for a response,
996     " then invalidate the requests before the timer ticks again.
997     if exists('b:ale_completion_info')
998         let b:ale_completion_info.request_id = 0
999     endif
1000
1001     call ale#completion#StopTimer()
1002
1003     let s:timer_id = timer_start(g:ale_completion_delay, function('s:TimerHandler'))
1004 endfunction
1005
1006 function! ale#completion#HandleUserData(completed_item) abort
1007     let l:user_data_json = get(a:completed_item, 'user_data', '')
1008     let l:user_data = type(l:user_data_json) is v:t_dict
1009     \   ? l:user_data_json
1010     \   : ale#util#FuzzyJSONDecode(l:user_data_json, {})
1011
1012     if !has_key(l:user_data, '_ale_completion_item')
1013         return
1014     endif
1015
1016     let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
1017
1018     if l:source is# 'ale-automatic'
1019     \|| l:source is# 'ale-manual'
1020     \|| l:source is# 'ale-callback'
1021     \|| l:source is# 'ale-import'
1022     \|| l:source is# 'ale-omnifunc'
1023         for l:code_action in get(l:user_data, 'code_actions', [])
1024             call ale#code_action#HandleCodeAction(l:code_action, {})
1025         endfor
1026     endif
1027
1028     silent doautocmd <nomodeline> User ALECompletePost
1029 endfunction
1030
1031 function! ale#completion#Done() abort
1032     silent! pclose
1033
1034     call ale#completion#RestoreCompletionOptions()
1035
1036     let s:last_done_pos = getpos('.')[1:2]
1037 endfunction
1038
1039 augroup ALECompletionActions
1040     autocmd!
1041
1042     autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item)
1043 augroup END
1044
1045 function! s:Setup(enabled) abort
1046     augroup ALECompletionGroup
1047         autocmd!
1048
1049         if a:enabled
1050             autocmd TextChangedI * call ale#completion#Queue()
1051             autocmd CompleteDone * call ale#completion#Done()
1052         endif
1053     augroup END
1054
1055     if !a:enabled
1056         augroup! ALECompletionGroup
1057     endif
1058 endfunction
1059
1060 function! ale#completion#Enable() abort
1061     let g:ale_completion_enabled = 1
1062     call s:Setup(1)
1063 endfunction
1064
1065 function! ale#completion#Disable() abort
1066     let g:ale_completion_enabled = 0
1067     call s:Setup(0)
1068 endfunction