Add some experimental completion code for tsserver

This commit is contained in:
w0rp 2017-07-01 01:22:03 +01:00
parent 0d8be55c51
commit 9f21e45156
5 changed files with 262 additions and 11 deletions

192
autoload/ale/completion.vim Normal file
View 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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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'])