Add some experimental completion code for tsserver
This commit is contained in:
parent
0d8be55c51
commit
9f21e45156
192
autoload/ale/completion.vim
Normal file
192
autoload/ale/completion.vim
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
" Author: w0rp <devw0rp@gmail.com>
|
||||||
|
" Description: Completion support for LSP linters
|
||||||
|
|
||||||
|
let s:timer = -1
|
||||||
|
let s:delay = 300
|
||||||
|
let s:max_suggestions = 20
|
||||||
|
let s:buffer_completion_map = {}
|
||||||
|
|
||||||
|
function! s:RememberCompletionInfo(buffer, executable, request_id, line, column) abort
|
||||||
|
let s:buffer_completion_map[a:buffer] = {
|
||||||
|
\ 'executable': a:executable,
|
||||||
|
\ 'request_id': a:request_id,
|
||||||
|
\ 'line': a:line,
|
||||||
|
\ 'column': a:column,
|
||||||
|
\}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" Find completion information for a response, and delete the information
|
||||||
|
" if the request failed.
|
||||||
|
function! s:FindCompletionInfo(response) abort
|
||||||
|
let l:matched_buffer = -1
|
||||||
|
let l:matched_data = {}
|
||||||
|
|
||||||
|
for l:key in keys(s:buffer_completion_map)
|
||||||
|
let l:obj = s:buffer_completion_map[l:key]
|
||||||
|
|
||||||
|
if l:obj.request_id ==# a:response.request_seq
|
||||||
|
if get(a:response, 'success')
|
||||||
|
let l:matched_buffer = str2nr(l:key)
|
||||||
|
let l:matched_data = l:obj
|
||||||
|
else
|
||||||
|
" Clean up the data we remembered if the request failed.
|
||||||
|
call remove(s:buffer_completion_map, l:matched_buffer)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
|
||||||
|
return [l:matched_buffer, l:matched_data]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:HandleCompletions(response) abort
|
||||||
|
let [l:buffer, l:info] = s:FindCompletionInfo(a:response)
|
||||||
|
|
||||||
|
if l:buffer >= 0
|
||||||
|
let l:names = []
|
||||||
|
|
||||||
|
for l:suggestion in a:response.body[: s:max_suggestions]
|
||||||
|
call add(l:names, l:suggestion.name)
|
||||||
|
endfor
|
||||||
|
|
||||||
|
let l:request_id = ale#lsp#SendMessageToProgram(
|
||||||
|
\ l:info.executable,
|
||||||
|
\ ale#lsp#tsserver_message#CompletionEntryDetails(
|
||||||
|
\ l:buffer,
|
||||||
|
\ l:info.line,
|
||||||
|
\ l:info.column,
|
||||||
|
\ l:names,
|
||||||
|
\ ),
|
||||||
|
\)
|
||||||
|
|
||||||
|
if l:request_id
|
||||||
|
let l:info.request_id = l:request_id
|
||||||
|
else
|
||||||
|
" Remove the info now if we failed to start the request.
|
||||||
|
call remove(s:buffer_completion_map, l:buffer)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:HandleCompletionDetails(response) abort
|
||||||
|
let [l:buffer, l:info] = s:FindCompletionInfo(a:response)
|
||||||
|
|
||||||
|
if l:buffer >= 0
|
||||||
|
call remove(s:buffer_completion_map, l:buffer)
|
||||||
|
|
||||||
|
let l:name_list = []
|
||||||
|
|
||||||
|
for l:suggestion in a:response.body[: s:max_suggestions]
|
||||||
|
" Each suggestion has 'kind' and 'kindModifier' properties
|
||||||
|
" which could be useful.
|
||||||
|
" Each one of these parts has 'kind' properties
|
||||||
|
let l:displayParts = []
|
||||||
|
|
||||||
|
for l:part in l:suggestion.displayParts
|
||||||
|
call add(l:displayParts, l:part.text)
|
||||||
|
endfor
|
||||||
|
|
||||||
|
" Each one of these parts has 'kind' properties
|
||||||
|
let l:documentationParts = []
|
||||||
|
|
||||||
|
for l:part in l:suggestion.documentation
|
||||||
|
call add(l:documentationParts, l:part.text)
|
||||||
|
endfor
|
||||||
|
|
||||||
|
let l:text = l:suggestion.name
|
||||||
|
\ . ' - '
|
||||||
|
\ . join(l:displayParts, '')
|
||||||
|
\ . (!empty(l:documentationParts) ? ' ' : '')
|
||||||
|
\ . join(l:documentationParts, '')
|
||||||
|
|
||||||
|
call add(l:name_list, l:text)
|
||||||
|
endfor
|
||||||
|
|
||||||
|
echom string(l:name_list)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:HandleLSPResponse(response) abort
|
||||||
|
let l:command = get(a:response, 'command', '')
|
||||||
|
|
||||||
|
if l:command ==# 'completions'
|
||||||
|
call s:HandleCompletions(a:response)
|
||||||
|
elseif l:command ==# 'completionEntryDetails'
|
||||||
|
call s:HandleCompletionDetails(a:response)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:GetCompletionsForTSServer(buffer, linter, line, column) abort
|
||||||
|
let l:executable = has_key(a:linter, 'executable_callback')
|
||||||
|
\ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer)
|
||||||
|
\ : a:linter.executable
|
||||||
|
let l:command = l:executable
|
||||||
|
|
||||||
|
let l:job_id = ale#lsp#StartProgram(
|
||||||
|
\ l:executable,
|
||||||
|
\ l:executable,
|
||||||
|
\ function('s:HandleLSPResponse')
|
||||||
|
\)
|
||||||
|
|
||||||
|
if !l:job_id
|
||||||
|
if g:ale_history_enabled
|
||||||
|
call ale#history#Add(a:buffer, 'failed', l:job_id, l:command)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
if ale#lsp#OpenTSServerDocumentIfNeeded(l:executable, a:buffer)
|
||||||
|
if g:ale_history_enabled
|
||||||
|
call ale#history#Add(a:buffer, 'started', l:job_id, l:command)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
call ale#lsp#SendMessageToProgram(
|
||||||
|
\ l:executable,
|
||||||
|
\ ale#lsp#tsserver_message#Change(a:buffer),
|
||||||
|
\)
|
||||||
|
|
||||||
|
let l:request_id = ale#lsp#SendMessageToProgram(
|
||||||
|
\ l:executable,
|
||||||
|
\ ale#lsp#tsserver_message#Completions(a:buffer, a:line, a:column),
|
||||||
|
\)
|
||||||
|
|
||||||
|
if l:request_id
|
||||||
|
call s:RememberCompletionInfo(
|
||||||
|
\ a:buffer,
|
||||||
|
\ l:executable,
|
||||||
|
\ l:request_id,
|
||||||
|
\ a:line,
|
||||||
|
\ a:column,
|
||||||
|
\)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ale#completion#GetCompletions() abort
|
||||||
|
let l:buffer = bufnr('')
|
||||||
|
let [l:line, l:column] = getcurpos()[1:2]
|
||||||
|
|
||||||
|
for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype'))
|
||||||
|
if l:linter.lsp ==# 'tsserver'
|
||||||
|
call s:GetCompletionsForTSServer(l:buffer, l:linter, l:line, l:column)
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:TimerHandler(...) abort
|
||||||
|
call ale#completion#GetCompletions()
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ale#completion#Queue() abort
|
||||||
|
if s:timer != -1
|
||||||
|
call timer_stop(s:timer)
|
||||||
|
let s:timer = -1
|
||||||
|
endif
|
||||||
|
|
||||||
|
let s:timer = timer_start(s:delay, function('s:TimerHandler'))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ale#completion#Start() abort
|
||||||
|
augroup ALECompletionGroup
|
||||||
|
autocmd!
|
||||||
|
autocmd TextChangedI * call ale#completion#Queue()
|
||||||
|
augroup END
|
||||||
|
endfunction
|
@ -42,7 +42,6 @@ function! ale#engine#InitBufferInfo(buffer) abort
|
|||||||
\ 'temporary_file_list': [],
|
\ 'temporary_file_list': [],
|
||||||
\ 'temporary_directory_list': [],
|
\ 'temporary_directory_list': [],
|
||||||
\ 'history': [],
|
\ 'history': [],
|
||||||
\ 'open_lsp_documents': [],
|
|
||||||
\}
|
\}
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
@ -563,8 +562,6 @@ endfunction
|
|||||||
|
|
||||||
function! s:CheckWithTSServer(buffer, linter, executable) abort
|
function! s:CheckWithTSServer(buffer, linter, executable) abort
|
||||||
let l:info = g:ale_buffer_info[a:buffer]
|
let l:info = g:ale_buffer_info[a:buffer]
|
||||||
let l:open_documents = l:info.open_lsp_documents
|
|
||||||
let l:is_open = index(l:open_documents, a:linter.name) >= 0
|
|
||||||
|
|
||||||
let l:command = ale#job#PrepareCommand(a:executable)
|
let l:command = ale#job#PrepareCommand(a:executable)
|
||||||
let l:job_id = ale#lsp#StartProgram(a:executable, l:command, function('s:HandleLSPResponse'))
|
let l:job_id = ale#lsp#StartProgram(a:executable, l:command, function('s:HandleLSPResponse'))
|
||||||
@ -577,16 +574,10 @@ function! s:CheckWithTSServer(buffer, linter, executable) abort
|
|||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if !l:is_open
|
if ale#lsp#OpenTSServerDocumentIfNeeded(a:executable, a:buffer)
|
||||||
if g:ale_history_enabled
|
if g:ale_history_enabled
|
||||||
call ale#history#Add(a:buffer, 'started', l:job_id, l:command)
|
call ale#history#Add(a:buffer, 'started', l:job_id, l:command)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call add(l:open_documents, a:linter.name)
|
|
||||||
call ale#lsp#SendMessageToProgram(
|
|
||||||
\ a:executable,
|
|
||||||
\ ale#lsp#tsserver_message#Open(a:buffer),
|
|
||||||
\)
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call ale#lsp#SendMessageToProgram(
|
call ale#lsp#SendMessageToProgram(
|
||||||
|
@ -16,6 +16,7 @@ function! s:NewConnection() abort
|
|||||||
\ 'address': '',
|
\ 'address': '',
|
||||||
\ 'executable': '',
|
\ 'executable': '',
|
||||||
\ 'job_id': -1,
|
\ 'job_id': -1,
|
||||||
|
\ 'open_documents': [],
|
||||||
\}
|
\}
|
||||||
|
|
||||||
call add(s:connections, l:conn)
|
call add(s:connections, l:conn)
|
||||||
@ -283,3 +284,21 @@ function! ale#lsp#SendMessageToAddress(address, message) abort
|
|||||||
|
|
||||||
return l:id == 0 ? -1 : l:id
|
return l:id == 0 ? -1 : l:id
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! ale#lsp#OpenTSServerDocumentIfNeeded(executable, buffer) abort
|
||||||
|
let l:opened = 0
|
||||||
|
let l:matches = filter(s:connections[:], 'v:val.executable ==# a:executable')
|
||||||
|
|
||||||
|
" Send the command for opening the document only if needed.
|
||||||
|
if !empty(l:matches) && index(l:matches[0].open_documents, a:buffer) < 0
|
||||||
|
call ale#lsp#SendMessageToProgram(
|
||||||
|
\ a:executable,
|
||||||
|
\ ale#lsp#tsserver_message#Open(a:buffer),
|
||||||
|
\)
|
||||||
|
call add(l:matches[0].open_documents, a:buffer)
|
||||||
|
|
||||||
|
let l:opened = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
return l:opened
|
||||||
|
endfunction
|
||||||
|
@ -26,7 +26,7 @@ function! ale#lsp#tsserver_message#Change(buffer) abort
|
|||||||
\ 'file': expand('#' . a:buffer . ':p'),
|
\ 'file': expand('#' . a:buffer . ':p'),
|
||||||
\ 'line': 1,
|
\ 'line': 1,
|
||||||
\ 'offset': 1,
|
\ 'offset': 1,
|
||||||
\ 'endLine': 1073741824 ,
|
\ 'endLine': 1073741824,
|
||||||
\ 'endOffset': 1,
|
\ 'endOffset': 1,
|
||||||
\ 'insertString': join(l:lines, "\n"),
|
\ 'insertString': join(l:lines, "\n"),
|
||||||
\}]
|
\}]
|
||||||
@ -35,3 +35,21 @@ endfunction
|
|||||||
function! ale#lsp#tsserver_message#Geterr(buffer) abort
|
function! ale#lsp#tsserver_message#Geterr(buffer) abort
|
||||||
return [1, 'ts@geterr', {'files': [expand('#' . a:buffer . ':p')]}]
|
return [1, 'ts@geterr', {'files': [expand('#' . a:buffer . ':p')]}]
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! ale#lsp#tsserver_message#Completions(buffer, line, column) abort
|
||||||
|
" An optional 'prefix' key can be added here for a completion prefix.
|
||||||
|
return [0, 'ts@completions', {
|
||||||
|
\ 'line': a:line,
|
||||||
|
\ 'offset': a:column,
|
||||||
|
\ 'file': expand('#' . a:buffer . ':p'),
|
||||||
|
\}]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ale#lsp#tsserver_message#CompletionEntryDetails(buffer, line, column, entry_names) abort
|
||||||
|
return [0, 'ts@completionEntryDetails', {
|
||||||
|
\ 'line': a:line,
|
||||||
|
\ 'offset': a:column,
|
||||||
|
\ 'file': expand('#' . a:buffer . ':p'),
|
||||||
|
\ 'entryNames': a:entry_names,
|
||||||
|
\}]
|
||||||
|
endfunction
|
||||||
|
@ -146,3 +146,34 @@ Execute(ale#lsp#tsserver_message#Geterr() should return correct messages):
|
|||||||
\ }
|
\ }
|
||||||
\ ],
|
\ ],
|
||||||
\ ale#lsp#tsserver_message#Geterr(bufnr(''))
|
\ ale#lsp#tsserver_message#Geterr(bufnr(''))
|
||||||
|
|
||||||
|
Execute(ale#lsp#tsserver_message#Completions() should return correct messages):
|
||||||
|
silent! noautocmd file foo.ts
|
||||||
|
|
||||||
|
AssertEqual
|
||||||
|
\ [
|
||||||
|
\ 0,
|
||||||
|
\ 'ts@completions',
|
||||||
|
\ {
|
||||||
|
\ 'file': b:dir . '/foo.ts',
|
||||||
|
\ 'line': 347,
|
||||||
|
\ 'offset': 12,
|
||||||
|
\ }
|
||||||
|
\ ],
|
||||||
|
\ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12)
|
||||||
|
|
||||||
|
Execute(ale#lsp#tsserver_message#CompletionEntryDetails() should return correct messages):
|
||||||
|
silent! noautocmd file foo.ts
|
||||||
|
|
||||||
|
AssertEqual
|
||||||
|
\ [
|
||||||
|
\ 0,
|
||||||
|
\ 'ts@completionEntryDetails',
|
||||||
|
\ {
|
||||||
|
\ 'file': b:dir . '/foo.ts',
|
||||||
|
\ 'line': 347,
|
||||||
|
\ 'offset': 12,
|
||||||
|
\ 'entryNames': ['foo', 'bar'],
|
||||||
|
\ }
|
||||||
|
\ ],
|
||||||
|
\ ale#lsp#tsserver_message#CompletionEntryDetails(bufnr(''), 347, 12, ['foo', 'bar'])
|
||||||
|
Loading…
Reference in New Issue
Block a user