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.
1 " Author: w0rp <devw0rp@gmail.com>
2 " Description: Completion support for LSP linters
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>
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)
23 let s:last_done_pos = []
25 " CompletionItemKind values from the LSP protocol.
26 let g:ale_lsp_types = {
51 \ 25: 'type_parameter',
54 " from https://github.com/microsoft/TypeScript/blob/29becf05012bfa7ba20d50b0d16813971e46b8a6/lib/protocol.d.ts#L2472
55 let g:ale_tsserver_types = {
57 \ 'keyword': 'keyword',
61 \ 'local class': 'class',
62 \ 'interface': 'interface',
65 \ 'enum member': 'enum_member',
67 \ 'local var': 'variable',
68 \ 'function': 'function',
69 \ 'local function': 'function',
71 \ 'getter': 'property',
73 \ 'property': 'property',
74 \ 'constructor': 'constructor',
77 \ 'construct': 'constructor',
78 \ 'parameter': 'parameter',
79 \ 'type parameter': 'type_parameter',
80 \ 'primitive type': 'unit',
83 \ 'const': 'constant',
85 \ 'directory': 'folder',
86 \ 'external module name': 'text',
87 \ 'JSX attribute': 'parameter',
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', {
113 \ 'enum_member': 'm',
118 \ 'type_parameter': 'p',
122 let s:LSP_INSERT_TEXT_FORMAT_PLAIN = 1
123 let s:LSP_INSERT_TEXT_FORMAT_SNIPPET = 2
125 let s:lisp_regex = '\v[a-zA-Z_\-][a-zA-Z_\-0-9]*$'
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,
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]*$|\.$|-\>$',
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]*$',
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': ['.', '::', '->'],
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, [])
165 " Use the default regex for other files.
166 return a:map['<default>']
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)
173 " The column we're using completions for is where we are inserting text,
177 " So we need check the text in the column before that position.
178 return matchstr(getline(a:line)[: a:column - 2], l:regex)
181 function! ale#completion#GetTriggerCharacter(filetype, prefix) abort
186 let l:char_list = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
188 if index(l:char_list, a:prefix) >= 0
195 function! ale#completion#Filter(
200 \ exact_prefix_match,
202 let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words')
205 let l:filtered_suggestions = a:suggestions
207 let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype)
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
216 let l:filtered_suggestions = []
218 " Filter suggestions down to those starting with the prefix we
219 " used for finding suggestions in the first place.
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
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)
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)
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)
251 " Remove suggestions with words in the exclusion List.
253 \ l:filtered_suggestions,
254 \ 'index(l:excluded_words, type(v:val) is v:t_string ? v:val : v:val.word) < 0',
258 return l:filtered_suggestions
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
269 let &l:omnifunc = 'ale#completion#AutomaticOmniFunc'
271 if a:source is# 'ale-automatic'
272 if !exists('b:ale_old_completeopt')
273 let b:ale_old_completeopt = &l:completeopt
276 let l:opt_list = split(&l:completeopt, ',')
277 " The menu and noinsert options must be set, or automatic completion
279 let l:new_opt_list = ['menu', 'menuone', 'noinsert']
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)
288 let &l:completeopt = join(l:new_opt_list, ',')
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
299 unlet b:ale_old_omnifunc
302 if exists('b:ale_old_completeopt')
303 let &l:completeopt = b:ale_old_completeopt
304 unlet b:ale_old_completeopt
308 function! ale#completion#GetCompletionPosition() abort
309 if !exists('b:ale_completion_info')
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)
319 return l:column - len(l:match) - 1
322 function! ale#completion#GetCompletionPositionForDeoplete(input) abort
323 return match(a:input, '\k*$')
326 function! ale#completion#GetCompletionResult() abort
327 if exists('b:ale_completion_result')
328 return b:ale_completion_result
334 function! ale#completion#AutomaticOmniFunc(findstart, base) abort
336 return ale#completion#GetCompletionPosition()
338 let l:result = ale#completion#GetCompletionResult()
340 let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
342 if l:source is# 'ale-automatic' || l:source is# 'ale-manual'
343 call s:ReplaceCompletionOptions(l:source)
346 return l:result isnot v:null ? l:result : []
350 function! s:OpenCompletionMenu(...) abort
352 call ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")
356 function! ale#completion#Show(result) abort
357 let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
359 if ale#util#Mode() isnot# 'i' && l:source isnot# 'ale-import'
363 " Set the list in the buffer.
364 let b:ale_completion_result = a:result
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.')
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)
381 call timer_start(0, function('s:OpenCompletionMenu'))
384 if l:source is# 'ale-callback'
385 call b:CompleteCallback(b:ale_completion_result)
388 if l:source is# 'ale-import'
389 call ale#completion#HandleUserData(b:ale_completion_result[0])
391 let l:text_changed = '' . g:ale_lint_on_text_changed
393 " Check the buffer again right away, if linting is enabled.
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'
402 call ale#Queue(0, '')
407 function! ale#completion#GetAllTriggers() abort
408 return deepcopy(s:trigger_character_map)
411 function! ale#completion#GetCompletionKind(kind) abort
412 let l:lsp_symbol = get(g:ale_lsp_types, a:kind, '')
414 if !empty(l:lsp_symbol)
418 return get(g:ale_tsserver_types, a:kind, '')
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, '')
429 return get(g:ale_completion_symbols, '<default>', 'v')
432 function! s:CompletionStillValid(request_id) abort
433 let [l:line, l:column] = getpos('.')[1:2]
435 return has_key(b:, 'ale_completion_info')
437 \ ale#util#Mode() is# 'i'
438 \ || b:ale_completion_info.source is# 'ale-import'
440 \&& b:ale_completion_info.request_id == a:request_id
441 \&& b:ale_completion_info.line == l:line
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'
450 function! ale#completion#ParseTSServerCompletions(response) abort
453 for l:suggestion in a:response.body
454 let l:kind = get(l:suggestion, 'kind', '')
456 if g:ale_completion_tsserver_remove_warnings == 0 || l:kind isnot# 'warning'
458 \ 'word': l:suggestion.name,
459 \ 'source': get(l:suggestion, 'source', ''),
467 function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort
468 let l:buffer = bufnr('')
470 let l:names_with_details = []
471 let l:info = get(b:, 'ale_completion_info', {})
473 for l:suggestion in a:response.body
474 let l:displayParts = []
475 let l:local_name = v:null
477 for l:action in get(l:suggestion, 'codeActions', [])
478 call add(l:displayParts, l:action.description . ' ')
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'
487 if get(l:part, 'kind') is# 'localName'
488 let l:local_name = l:part.text
491 call add(l:displayParts, l:part.text)
494 " Each one of these parts has 'kind' properties
495 let l:documentationParts = []
497 for l:part in get(l:suggestion, 'documentation', [])
498 call add(l:documentationParts, l:part.text)
501 " See :help complete-items
504 \ l:suggestion.name is# 'default'
505 \ && l:suggestion.kind is# 'alias'
506 \ && !empty(l:local_name)
508 \ : l:suggestion.name
510 \ 'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind),
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, ''),
517 " This flag is used to tell if this completion came from ALE or not.
518 let l:user_data = {'_ale_completion_item': 1}
520 if has_key(l:suggestion, 'codeActions')
521 let l:user_data.code_actions = l:suggestion.codeActions
524 let l:result.user_data = json_encode(l:user_data)
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)
534 let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', [])
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(
540 \ 'index(l:names_with_details, v:val.word) < 0',
543 for l:name in l:missing_names
544 call add(l:results, {
545 \ 'word': l:name.word,
550 \ 'user_data': json_encode({'_ale_completion_item': 1}),
558 function! ale#completion#NullFilter(buffer, item) abort
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)
567 if l:Filter is v:null
568 let l:Filter = function('ale#completion#NullFilter')
570 let l:Filter = ale#util#GetFunction(l:Filter)
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
584 for l:item in l:item_list
585 if !call(l:Filter, [l:buffer, l:item])
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
595 let l:text = l:item.label
598 let l:word = matchstr(l:text, '\v^[^(]+')
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'))
608 \ get(l:info, 'additional_edits_only', 0)
609 \ || g:ale_completion_autoimport
614 let l:doc = get(l:item, 'documentation', '')
616 if type(l:doc) is v:t_dict && has_key(l:doc, 'value')
617 let l:doc = l:doc.value
620 " Collapse whitespaces and line breaks into a single space.
621 let l:detail = substitute(get(l:item, 'detail', ''), '\_s\+', ' ', 'g')
625 \ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')),
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 : ''),
632 " This flag is used to tell if this completion came from ALE or not.
633 let l:user_data = {'_ale_completion_item': 1}
635 if has_key(l:item, 'additionalTextEdits')
636 \ && l:item.additionalTextEdits isnot v:null
637 let l:text_changes = []
639 for l:edit in l:item.additionalTextEdits
640 call add(l:text_changes, {
642 \ 'line': l:edit.range.start.line + 1,
643 \ 'offset': l:edit.range.start.character + 1,
646 \ 'line': l:edit.range.end.line + 1,
647 \ 'offset': l:edit.range.end.character + 1,
649 \ 'newText': l:edit.newText,
653 if !empty(l:text_changes)
654 let l:user_data.code_actions = [{
655 \ 'description': 'completion',
658 \ 'fileName': expand('#' . l:buffer . ':p'),
659 \ 'textChanges': l:text_changes,
666 let l:result.user_data = json_encode(l:user_data)
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)
676 if has_key(l:info, 'prefix')
677 let l:results = ale#completion#Filter(
682 \ get(l:info, 'additional_edits_only', 0),
686 return l:results[: g:ale_completion_max_suggestions - 1]
689 function! ale#completion#HandleTSServerResponse(conn_id, response) abort
690 if !s:CompletionStillValid(get(a:response, 'request_seq'))
694 if !has_key(a:response, 'body')
698 let l:buffer = bufnr('')
699 let l:command = get(a:response, 'command', '')
701 if l:command is# 'completions'
702 let l:names = ale#completion#Filter(
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]
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)
715 " Response with no results now and skip making a redundant request
717 call ale#completion#Show([])
719 let l:identifiers = []
721 for l:name in l:names
723 \ 'name': l:name.word,
725 let l:source = get(l:name, 'source', '')
727 " Empty source results in no details for the completed item
729 call extend(l:identifier, { 'source': l:source })
732 call add(l:identifiers, l:identifier)
735 let b:ale_completion_info.request_id = ale#lsp#Send(
736 \ b:ale_completion_info.conn_id,
737 \ ale#lsp#tsserver_message#CompletionEntryDetails(
739 \ b:ale_completion_info.line,
740 \ b:ale_completion_info.column,
745 elseif l:command is# 'completionEntryDetails'
746 call ale#completion#Show(
747 \ ale#completion#ParseTSServerCompletionEntryDetails(a:response),
753 function! ale#completion#HandleLSPResponse(conn_id, response) abort
754 if !s:CompletionStillValid(get(a:response, 'id'))
758 call ale#completion#Show(
759 \ ale#completion#ParseLSPCompletions(a:response),
763 function! s:OnReady(linter, lsp_details) abort
764 let l:id = a:lsp_details.connection_id
766 if !ale#lsp#HasCapability(l:id, 'completion')
770 let l:buffer = a:lsp_details.buffer
772 " If we have sent a completion request already, don't send another.
773 if b:ale_completion_info.request_id
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)
782 if a:linter.lsp is# 'tsserver'
783 let l:message = ale#lsp#tsserver_message#Completions(
785 \ b:ale_completion_info.line,
786 \ b:ale_completion_info.column,
787 \ b:ale_completion_info.prefix,
789 \ get(b:ale_completion_info, 'additional_edits_only', 0)
790 \ || g:ale_completion_autoimport
791 \ ) ? v:true : v:false,
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)
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
801 let l:message = ale#lsp#message#Completion(
803 \ b:ale_completion_info.line,
804 \ b:ale_completion_info.column,
805 \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix),
809 let l:request_id = ale#lsp#Send(l:id, l:message)
812 let b:ale_completion_info.conn_id = l:id
813 let b:ale_completion_info.request_id = l:request_id
815 if has_key(a:linter, 'completion_filter')
816 let b:ale_completion_info.completion_filter = a:linter.completion_filter
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)
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, {})
843 throw 'Too many arguments!'
846 let l:CompleteCallback = get(l:options, 'callback', v:null)
848 if l:CompleteCallback isnot v:null
849 let b:CompleteCallback = l:CompleteCallback
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
857 let [l:line, l:column] = getpos('.')[1:2]
860 if has_key(l:options, 'prefix')
861 let l:prefix = l:options.prefix
863 let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column)
866 if l:source is# 'ale-automatic' && empty(l:prefix)
870 let l:line_length = len(getline('.'))
872 let b:ale_completion_info = {
874 \ 'line_length': l:line_length,
875 \ 'column': l:column,
876 \ 'prefix': l:prefix,
879 \ 'source': l:source,
881 unlet! b:ale_completion_result
883 if has_key(l:options, 'additional_edits_only')
884 let b:ale_completion_info.additional_edits_only =
885 \ l:options.additional_edits_only
888 let l:buffer = bufnr('')
889 let l:Callback = function('s:OnReady')
893 for l:linter in ale#lsp_linter#GetEnabled(l:buffer)
894 if ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback)
902 function! s:message(message) abort
903 call ale#util#Execute('echom ' . string(a:message))
906 " This function implements the :ALEImport command.
907 function! ale#completion#Import() abort
908 let l:word = expand('<cword>')
911 call s:message('Nothing to complete at cursor!')
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
921 let l:started = ale#completion#GetCompletions('ale-import', {
923 \ 'column': l:column,
925 \ 'additional_edits_only': 1,
929 call s:message('No completion providers are available.')
934 function! ale#completion#OmniFunc(findstart, base) abort
936 let l:started = ale#completion#GetCompletions('ale-omnifunc')
939 " This is the special value for cancelling completions silently.
940 " See :help complete-functions
944 return ale#completion#GetCompletionPosition()
946 let l:result = ale#completion#GetCompletionResult()
948 while l:result is v:null && !complete_check()
950 let l:result = ale#completion#GetCompletionResult()
953 return l:result isnot v:null ? l:result : []
957 function! s:TimerHandler(...) abort
958 if !get(b:, 'ale_completion_enabled', g:ale_completion_enabled)
964 let [l:line, l:column] = getpos('.')[1:2]
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')
973 " Stop any completion timer that is queued. This is useful for tests.
974 function! ale#completion#StopTimer() abort
976 call timer_stop(s:timer_id)
982 function! ale#completion#Queue() abort
983 if !get(b:, 'ale_completion_enabled', g:ale_completion_enabled)
987 let s:timer_pos = getpos('.')[1:2]
989 if s:timer_pos == s:last_done_pos
990 " Do not ask for completions if the cursor rests on the position we
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
1001 call ale#completion#StopTimer()
1003 let s:timer_id = timer_start(g:ale_completion_delay, function('s:TimerHandler'))
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, {})
1012 if !has_key(l:user_data, '_ale_completion_item')
1016 let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '')
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, {})
1028 silent doautocmd <nomodeline> User ALECompletePost
1031 function! ale#completion#Done() abort
1034 call ale#completion#RestoreCompletionOptions()
1036 let s:last_done_pos = getpos('.')[1:2]
1039 augroup ALECompletionActions
1042 autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item)
1045 function! s:Setup(enabled) abort
1046 augroup ALECompletionGroup
1050 autocmd TextChangedI * call ale#completion#Queue()
1051 autocmd CompleteDone * call ale#completion#Done()
1056 augroup! ALECompletionGroup
1060 function! ale#completion#Enable() abort
1061 let g:ale_completion_enabled = 1
1065 function! ale#completion#Disable() abort
1066 let g:ale_completion_enabled = 0