You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
941 lines
33 KiB
VimL
941 lines
33 KiB
VimL
"============================================================================
|
|
" FILE: tsuquyomi.vim
|
|
" AUTHOR: Quramy <yosuke.kurami@gmail.com>
|
|
"============================================================================
|
|
|
|
scriptencoding utf-8
|
|
|
|
let s:save_cpo = &cpo
|
|
set cpo&vim
|
|
|
|
if !tsuquyomi#config#preconfig()
|
|
finish
|
|
endif
|
|
|
|
let s:script_dir = expand('<sfile>:p:h')
|
|
|
|
let s:V = vital#of('tsuquyomi')
|
|
let s:JSON = s:V.import('Web.JSON')
|
|
let s:Filepath = s:V.import('System.Filepath')
|
|
|
|
let s:is_vim8 = !has('nvim') && has('patch-8.0.1')
|
|
|
|
if !s:is_vim8 || g:tsuquyomi_use_vimproc
|
|
let s:P = s:V.import('ProcessManager')
|
|
let s:tsq = 'tsuquyomiTSServer'
|
|
else
|
|
let s:tsq = {'job':0}
|
|
endif
|
|
|
|
let s:request_seq = 0
|
|
|
|
let s:ignore_response_conditions = []
|
|
" ignore events configFileDiag triggered by reload event. See also #99
|
|
call add(s:ignore_response_conditions, '"type":"event","event":"configFileDiag"')
|
|
call add(s:ignore_response_conditions, '"type":"event","event":"telemetry"')
|
|
call add(s:ignore_response_conditions, '"type":"event","event":"projectsUpdatedInBackground"')
|
|
call add(s:ignore_response_conditions, '"type":"event","event":"typingsInstallerPid"')
|
|
call add(s:ignore_response_conditions, 'npm notice created a lockfile')
|
|
|
|
" ### Async variables
|
|
let s:callbacks = {}
|
|
let s:notify_callback = {}
|
|
let s:quickfix_list = []
|
|
" ### }}}
|
|
|
|
" ### Utilites {{{
|
|
function! s:error(msg)
|
|
echoerr (a:msg)
|
|
endfunction
|
|
|
|
function! s:debugLog(msg)
|
|
if g:tsuquyomi_debug
|
|
echom a:msg
|
|
endif
|
|
endfunction
|
|
|
|
" ### Utilites }}}
|
|
|
|
" ### Core Functions {{{
|
|
"
|
|
" If not exsiting process of TSServer, create it.
|
|
function! s:startTssVimproc()
|
|
if s:P.state(s:tsq) == 'existing'
|
|
return 'existing'
|
|
endif
|
|
let l:cmd = substitute(tsuquyomi#config#tsscmd(), '\\', '\\\\', 'g').' '.tsuquyomi#config#tssargs()
|
|
let l:is_new = s:P.touch(s:tsq, l:cmd)
|
|
if l:is_new == 'new'
|
|
let [out, err, type] = s:P.read_wait(s:tsq, 0.1, [])
|
|
let st = tsuquyomi#tsClient#statusTss()
|
|
if !g:tsuquyomi_tsserver_debug
|
|
if err != ''
|
|
call s:error('Fail to start TSServer... '.err)
|
|
endif
|
|
endif
|
|
endif
|
|
return l:is_new
|
|
endfunction
|
|
|
|
function! s:startTssVim8()
|
|
if type(s:tsq['job']) == 8 && job_info(s:tsq['job']).status == 'run'
|
|
return 'existing'
|
|
endif
|
|
let l:cmd = substitute(tsuquyomi#config#tsscmd(), '\\', '\\\\', 'g').' '.tsuquyomi#config#tssargs()
|
|
try
|
|
let s:tsq['job'] = job_start(l:cmd, {
|
|
\ 'out_cb': {ch, msg -> tsuquyomi#tsClient#handleMessage(ch, msg)},
|
|
\ })
|
|
|
|
let s:tsq['channel'] = job_getchannel(s:tsq['job'])
|
|
|
|
let out = ch_readraw(s:tsq['channel'])
|
|
let st = tsuquyomi#tsClient#statusTss()
|
|
if !g:tsuquyomi_tsserver_debug
|
|
if err != ''
|
|
call s:error('Fail to start TSServer... '.err)
|
|
return 0
|
|
endif
|
|
endif
|
|
catch
|
|
return 0
|
|
endtry
|
|
return 1
|
|
endfunction
|
|
|
|
function! s:getEventType(item)
|
|
if type(a:item) == v:t_dict
|
|
\ && has_key(a:item, 'type')
|
|
\ && a:item.type ==# 'event'
|
|
\ && (a:item.event ==# 'syntaxDiag'
|
|
\ || a:item.event ==# 'semanticDiag'
|
|
\ || a:item.event ==# 'requestCompleted')
|
|
return 'diagnostics'
|
|
endif
|
|
return 0
|
|
endfunction
|
|
|
|
|
|
function! tsuquyomi#tsClient#startTss()
|
|
if !s:is_vim8 || g:tsuquyomi_use_vimproc
|
|
return s:startTssVimproc()
|
|
else
|
|
return s:startTssVim8()
|
|
endif
|
|
endfunction
|
|
|
|
"
|
|
"Terminate TSServer process if it exsits.
|
|
function! tsuquyomi#tsClient#stopTss()
|
|
if tsuquyomi#tsClient#statusTss() != 'dead'
|
|
if !s:is_vim8 || g:tsuquyomi_use_vimproc
|
|
let l:res = s:P.term(s:tsq)
|
|
return l:res
|
|
else
|
|
let l:res = job_stop(s:tsq['job'])
|
|
return l:res
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#stopTssSync() abort
|
|
let res = tsuquyomi#tsClient#stopTss()
|
|
call s:ensureStatus('dead')
|
|
return res
|
|
endfunction
|
|
|
|
" Wait for the status to become the argument.
|
|
" Note: It throws an error to avoid infinite loop after 1s.
|
|
function! s:ensureStatus(expected) abort
|
|
let cnt = 0
|
|
while v:true
|
|
let got = tsuquyomi#tsClient#statusTss()
|
|
if got ==# a:expected
|
|
return
|
|
endif
|
|
if cnt > 100
|
|
throw "TSServer status does not become " . a:expected . " in 1s. It is " . got . "."
|
|
endif
|
|
let cnt += 1
|
|
sleep 10m
|
|
endwhile
|
|
endfunction
|
|
|
|
" RETURNS: {string} 'run' or 'dead'
|
|
function! tsuquyomi#tsClient#statusTss()
|
|
try
|
|
if !s:is_vim8 || g:tsuquyomi_use_vimproc
|
|
let stat = s:P.state(s:tsq)
|
|
if stat == 'undefined'
|
|
return 'dead'
|
|
elseif stat == 'reading'
|
|
return 'run'
|
|
else
|
|
return stat
|
|
endif
|
|
else
|
|
return job_info(s:tsq['job']).status
|
|
endif
|
|
catch
|
|
return 'dead'
|
|
endtry
|
|
endfunction
|
|
|
|
"
|
|
"Read diagnostics and add to QuickList.
|
|
"
|
|
" PARAM: {dict} response
|
|
function! tsuquyomi#tsClient#readDiagnostics(item)
|
|
if a:item.event == 'requestCompleted'
|
|
if has_key(s:notify_callback, 'diagnostics')
|
|
let Callback = function(s:notify_callback['diagnostics'], [s:quickfix_list])
|
|
call Callback()
|
|
let s:quickfix_list = []
|
|
let s:request_seq = s:request_seq + 1
|
|
endif
|
|
else
|
|
" Cache syntaxDiag and semanticDiag messages until request was completed.
|
|
let l:qflist = tsuquyomi#parseDiagnosticEvent(a:item, [])
|
|
let s:quickfix_list += l:qflist
|
|
endif
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#registerNotify(callback, key)
|
|
let s:notify_callback[a:key] = a:callback
|
|
endfunction
|
|
|
|
"
|
|
" Handle TSServer responses.
|
|
"
|
|
function! tsuquyomi#tsClient#handleMessage(ch, msg)
|
|
if type(a:msg) != 1 || a:msg == ''
|
|
" Not a string or blank message.
|
|
return
|
|
endif
|
|
let l:res_item = substitute(a:msg, 'Content-Length: \d\+', '', 'g')
|
|
if l:res_item == ''
|
|
" Ignore content-length.
|
|
return
|
|
endif
|
|
" Ignore messages.
|
|
let l:to_be_ignored = 0
|
|
for ignore_reg in s:ignore_response_conditions
|
|
let l:to_be_ignored = l:to_be_ignored || (l:res_item =~ ignore_reg)
|
|
if l:to_be_ignored
|
|
return
|
|
endif
|
|
endfor
|
|
let l:item = json_decode(l:res_item)
|
|
let l:eventName = s:getEventType(l:item)
|
|
|
|
if(has_key(s:callbacks, l:eventName))
|
|
let Callback = function(s:callbacks[l:eventName], [l:item])
|
|
call Callback()
|
|
endif
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#clearCallbacks()
|
|
let s:callbacks = {}
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#registerCallback(callback, eventName)
|
|
let s:callbacks[a:eventName] = a:callback
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#sendAsyncRequest(line)
|
|
if s:is_vim8 && g:tsuquyomi_use_vimproc == 0
|
|
call tsuquyomi#tsClient#startTss()
|
|
call ch_sendraw(s:tsq['channel'], a:line . "\n")
|
|
endif
|
|
endfunction
|
|
|
|
"
|
|
"Write to stdin of tsserver proc, and return stdout.
|
|
"
|
|
" PARAM: {string} line Stdin input.
|
|
" PARAM: {float} delay Wait time(sec) after request, until response.
|
|
" PARAM: {int} retry_count Retry count.
|
|
" PARAM: {int} response_length The number of JSONs contained by this response.
|
|
" RETURNS: {list<dict>} A list of response.
|
|
function! tsuquyomi#tsClient#sendRequest(line, delay, retry_count, response_length)
|
|
"call s:debugLog('called! '.a:line)
|
|
call tsuquyomi#tsClient#startTss()
|
|
if !s:is_vim8 || g:tsuquyomi_use_vimproc
|
|
call s:P.writeln(s:tsq, a:line)
|
|
else
|
|
call ch_sendraw(s:tsq['channel'], a:line."\n")
|
|
endif
|
|
|
|
let l:retry = 0
|
|
let response_list = []
|
|
|
|
while len(response_list) < a:response_length
|
|
if !s:is_vim8 || g:tsuquyomi_use_vimproc
|
|
let [out, err, type] = s:P.read_wait(s:tsq, a:delay, ['Content-Length: \d\+'])
|
|
call s:debugLog('out: '.out.', type:'.type)
|
|
if type == 'timedout'
|
|
let retry_delay = 0.05
|
|
while l:retry < a:retry_count
|
|
let [out, err, type] = s:P.read_wait(s:tsq, retry_delay, ['Content-Length: \d\+'])
|
|
if type == 'matched'
|
|
call tsuquyomi#perfLogger#record('tssMatched')
|
|
"call s:debugLog('retry: '.l:retry.', length: '.len(response_list))
|
|
break
|
|
endif
|
|
let l:retry = l:retry + 1
|
|
call tsuquyomi#perfLogger#record('tssRetry:'.l:retry)
|
|
endwhile
|
|
endif
|
|
else
|
|
let out = ch_readraw(s:tsq['channel'])
|
|
let type = 'matched'
|
|
endif
|
|
if type == 'matched'
|
|
let l:tmp1 = substitute(out, 'Content-Length: \d\+', '', 'g')
|
|
let l:tmp2 = substitute(l:tmp1, '\r', '', 'g')
|
|
let l:res_list = split(l:tmp2, '\n\+')
|
|
for res_item in l:res_list
|
|
let l:to_be_ignored = 0
|
|
for ignore_reg in s:ignore_response_conditions + ['"type":"event","event":"requestCompleted"']
|
|
let l:to_be_ignored = l:to_be_ignored || (res_item =~ ignore_reg)
|
|
if l:to_be_ignored
|
|
break
|
|
endif
|
|
endfor
|
|
let l:decoded_res_item = s:JSON.decode(res_item)
|
|
let l:to_be_ignored = l:to_be_ignored || (has_key(l:decoded_res_item, 'request_seq') && l:decoded_res_item.request_seq != s:request_seq)
|
|
if !l:to_be_ignored
|
|
call add(response_list, decoded_res_item)
|
|
endif
|
|
endfor
|
|
else
|
|
echom '[Tsuquyomi] TSServer request was timeout:'.a:line
|
|
return response_list
|
|
endif
|
|
|
|
endwhile
|
|
"call s:debugLog(a:response_length.', '.len(response_list))
|
|
let s:request_seq = s:request_seq + 1
|
|
return response_list
|
|
endfunction
|
|
|
|
"
|
|
" Send a command to TSServer.
|
|
" This function is called pseudo synchronously.
|
|
" PARAM: {string} cmd Command type. e.g. 'completion', etc...
|
|
" PARAM: {dictionary} args Arguments object. e.g. {'file': 'myApp.ts'}.
|
|
" RETURNS: {list<dictionary>}
|
|
function! tsuquyomi#tsClient#sendCommandSyncResponse(cmd, args)
|
|
let l:input = s:JSON.encode({'command': a:cmd, 'arguments': a:args, 'type': 'request', 'seq': s:request_seq})
|
|
call tsuquyomi#perfLogger#record('beforeCmd:'.a:cmd)
|
|
let l:stdout_list = tsuquyomi#tsClient#sendRequest(l:input, str2float("0.0001"), 1000, 1)
|
|
call tsuquyomi#perfLogger#record('afterCmd:'.a:cmd)
|
|
let l:length = len(l:stdout_list)
|
|
if l:length == 1
|
|
"if res.success == 0
|
|
" echom '[Tsuquyomi] TSServer command fail. command: '.res.command.', message: '.res.message
|
|
"endif
|
|
call tsuquyomi#perfLogger#record('afterDecode:'.a:cmd)
|
|
return l:stdout_list
|
|
else
|
|
return []
|
|
endif
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#sendCommandSyncEvents(cmd, args, delay, length)
|
|
let l:input = s:JSON.encode({'command': a:cmd, 'arguments': a:args, 'type': 'request', 'seq': s:request_seq})
|
|
let l:stdout_list = tsuquyomi#tsClient#sendRequest(l:input, a:delay, 2000, a:length)
|
|
"echo l:stdout_list
|
|
let l:length = len(l:stdout_list)
|
|
let l:result_list = []
|
|
if l:length > 0
|
|
for res in l:stdout_list
|
|
if res.type != 'event'
|
|
"echom '[Tsuquyomi] TSServer return invalid response: '.string(res)
|
|
else
|
|
call add(l:result_list, res)
|
|
endif
|
|
endfor
|
|
return l:result_list
|
|
else
|
|
return []
|
|
endif
|
|
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#sendCommandOneWay(cmd, args)
|
|
let l:input = s:JSON.encode({'command': a:cmd, 'arguments': a:args, 'type': 'request', 'seq': s:request_seq})
|
|
call tsuquyomi#tsClient#sendRequest(l:input, str2float("0.01"), 0, 0)
|
|
return []
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#getResponseBodyAsList(responses)
|
|
if len(a:responses) != 1
|
|
return []
|
|
endif
|
|
let response = a:responses[0]
|
|
if has_key(response, 'success') && response.success && has_key(response, 'body')
|
|
return response.body
|
|
else
|
|
return []
|
|
endif
|
|
endfunction
|
|
|
|
function! tsuquyomi#tsClient#getResponseBodyAsDict(responses)
|
|
if len(a:responses) != 1
|
|
return {}
|
|
endif
|
|
let response = a:responses[0]
|
|
if has_key(response, 'success') && response.success && has_key(response, 'body')
|
|
return response.body
|
|
else
|
|
return {}
|
|
endif
|
|
endfunction
|
|
|
|
"
|
|
" Send a command to TSServer.
|
|
" This function is called asynchronously.
|
|
" PARAM: {string} cmd Command type. e.g. 'completion', etc...
|
|
" PARAM: {dictionary} args Arguments object. e.g. {'file': 'myApp.ts'}.
|
|
function! tsuquyomi#tsClient#sendCommandAsyncEvents(cmd, args)
|
|
let s:quickfix_list = []
|
|
let l:input = json_encode({'command': a:cmd, 'arguments': a:args, 'type': 'request', 'seq': s:request_seq})
|
|
" call tsuquyomi#perfLogger#record('beforeCmd:'.a:cmd)
|
|
call tsuquyomi#tsClient#sendAsyncRequest(l:input)
|
|
endfunction
|
|
|
|
"
|
|
" ### Core Functions }}}
|
|
|
|
" ### TSServer command wrappers {{{
|
|
"
|
|
" There are client's methods of TSServer.
|
|
" If you want to know details of APIs, see 'typescript/src/server/protocol.d.ts'.
|
|
|
|
"
|
|
" Send oepn command to TSServer.
|
|
" This command does not return any response.
|
|
function! tsuquyomi#tsClient#tsOpen(file)
|
|
let l:args = {'file': a:file}
|
|
call tsuquyomi#tsClient#sendCommandOneWay('open', l:args)
|
|
endfunction
|
|
|
|
" Send close command to TSServer.
|
|
" This command does not return any response.
|
|
function! tsuquyomi#tsClient#tsClose(file)
|
|
let l:args = {'file': a:file}
|
|
return tsuquyomi#tsClient#sendCommandOneWay('close', l:args)
|
|
endfunction
|
|
|
|
" Save an opened file to tmpfile.
|
|
" This function can be called for only debugging.
|
|
" This command does not return any response.
|
|
function! tsuquyomi#tsClient#tsSaveto(file, tmpfile)
|
|
let l:args = {'file': a:file, 'tmpfile': a:tmpfile}
|
|
return tsuquyomi#tsClient#sendCommandOneWay('saveto', l:args)
|
|
endfunction
|
|
|
|
" Fetch keywards to complete from TSServer.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {string} line The line number of location to complete.
|
|
" PARAM: {string} offset The col number of location to complete.
|
|
" PARAM: {string} prefix Prefix to filter result set.
|
|
" RETURNS: {list} A List of completion info Dictionary.
|
|
" e.g. :
|
|
" [
|
|
" {'name': 'close', 'kindModifiers': 'declare', 'kind': 'function'},
|
|
" {'name': 'clipboardData', 'kindModifiers': 'declare', 'kind': 'var'}
|
|
" ]
|
|
function! tsuquyomi#tsClient#tsCompletions(file, line, offset, prefix)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset, 'prefix': a:prefix}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('completions', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Emmit to change file to TSServer.
|
|
" Param: {string} file File name to change.
|
|
" Param: {int} line The line number of starting point of range to change.
|
|
" Param: {int} offset The col number of starting point of range to change.
|
|
" Param: {int} endLine The line number of end point of range to change.
|
|
" Param: {int} endOffset The col number of end point of range to change.
|
|
" Param: {string} insertString String after replacing
|
|
" This command does not return any response.
|
|
function! tsuquyomi#tsClient#tsChange(file, line, offset, endLine, endOffset, insertString)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset, 'endLine': a:endLine, 'endOffset': a:endOffset, 'insertString': a:insertString}
|
|
return tsuquyomi#tsClient#sendCommandOneWay('change', l:args)
|
|
endfunction
|
|
|
|
" Fetch details of completion from TSServer.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of location to complete.
|
|
" PARAM: {int} offset The col number of location to complete.
|
|
" PARAM: {list<string>} entryNames A list of names. These names may be fetched by tsuquyomi#tsClient#tsCompletions function.
|
|
" RETURNS: {list} A list of details.
|
|
" e.g. :
|
|
" [{
|
|
" 'name': 'DOMError',
|
|
" 'kind': 'var',
|
|
" 'kindModifier': 'declare',
|
|
" 'displayParts': [
|
|
" {'kind': 'keyword', 'text': 'interface'},
|
|
" {'kind': 'space', 'text': ' '},
|
|
" ...
|
|
" {'kind': 'lineBreak', 'text': '\n'},
|
|
" ...
|
|
" ]
|
|
" }, ...]
|
|
function! tsuquyomi#tsClient#tsCompletionEntryDetails(file, line, offset, entryNames)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset, 'entryNames': a:entryNames}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('completionEntryDetails', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Configure editor parameter
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {string} hostInfo Information of Vim
|
|
" PARAM: {dict} formatOptions Options for editor. See the following FormatCodeSetting interface.
|
|
" PARAM: {list} extraFileExtensions List of extensions
|
|
"
|
|
" interface FormatCodeSetting {
|
|
" baseIndentSize?: number;
|
|
" indentSize?: number;
|
|
" tabSize?: number;
|
|
" newLineCharacter?: string;
|
|
" convertTabsToSpaces?: boolean;
|
|
" indentStyle?: IndentStyle | ts.IndentStyle;
|
|
" insertSpaceAfterCommaDelimiter?: boolean;
|
|
" insertSpaceAfterSemicolonInForStatements?: boolean;
|
|
" insertSpaceBeforeAndAfterBinaryOperators?: boolean;
|
|
" insertSpaceAfterConstructor?: boolean;
|
|
" insertSpaceAfterKeywordsInControlFlowStatements?: boolean;
|
|
" insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean;
|
|
" insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean;
|
|
" insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean;
|
|
" insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
|
|
" insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean;
|
|
" insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean;
|
|
" insertSpaceBeforeFunctionParenthesis?: boolean;
|
|
" placeOpenBraceOnNewLineForFunctions?: boolean;
|
|
" placeOpenBraceOnNewLineForControlBlocks?: boolean;
|
|
" }
|
|
let s:NON_BOOLEAN_KEYS_IN_FOPT = [ 'baseIndentSize', 'indentSize', 'tabSize', 'newLineCharacter', 'convertTabsToSpaces', 'indentStyle' ]
|
|
function! tsuquyomi#tsClient#tsConfigure(file, hostInfo, formatOptions, extraFileExtensions)
|
|
let fopt = { }
|
|
for k in keys(a:formatOptions)
|
|
if index(s:NON_BOOLEAN_KEYS_IN_FOPT, k) != -1
|
|
let fopt[k] = a:formatOptions[k]
|
|
else
|
|
let fopt[k] = a:formatOptions[k] ? s:JSON.true : s.JSON.false
|
|
endif
|
|
endfor
|
|
let l:args = {
|
|
\ 'file': a:file,
|
|
\ 'hostInfo': a:hostInfo,
|
|
\ 'formatOptions': fopt,
|
|
\ 'extraFileExtensions': a:extraFileExtensions
|
|
\ }
|
|
return tsuquyomi#tsClient#sendCommandOneWay('configure', l:args)
|
|
endfunction
|
|
|
|
" Fetch location where the symbol at cursor(line, offset) in file is defined.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of location to complete.
|
|
" PARAM: {int} offset The col number of location to complete.
|
|
" RETURNS: {list<dict>} A list of dictionaries of definition location.
|
|
" e.g. :
|
|
" [{'file': 'hogehoge.ts', 'start': {'line': 3, 'offset': 2}, 'end': {'line': 3, 'offset': 10}}]
|
|
function! tsuquyomi#tsClient#tsDefinition(file, line, offset)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('definition', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Format = "format";
|
|
function! tsuquyomi#tsClient#tsFormat(file, line, offset, endLine, endOffset)
|
|
call s:error('not implemented!')
|
|
endfunction
|
|
|
|
" Formatonkey = "formatonkey";
|
|
function! tsuquyomi#tsClient#tsFormationkey(file, line, offset, key)
|
|
call s:error('not implemented!')
|
|
endfunction
|
|
|
|
" Get error for files.
|
|
" PARAM: {list<string>} files List of filename
|
|
" PARAM: {int} delay Delay time [msec].
|
|
" PARAM: {list<dict>} error event list
|
|
function! tsuquyomi#tsClient#tsGeterr(files, delay)
|
|
let l:args = {'files': a:files, 'delay': a:delay}
|
|
let l:delaySec = a:delay * 1.0 / 1000.0
|
|
let l:typeCount = tsuquyomi#config#isHigher(280) ? 3 : 2
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncEvents('geterr', l:args, l:delaySec, len(a:files) * l:typeCount)
|
|
return l:result
|
|
endfunction
|
|
|
|
" Get errors for project.
|
|
" This command is available only at tsserver ~v.1.6
|
|
" PARAM: {string} file File name in target project.
|
|
" PARAM: {int} delay Delay time [msec].
|
|
" PARAM: {count} count The number of files in the project(you can fetch this from tsProjectInfo).
|
|
" PARAM: {list<dict>} error event list
|
|
function! tsuquyomi#tsClient#tsGeterrForProject(file, delay, count)
|
|
let l:args = {'file': a:file, 'delay': a:delay}
|
|
let l:delaySec = a:delay * 1.0 / 1000.0
|
|
let l:typeCount = tsuquyomi#config#isHigher(280) ? 3 : 2
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncEvents('geterrForProject', l:args, l:delaySec, a:count * l:typeCount)
|
|
return l:result
|
|
endfunction
|
|
|
|
" Fetch a list of implementations of an interface.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of the symbol's position.
|
|
" PARAM: {int} offset The col number of the symbol's position.
|
|
" RETURNS: {list<dict>} Reference information.
|
|
" e.g. :
|
|
" [
|
|
" {
|
|
" 'file': 'SomeClass.ts',
|
|
" 'start': {'offset': 11, 'line': 23}, 'end': {'offset': 5, 'line': 35}
|
|
" }, {
|
|
" 'file': 'OtherClass.ts',
|
|
" 'start': {'offset': 31, 'line': 26}, 'end': {'offset': 68, 'line': 26}
|
|
" }
|
|
" ]
|
|
function! tsuquyomi#tsClient#tsImplementation(file, line, offset)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('implementation', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Fetch navigation list from TSServer.
|
|
" PARAM: {string} file File name.
|
|
" RETURNS: {list<dict>} Navigation info
|
|
" e.g. :
|
|
" [{
|
|
" 'text': 'ModName',
|
|
" 'kind': 'module',
|
|
" 'kindModifiers: '',
|
|
" 'spans': [{
|
|
" 'start': {'line': 1, 'offset': 5},
|
|
" 'end': {'line': 1, 'offset': 12},
|
|
" }],
|
|
" childItems: [
|
|
" ... " REMAKS: childItems contains a recursive structure.
|
|
" ]
|
|
" }]
|
|
function! tsuquyomi#tsClient#tsNavBar(file)
|
|
let l:args = {'file': a:file}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('navbar', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Navto = "navto";
|
|
function! tsuquyomi#tsClient#tsNavto(file, searchValue, maxResultCount)
|
|
let l:args = {'file': a:file, 'searchValue': a:searchValue, 'maxResultCount': a:maxResultCount}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('navto', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Fetch quickinfo from TSServer.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of the symbol's position.
|
|
" PARAM: {int} offset The col number of the symbol's position.
|
|
" RETURNS: {dict}
|
|
" e.g. :
|
|
" {
|
|
" 'kind': 'method',
|
|
" 'kindModifiers': '',
|
|
" 'displayString': '(method) SimpleModule.MyClass.say(): string',
|
|
" 'start': {'line': 2, 'offset': 2},
|
|
" 'start': {'line': 2, 'offset': 9}
|
|
" }
|
|
function! tsuquyomi#tsClient#tsQuickinfo(file, line, offset)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('quickinfo', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsDict(l:result)
|
|
endfunction
|
|
|
|
" Fetch a list of references.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of the symbol's position.
|
|
" PARAM: {int} offset The col number of the symbol's position.
|
|
" RETURNS: {dictionary} Reference information.
|
|
" e.g. :
|
|
" {
|
|
" 'symbolName': 'SomeClass',
|
|
" 'symbolDisplayString': 'SomeModule.SomeClass',
|
|
" 'refs': [
|
|
" {
|
|
" 'file': 'SomeClass.ts', 'isWriteAccess': 1,
|
|
" 'start': {'line': 3', 'offset': 2}, 'end': {'line': 3, 'offset': 20},
|
|
" 'lineText': 'export class SomeClass {'
|
|
" }, {
|
|
" 'file': 'OtherClass.ts', 'isWriteAccess': 0,
|
|
" 'start': {'line': 5', 'offset': 2}, 'end': {'line': 5, 'offset': 20},
|
|
" 'lineText': 'export class OtherClass extends SomeClass{'
|
|
" }
|
|
" ]
|
|
" }
|
|
function! tsuquyomi#tsClient#tsReferences(file, line, offset)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('references', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsDict(l:result)
|
|
endfunction
|
|
|
|
" Reload an opend file.
|
|
" It can be used for telling change of buffer to TSServer.
|
|
" PARAM: {string} file File name
|
|
" PARAM: {string} tmpfile
|
|
" RETURNS: {0|1}
|
|
function! tsuquyomi#tsClient#tsReload(file, tmpfile)
|
|
let l:arg = {'file': a:file, 'tmpfile': a:tmpfile}
|
|
" With ts > 2.6 and ts <=1.9, tsserver emit 2 responses by reload request.
|
|
" ignore 2nd response of reload command. See also #62
|
|
if tsuquyomi#config#isHigher(260) || !tsuquyomi#config#isHigher(190)
|
|
let l:res_count = 1
|
|
else
|
|
let l:res_count = 2
|
|
endif
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncEvents('reload', l:arg, 0.01, l:res_count)
|
|
"echo l:result
|
|
if(len(l:result) >= 1)
|
|
if(has_key(l:result[0], 'success'))
|
|
return l:result[0].success
|
|
else
|
|
return 0
|
|
endif
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction
|
|
|
|
" Fetch locatoins of symbols to be replaced from TSServer.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of the symbol's position.
|
|
" PARAM: {int} offset The col number of the symbol's position.
|
|
" PARAM: {0|1} findInComments Whether result contains word in comments.
|
|
" PARAM: {0|1} findInString Whether result contains word in String literals.
|
|
" RETURNS: {dict} Rename information dictionary.
|
|
" e.g.:
|
|
" {
|
|
" 'info': {
|
|
" 'canRename': 1,
|
|
" 'displayName': 'myApp',
|
|
" 'fullDisplayName': 'myApp',
|
|
" 'kind': 'class',
|
|
" 'kindModifiers': '',
|
|
" 'triggerSpan': {
|
|
" 'start': 44,
|
|
" 'length': 5
|
|
" },
|
|
" },
|
|
" 'locs': [{
|
|
" 'file': 'hoge.ts'',
|
|
" 'locs': [
|
|
" {'start':{'line': 3, 'offset': 4}, 'end':{'line': 3, 'offset': 12}},
|
|
" ...
|
|
" ]
|
|
" },
|
|
" ...,
|
|
" ]
|
|
" }
|
|
function! tsuquyomi#tsClient#tsRename(file, line, offset, findInComments, findInString)
|
|
" TODO findInString parameter does not work... why?
|
|
let l:arg = {'file': a:file, 'line': a:line, 'offset': a:offset,
|
|
\ 'findInComments': a:findInComments ? s:JSON.true : s:JSON.false,
|
|
\ 'findInString' : a:findInString ? s:JSON.true : s:JSON.false
|
|
\ }
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('rename', l:arg)
|
|
return tsuquyomi#tsClient#getResponseBodyAsDict(l:result)
|
|
endfunction
|
|
|
|
" Find brace matching pair.
|
|
" Vim has brace matching natively, so Tsuquyomi does not support this method.
|
|
function! tsuquyomi#tsClient#tsBrace(file, line, offset)
|
|
call s:error('not implemented!')
|
|
endfunction
|
|
|
|
" This command is available only at tsserver ~v.1.6
|
|
function! tsuquyomi#tsClient#tsDocumentHighlights(file, line, offset, filesToSearch)
|
|
call s:error('not implemented!')
|
|
endfunction
|
|
|
|
" Fetch project information.
|
|
" This command is available only at tsserver ~v.1.6
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {0|1} needFileNameList Whether include list of files in response.
|
|
" RETURNS: dict Project information dictionary.
|
|
" e.g.:
|
|
" {
|
|
" 'configFileName': '/samplePrjs/prj001/tsconfig.json',
|
|
" 'fileNames': [
|
|
" '/PATH_TO_TYPESCRIPT/node_modules/typescript/lib/lib.d.ts',
|
|
" '/samplePrjs/prj001/main.ts'
|
|
" ]
|
|
" }
|
|
function! tsuquyomi#tsClient#tsProjectInfo(file, needFileNameList)
|
|
let l:arg = {'file': a:file,
|
|
\ 'needFileNameList': a:needFileNameList ? s:JSON.true : s:JSON.false
|
|
\ }
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('projectInfo', l:arg)
|
|
return tsuquyomi#tsClient#getResponseBodyAsDict(l:result)
|
|
endfunction
|
|
|
|
" Fetch method signature information from TSServer.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of the symbol's position.
|
|
" PARAM: {int} offset The col number of the symbol's position.
|
|
" RETURNS: {dict}
|
|
" e.g. :
|
|
" {
|
|
" 'selectedItemIndex': 0,
|
|
" 'argumentCount': 1,
|
|
" 'argumentIndex': 0,
|
|
" 'applicableSpan': {
|
|
" 'start': { 'offset': 27, 'line': 25 }, 'end': { 'offset': 40, 'line': 25 }
|
|
" },
|
|
" 'items': [{
|
|
" 'tags': [],
|
|
" 'separatorDisplayParts': [
|
|
" { 'kind': 'punctuation', 'text': ',' },
|
|
" { 'kind': 'space', 'text': ' ' }
|
|
" ],
|
|
" 'prefixDisplayParts': [
|
|
" { 'kind': 'methodName', 'text': 'deleteTerms' },
|
|
" { 'kind': 'punctuation', 'text': '(' }
|
|
" ],
|
|
" 'parameters': [
|
|
" {
|
|
" 'isOptional': 0,
|
|
" 'name': 'id',
|
|
" 'documentation': [],
|
|
" 'displayParts': [
|
|
" { 'kind': 'parameterName', 'text': 'id' },
|
|
" { 'kind': 'punctuation', 'text': ':' },
|
|
" { 'kind': 'space', 'text': ' ' },
|
|
" { 'kind': 'keyword', 'text': 'number' }
|
|
" ]
|
|
" }
|
|
" ],
|
|
" 'suffixDisplayParts': [
|
|
" { 'kind': 'punctuation', 'text': ')' },
|
|
" { 'kind': 'punctuation', 'text': ':' },
|
|
" { 'kind': 'space', 'text': ' ' },
|
|
" { 'kind': 'className', 'text': 'Observable' },
|
|
" { 'kind': 'punctuation', 'text': '<' },
|
|
" { 'kind': 'interfaceName', 'text': 'ApiResponseData' },
|
|
" { 'kind': 'punctuation', 'text': '>' }
|
|
" ],
|
|
" 'isVariadic': 0,
|
|
" 'documentation': []
|
|
" }]
|
|
" }
|
|
"
|
|
" This can be combined into a simple signature like this:
|
|
" deleteTerms(id: number): Observable<ApiResponseData>
|
|
function! tsuquyomi#tsClient#tsSignatureHelp(file, line, offset)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('signatureHelp', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsDict(l:result)
|
|
endfunction
|
|
|
|
" Fetch location where the type of the symbol at cursor(line, offset) in file is defined.
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {int} line The line number of location to complete.
|
|
" PARAM: {int} offset The col number of location to complete.
|
|
" RETURNS: {list<dict>} A list of dictionaries of type definition location.
|
|
" e.g. :
|
|
" [{'file': 'hogehoge.ts', 'start': {'line': 3, 'offset': 2}, 'end': {'line': 3, 'offset': 10}}]
|
|
function! tsuquyomi#tsClient#tsTypeDefinition(file, line, offset)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset}
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('typeDefinition', l:args)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Reload prjects.
|
|
" This command is available only at tsserver ~v.1.6
|
|
" This command does not return any response.
|
|
function! tsuquyomi#tsClient#tsReloadProjects()
|
|
return tsuquyomi#tsClient#sendCommandOneWay('reloadProjects', {})
|
|
endfunction
|
|
|
|
" This command is available only at tsserver ~v.2.1
|
|
" PARAM: {string} file File name.
|
|
" PARAM: {number} startLine The line number for the req
|
|
" PARAM: {number} startOffset The character offset for the req
|
|
" PARAM: {number} endLine The line number for the req
|
|
" PARAM: {number} endOffset The character offset for the req
|
|
" PARAM: {list<number>} errorCodes Error codes we want to get the fixes for
|
|
" RETURNS: {list<dict>}
|
|
" e.g.:
|
|
" [
|
|
" {
|
|
" 'description': 'Add missing ''super()'' call.',
|
|
" 'changes': [
|
|
" {
|
|
" 'fileName': '/SamplePrj/codeFixesTest.ts',
|
|
" 'textChanges': [
|
|
" {
|
|
" 'start': {'offset': 20, 'line': 6},
|
|
" 'end': {'offset': 20, 'line': 6},
|
|
" 'newText': 'super();'
|
|
" }
|
|
" ]
|
|
" }
|
|
" ]
|
|
" }
|
|
" ]
|
|
function! tsuquyomi#tsClient#tsGetCodeFixes(file, startLine, startOffset, endLine, endOffset, errorCodes)
|
|
let l:arg = {
|
|
\ 'file': a:file,
|
|
\ 'startLine': a:startLine, 'startOffset': a:startOffset,
|
|
\ 'endLine': a:endLine, 'endOffset': a:endOffset,
|
|
\ 'errorCodes': a:errorCodes
|
|
\ }
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('getCodeFixes', l:arg)
|
|
return tsuquyomi#tsClient#getResponseBodyAsList(l:result)
|
|
endfunction
|
|
|
|
" Get available code fixes list
|
|
" This command is available only at tsserver ~v.2.1
|
|
function! tsuquyomi#tsClient#tsGetSupportedCodeFixes()
|
|
let l:result = tsuquyomi#tsClient#sendCommandSyncResponse('getSupportedCodeFixes', {})
|
|
let l:body = tsuquyomi#tsClient#getResponseBodyAsDict(l:result)
|
|
if (type(l:body) != v:t_list)
|
|
return []
|
|
else
|
|
return l:body
|
|
endif
|
|
endfunction
|
|
|
|
"
|
|
" Emmit to change file to TSServer.
|
|
" Param: {string} file File name to change.
|
|
" Param: {int} line The line number of starting point of range to change.
|
|
" Param: {int} offset The col number of starting point of range to change.
|
|
" Param: {int} endLine The line number of end point of range to change.
|
|
" Param: {int} endOffset The col number of end point of range to change.
|
|
" Param: {string} insertString String after replacing
|
|
" This command does not return any response.
|
|
function! tsuquyomi#tsClient#tsAsyncChange(file, line, offset, endLine, endOffset, insertString)
|
|
let l:args = {'file': a:file, 'line': a:line, 'offset': a:offset, 'endLine': a:endLine, 'endOffset': a:endOffset, 'insertString': a:insertString}
|
|
call tsuquyomi#tsClient#sendCommandAsyncEvents('change', l:args)
|
|
endfunction
|
|
|
|
"
|
|
" Get error for files.
|
|
" PARAM: {list<string>} files List of filename
|
|
" PARAM: {int} delay Delay time [msec].
|
|
function! tsuquyomi#tsClient#tsAsyncGeterr(files, delay)
|
|
let l:args = {'files': a:files, 'delay': a:delay}
|
|
let l:delaySec = a:delay * 1.0 / 1000.0
|
|
let l:typeCount = tsuquyomi#config#isHigher(280) ? 3 : 2
|
|
call tsuquyomi#tsClient#sendCommandAsyncEvents('geterr', l:args)
|
|
endfunction
|
|
|
|
" ### TSServer command wrappers }}}
|
|
|
|
let &cpo = s:save_cpo
|
|
unlet s:save_cpo
|