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.

1206 lines
35 KiB
VimL

"============================================================================
" FILE: tsuquyomi.vim
" AUTHOR: Quramy <yosuke.kurami@gmail.com>
"============================================================================
scriptencoding utf-8
let s:save_cpo = &cpo
set cpo&vim
let s:V = vital#of('tsuquyomi')
let s:Filepath = s:V.import('System.Filepath')
let s:script_dir = expand('<sfile>:p:h')
"let s:root_dir = s:Filepath.join(s:script_dir, '../')
let s:root_dir = s:Filepath.dirname(s:Filepath.dirname(s:Filepath.remove_last_separator(s:Filepath.join(s:script_dir, '../'))))
"
" ### Utilites {{{
function! s:error(msg)
echom (a:msg)
throw 'tsuquyomi: '.a:msg
endfunction
function! s:normalizePath(path)
return substitute(a:path, '\\', '/', 'g')
endfunction
function! s:joinParts(displayParts)
return join(map(a:displayParts, 'v:val.text'), '')
endfunction
function! s:joinPartsIgnoreBreak(displayParts, replaceString)
let l:display = ''
for part in a:displayParts
if part.kind == 'lineBreak'
let l:display = l:display.a:replaceString
break
endif
let l:display = l:display.part.text
endfor
return l:display
endfunction
" Check whether files are opened.
" Found not opend file, show message.
function! s:checkOpenAndMessage(filelist)
if tsuquyomi#tsClient#statusTss() == 'dead'
return [[], a:filelist]
endif
let opened = []
let not_opend = []
for file in a:filelist
if tsuquyomi#bufManager#isOpened(file)
call add(opened, file)
else
call add(not_opend, file)
endif
endfor
if len(not_opend)
for file in not_opend
if tsuquyomi#bufManager#isNotOpenable(file)
echom '[Tsuquyomi] The buffer "'.file.'" is not valid filepath, so tusuqoymi cannot open this buffer.'
return [opened, not_opend]
endif
endfor
echom '[Tsuquyomi] Buffers ['.join(not_opend, ', ').'] are not opened by TSServer. Please exec command ":TsuquyomiOpen '.join(not_opend).'" and retry.'
endif
return [opened, not_opend]
endfunction
" Save current buffer to a temp file, and emit to reload TSServer.
" This function may be called for conversation with TSServer after user's change buffer.
function! s:flush()
if tsuquyomi#bufManager#isDirty(expand('%:p'))
let file_name = expand('%:p')
call tsuquyomi#bufManager#saveTmp(file_name)
call tsuquyomi#tsClient#tsReload(file_name, tsuquyomi#bufManager#tmpfile(file_name))
call tsuquyomi#bufManager#setDirty(file_name, 0)
endif
endfunction
function! s:is_valid_identifier(symbol_str)
return a:symbol_str =~ '^[A-Za-z_\$][A-Za-z_\$0-9]*$'
endfunction
" Manually write content to the preview window.
" Opens a preview window to a scratch buffer named '__TsuquyomiScratch__'
function! s:writeToPreview(content)
silent pedit __TsuquyomiScratch__
silent wincmd P
setlocal modifiable noreadonly
setlocal nobuflisted buftype=nofile bufhidden=wipe ft=typescript
put =a:content
0d_
setlocal nomodifiable readonly
silent wincmd p
endfunction
function! s:setqflist(quickfix_list, ...)
" 0: Do not close cwindow automatically
" 1: Close cwindow automatically
let auto_close = len(a:000) ? a:0 : 0
call setqflist(a:quickfix_list, 'r')
if len(a:quickfix_list) > 0
cwindow
else
if auto_close != 0
cclose
endif
endif
endfunction
let s:diagnostics_queue = []
let s:diagnostics_timer = -1
function! s:addDiagnosticsQueue(delay, bufnum)
if index(s:diagnostics_queue, a:bufnum) != -1
return
endif
if s:diagnostics_timer != -1
call timer_stop(s:diagnostics_timer)
let s:diagnostics_timer = -1
endif
call add(s:diagnostics_queue, a:bufnum)
let s:diagnostics_timer = timer_start(
\ a:delay,
\ function('s:sendDiagnosticsQueue')
\ )
endfunction
function! s:sendDiagnosticsQueue(timer) abort
for l:bufnum in s:diagnostics_queue
if !bufexists(l:bufnum)
continue
endif
let l:file = tsuquyomi#emitChange(l:bufnum)
let l:delayMsec = 50 "TODO export global option
call tsuquyomi#tsClient#tsAsyncGeterr([l:file], l:delayMsec)
endfor
let s:diagnostics_queue = []
endfunction
" ### Utilites }}}
" ### Public functions {{{
"
function! tsuquyomi#rootDir()
return s:root_dir
endfunction
" #### Server operations {{{
function! tsuquyomi#startServer()
return tsuquyomi#tsClient#startTss()
endfunction
function! tsuquyomi#stopServer()
call tsuquyomi#bufManager#clearMap()
return tsuquyomi#tsClient#stopTss()
endfunction
function! tsuquyomi#statusServer()
return tsuquyomi#tsClient#statusTss()
endfunction
" #### Server operations }}}
" #### Notify changed {{{
function! tsuquyomi#letDirty()
return tsuquyomi#bufManager#setDirty(expand('%:p'), 1)
endfunction
function! tsuquyomi#flush()
call s:flush()
endfunction
" #### Notify changed }}}
" #### File operations {{{
function! tsuquyomi#open(...)
let filelist = a:0 ? map(range(1, a:{0}), 'expand(a:{v:val})') : [expand('%:p')]
return s:openFromList(filelist)
endfunction
function! s:openFromList(filelist)
for file in a:filelist
if file == '' || tsuquyomi#bufManager#isNotOpenable(file) ||tsuquyomi#bufManager#isOpened(file)
continue
endif
call tsuquyomi#tsClient#tsOpen(file)
call tsuquyomi#bufManager#open(file)
endfor
return 1
endfunction
function! tsuquyomi#close(...)
let filelist = a:0 ? map(range(1, a:{0}), 'expand(a:{v:val})') : [expand('%:p')]
return s:closeFromList(filelist)
endfunction
function! s:closeFromList(filelist)
let file_count = 0
for file in a:filelist
if tsuquyomi#bufManager#isOpened(file)
call tsuquyomi#tsClient#tsClose(file)
call tsuquyomi#bufManager#close(file)
let file_count = file_count + 1
endif
endfor
return file_count
endfunction
function! s:reloadFromList(filelist)
let file_count = 0
for file in a:filelist
if tsuquyomi#bufManager#isOpened(file)
call tsuquyomi#tsClient#tsReload(file, file)
else
call tsuquyomi#tsClient#tsOpen(file)
call tsuquyomi#bufManager#open(file)
endif
call tsuquyomi#bufManager#setDirty(file, 0)
let file_count = file_count + 1
endfor
return file_count
endfunction
function! tsuquyomi#reload(...)
let filelist = a:0 ? map(range(1, a:{0}), 'expand(a:{v:val})') : [expand('%:p')]
return s:reloadFromList(filelist)
endfunction
function! tsuquyomi#reloadProject()
if tsuquyomi#config#isHigher(160)
call tsuquyomi#tsClient#tsReloadProjects()
else
let filelist = values(map(tsuquyomi#bufManager#openedFiles(), 'v:val.bufname'))
if len(filelist)
call s:closeFromList(filelist)
call s:openFromList(filelist)
endif
endif
endfunction
function! tsuquyomi#dump(...)
let filelist = a:0 ? map(range(1, a:{0}), 'expand(a:{v:val})') : [expand('%:p')]
let [opend, not_opend] = s:checkOpenAndMessage(filelist)
for file in opend
call tsuquyomi#tsClient#tsSaveto(file, file.'.dump')
endfor
endfunction
" #### File operations }}}
" #### Project information {{{
function! tsuquyomi#projectInfo(file)
if !tsuquyomi#config#isHigher(160)
echom '[Tsuquyomi] This feature requires TypeScript@1.6.0 or higher'
return {}
endif
if len(s:checkOpenAndMessage([a:file])[1])
return {}
endif
let l:result = tsuquyomi#tsClient#tsProjectInfo(a:file, 1)
let l:result.filteredFileNames = []
if has_key(l:result, 'fileNames')
for fileName in l:result.fileNames
if fileName =~ 'typescript/lib/lib.d.ts$'
else
call add(l:result.filteredFileNames, fileName)
endif
endfor
endif
return l:result
endfunction
" }}}
" #### Complete {{{
"
function! tsuquyomi#setPreviewOption()
" issue #41
" I'll consider how to highlighting preview window without setting filetype.
"
" if &previewwindow
" setlocal ft=typescript
" endif
endfunction
function! tsuquyomi#makeCompleteMenu(file, line, offset, entryNames)
call tsuquyomi#perfLogger#record('tsCompletionEntryDetail')
let res_list = tsuquyomi#tsClient#tsCompletionEntryDetails(a:file, a:line, a:offset, a:entryNames)
call tsuquyomi#perfLogger#record('tsCompletionEntryDetail_done')
let display_texts = []
for result in res_list
call add(display_texts, s:joinPartsIgnoreBreak(result.displayParts, '{...}'))
endfor
return display_texts
endfunction
" Get signature help information for preview window.
function! tsuquyomi#getSignatureHelp(file, line, offset)
if stridx(&completeopt, 'preview') == -1
return [0, '']
endif
let l:sig_dict = tsuquyomi#tsClient#tsSignatureHelp(a:file, a:line, a:offset)
let has_info = 0
if has_key(l:sig_dict, 'items') && len(l:sig_dict.items)
let has_info = 1
let info_lines = []
for sigitem in l:sig_dict.items
let siginfo_list = []
let dispText = s:joinParts(sigitem.prefixDisplayParts)
let params_list = []
for paramInfo in sigitem.parameters
let param_text = s:joinParts(paramInfo.displayParts)
if len(paramInfo.documentation)
let param_text = param_text.'/* '.s:joinPartsIgnoreBreak(paramInfo.documentation, ' ...').' */'
endif
call add(params_list, param_text)
endfor
let dispText = dispText.join(params_list, ', ').s:joinParts(sigitem.suffixDisplayParts)
if len(sigitem.documentation)
let dispText = dispText.'/* '.s:joinPartsIgnoreBreak(sigitem.documentation, ' ...').' */'
endif
call add(info_lines, dispText)
endfor
let sigitem = l:sig_dict.items[0]
return [has_info, join(info_lines, "\n\n")]
endif
return [has_info, '']
endfunction
" Comparator comparing on TypeScript CompletionEntry's 'sortText' property
" See https://github.com/Microsoft/TypeScript/blob/master/src/server/protocol.ts#L1483
function! s:sortTextComparator(entry1, entry2)
if a:entry1.sortText < a:entry2.sortText
return -1
elseif a:entry1.sortText > a:entry2.sortText
return 1
else
return 0
endif
endfunction
function! tsuquyomi#signatureHelp()
pclose
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
call s:flush()
let l:file = expand('%:p')
let l:line = line('.')
let l:offset = col('.')
let [has_info, siginfo] = tsuquyomi#getSignatureHelp(l:file, l:line, l:offset)
if has_info
call s:writeToPreview(siginfo)
endif
endfunction
function! tsuquyomi#complete(findstart, base)
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
let l:line_str = getline('.')
let l:line = line('.')
let l:offset = col('.')
" search backwards for start of identifier (iskeyword pattern)
let l:start = l:offset
while l:start > 0 && l:line_str[l:start-2] =~ "\\k"
let l:start -= 1
endwhile
if(a:findstart)
call tsuquyomi#perfLogger#record('before_flush')
call s:flush()
call tsuquyomi#perfLogger#record('after_flush')
return l:start - 1
else
let l:file = expand('%:p')
let l:res_dict = {'words': []}
call tsuquyomi#perfLogger#record('before_tsCompletions')
" By default the result list will be sorted by the 'name' properly alphabetically
let l:alpha_sorted_res_list = tsuquyomi#tsClient#tsCompletions(l:file, l:line, l:start, a:base)
call tsuquyomi#perfLogger#record('after_tsCompletions')
let is_javascript = (&filetype == 'javascript') || (&filetype == 'jsx') || (&filetype == 'javascript.jsx')
if is_javascript
" Sort the result list according to how TypeScript suggests entries to be sorted
let l:res_list = sort(copy(l:alpha_sorted_res_list), 's:sortTextComparator')
else
let l:res_list = l:alpha_sorted_res_list
endif
let enable_menu = stridx(&completeopt, 'menu') != -1
let length = strlen(a:base)
if enable_menu
call tsuquyomi#perfLogger#record('start_menu')
if g:tsuquyomi_completion_preview
let [has_info, siginfo] = tsuquyomi#getSignatureHelp(l:file, l:line, l:start)
else
let [has_info, siginfo] = [0, '']
endif
let size = g:tsuquyomi_completion_chunk_size
let j = 0
while j * size < len(l:res_list)
let entries = []
let items = []
let upper = min([(j + 1) * size, len(l:res_list)])
for i in range(j * size, upper - 1)
let info = l:res_list[i]
if !length
\ || !g:tsuquyomi_completion_case_sensitive && info.name[0:length - 1] == a:base
\ || g:tsuquyomi_completion_case_sensitive && info.name[0:length - 1] ==# a:base
let l:item = {'word': info.name, 'menu': info.kind }
if has_info
let l:item.info = siginfo
endif
if is_javascript && info.kind == 'warning'
let l:item.menu = '' " Make display cleaner by not showing 'warning' as the type
endif
if !g:tsuquyomi_completion_detail
call complete_add(l:item)
else
" if file is TypeScript, then always add to entries list to
" fetch details. Or in the case of JavaScript, avoid adding to
" entries list if ScriptElementKind is 'warning'. Because those
" entries are just random identifiers that occur in the file.
if !is_javascript || info.kind != 'warning'
call add(entries, info.name)
endif
call add(items, l:item)
endif
endif
endfor
if g:tsuquyomi_completion_detail
call tsuquyomi#perfLogger#record('before_completeMenu'.j)
let menus = tsuquyomi#makeCompleteMenu(l:file, l:line, l:start, entries)
call tsuquyomi#perfLogger#record('after_completeMenu'.j)
let idx = 0
for menu in menus
let items[idx].menu = menu
let items[idx].info = menu
call complete_add(items[idx])
let idx = idx + 1
endfor
" For JavaScript completion, there are entries whose
" ScriptElementKind is 'warning'. tsserver won't have any details
" returned for them, but they still need to be added at the end.
for i in range(idx, len(items) - 1)
call complete_add(items[i])
endfor
endif
if complete_check()
break
endif
let j = j + 1
endwhile
return []
else
return filter(map(l:res_list, 'v:val.name'), 'stridx(v:val, a:base) == 0')
endif
endif
endfunction
" ### Complete }}}
" #### Definition {{{
function! tsuquyomi#gotoDefinition(tsClientFunction, splitMode)
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
call s:flush()
let l:file = s:normalizePath(expand('%:p'))
let l:line = line('.')
let l:offset = col('.')
let l:res_list = a:tsClientFunction(l:file, l:line, l:offset)
let l:definition_split = a:splitMode > 0 ? a:splitMode : g:tsuquyomi_definition_split
if(len(l:res_list) > 0)
" If get result, go to last location.
let l:info = l:res_list[len(l:res_list) - 1]
if a:splitMode == 0 && l:file == l:info.file
" Same file without split
call tsuquyomi#bufManager#winPushNavDef(bufwinnr(bufnr('%')), l:file, {'line': l:line, 'col': l:offset})
call cursor(l:info.start.line, l:info.start.offset)
elseif l:definition_split == 0
call tsuquyomi#bufManager#winPushNavDef(bufwinnr(bufnr('%')), l:file, {'line': l:line, 'col': l:offset})
execute 'edit +call\ cursor('.l:info.start.line.','.l:info.start.offset.') '.l:info.file
elseif l:definition_split == 1
execute 'split +call\ cursor('.l:info.start.line.','.l:info.start.offset.') '.l:info.file
elseif l:definition_split == 2
execute 'vsplit +call\ cursor('.l:info.start.line.','.l:info.start.offset.') '.l:info.file
elseif l:definition_split == 3
execute 'tabedit +call\ cursor('.l:info.start.line.','.l:info.start.offset.') '.l:info.file
endif
else
" If don't get result, do nothing.
endif
endfunction
function! tsuquyomi#definition()
call tsuquyomi#gotoDefinition(function('tsuquyomi#tsClient#tsDefinition'), 0)
endfunction
function! tsuquyomi#splitDefinition()
call tsuquyomi#gotoDefinition(function('tsuquyomi#tsClient#tsDefinition'), 1)
endfunction
function! tsuquyomi#typeDefinition()
call tsuquyomi#gotoDefinition(function('tsuquyomi#tsClient#tsTypeDefinition'), 0)
endfunction
function! tsuquyomi#goBack()
let [type, result] = tsuquyomi#bufManager#winPopNavDef(bufwinnr(bufnr('%')))
if !type
echom '[Tsuquyomi] No items in navigation stack...'
return
endif
let [file_name, loc] = [result.file_name, result.loc]
if expand('%:p') == file_name
call cursor(loc.line, loc.col)
else
execute 'edit +call\ cursor('.loc.line.','.loc.col.') '.file_name
endif
endfunction
" #### Definition }}}
" #### References {{{
" Show reference on a location window.
function! tsuquyomi#getLocations(tsClientFunction, functionTitle)
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
call s:flush()
let l:file = expand('%:p')
let l:line = line('.')
let l:offset = col('.')
" 1. Fetch reference information.
let l:res = a:tsClientFunction(l:file, l:line, l:offset)
let l:references = []
if type(l:res) == v:t_dict && has_key(l:res, 'refs')
let l:references = l:res.refs
elseif type(l:res) == v:t_list
let l:references = l:res
endif
if len(l:references) != 0
let l:location_list = []
" 2. Make a location list for `setloclist`
for reference in l:references
if has_key(reference, 'lineText')
let l:location_info = {
\'filename': fnamemodify(reference.file, ':~:.'),
\'lnum': reference.start.line,
\'col': reference.start.offset,
\'text': reference.lineText
\}
else
let l:location_info = {
\'filename': fnamemodify(reference.file, ':~:.'),
\'lnum': reference.start.line,
\'col': reference.start.offset
\}
endif
call add(l:location_list, l:location_info)
endfor
if len(l:location_list) > 0
call setloclist(0, l:location_list, 'r')
"3. Open location window.
lwindow
endif
else
echom '[Tsuquyomi] '.a:functionTitle.': Not found...'
endif
endfunction
function! tsuquyomi#references()
call tsuquyomi#getLocations(function('tsuquyomi#tsClient#tsReferences'), 'References')
endfunction
function! tsuquyomi#implementation()
call tsuquyomi#getLocations(function('tsuquyomi#tsClient#tsImplementation'), 'Implementation')
endfunction
" #### References }}}
" #### Geterr {{{
function! tsuquyomi#asyncGeterr(...)
if g:tsuquyomi_is_available == 1
call tsuquyomi#registerNotify(function('s:setqflist'), 'diagnostics')
let l:delay = len(a:000) ? a:1 : 0
call tsuquyomi#asyncCreateFixlist(l:delay)
endif
endfunction
function! tsuquyomi#parseDiagnosticEvent(event, supportedCodes)
let quickfix_list = []
let codes = len(a:supportedCodes) > 0 ? a:supportedCodes : s:supportedCodeFixes
if has_key(a:event, 'type') && a:event.type ==# 'event' && (a:event.event ==# 'syntaxDiag' || a:event.event ==# 'semanticDiag')
for diagnostic in a:event.body.diagnostics
if diagnostic.text =~ "Cannot find module" && g:tsuquyomi_ignore_missing_modules == 1
continue
endif
let item = {}
let item.filename = a:event.body.file
let item.lnum = diagnostic.start.line
if(has_key(diagnostic.start, 'offset'))
let item.col = diagnostic.start.offset
endif
let item.text = diagnostic.text
if !has_key(diagnostic, 'code')
continue
endif
let item.code = diagnostic.code
let l:cfidx = index(codes, (diagnostic.code . ''))
if l:cfidx >= 0
let l:qfmark = '[QF available]'
let item.text = diagnostic.code . l:qfmark . ': ' . item.text
endif
let item.availableCodeFix = l:cfidx >= 0
let item.type = 'E'
call add(quickfix_list, item)
endfor
endif
return quickfix_list
endfunction
function! tsuquyomi#registerNotify(callback, eventName)
call tsuquyomi#tsClient#registerNotify(a:callback, a:eventName)
endfunction
function! tsuquyomi#emitChange(bufnum)
let l:input = join(getbufline(a:bufnum, 1, '$'), "\n") . "\n"
let l:file = expand('%:p')
" file, line, offset, endLine, endOffset, insertString
call tsuquyomi#tsClient#tsAsyncChange(l:file, 1, 1, len(l:input), 1, l:input)
return l:file
endfunction
function! tsuquyomi#asyncCreateFixlist(...)
" Works only Vim8(+channel, +job)
" We must register callbacks(handler and callback) before execute this.
" See `tsuquyomi#config#initBuffer()`
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return []
endif
let l:delay = len(a:000) ? a:1 : 0
let l:bufnum = bufnr('%')
" Tell TSServer to change for get syntaxDiag and semanticDiag errors.
if delay > 0
" Debunce request for Textchanged autocmd.
call s:addDiagnosticsQueue(l:delay, l:bufnum)
else
" Cancel current timer
if s:diagnostics_timer != -1
call timer_stop(s:diagnostics_timer)
let s:diagnostics_timer = -1
endif
let l:file = tsuquyomi#emitChange(l:bufnum)
let l:delayMsec = 50 "TODO export global option
call tsuquyomi#tsClient#tsAsyncGeterr([l:file], l:delayMsec)
endif
endfunction
function! tsuquyomi#createQuickFixListFromEvents(event_list)
if !len(a:event_list)
return []
endif
let quickfix_list = []
let supportedCodes = tsuquyomi#getSupportedCodeFixes()
for event_item in a:event_list
let items = tsuquyomi#parseDiagnosticEvent(event_item, supportedCodes)
let quickfix_list = quickfix_list + items
endfor
return quickfix_list
endfunction
function! tsuquyomi#createFixlist()
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return []
endif
call s:flush()
let l:files = [expand('%:p')]
let l:delayMsec = 50 "TODO export global option
" 1. Fetch error information from TSServer.
let result = tsuquyomi#tsClient#tsGeterr(l:files, l:delayMsec)
" 2. Make a quick fix list for `setqflist`.
return tsuquyomi#createQuickFixListFromEvents(result)
endfunction
function! tsuquyomi#geterr()
let quickfix_list = tsuquyomi#createFixlist()
call s:setqflist(quickfix_list, 1)
endfunction
function! tsuquyomi#geterrProject()
if !tsuquyomi#config#isHigher(160)
echom '[Tsuquyomi] This feature requires TypeScript@1.6.0 or higher'
return
endif
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
call s:flush()
let l:file = expand('%:p')
" 1. Fetch Project info for event count.
let l:pinfo = tsuquyomi#projectInfo(l:file)
if !has_key(l:pinfo, 'filteredFileNames') || !len(l:pinfo.filteredFileNames)
return
endif
" 2. Fetch error information from TSServer.
let l:delayMsec = 50 "TODO export global option
let l:result = tsuquyomi#tsClient#tsGeterrForProject(l:file, l:delayMsec, len(l:pinfo.filteredFileNames))
" 3. Make a quick fix list for `setqflist`.
let quickfix_list = tsuquyomi#createQuickFixListFromEvents(result)
call s:setqflist(quickfix_list, 1)
endfunction
function! tsuquyomi#reloadAndGeterr()
if tsuquyomi#tsClient#statusTss() != 'dead'
return tsuquyomi#geterr()
endif
endfunction
" #### Geterr }}}
" #### Balloon {{{
function! tsuquyomi#balloonexpr()
call s:flush()
let res = tsuquyomi#tsClient#tsQuickinfo(fnamemodify(buffer_name(v:beval_bufnr),":p"), v:beval_lnum, v:beval_col)
if has_key(res, 'displayString')
if (has_key(res, 'documentation') && res.documentation != '')
return join([res.documentation, res.displayString], "\n\n")
endif
return res.displayString
endif
endfunction
function! tsuquyomi#hint()
call s:flush()
let res = tsuquyomi#tsClient#tsQuickinfo(expand('%:p'), line('.'), col('.'))
if has_key(res, 'displayString')
if (has_key(res, 'documentation') && res.documentation != '')
return join([res.documentation, res.displayString], "\n\n")
endif
return res.displayString
else
return '[Tsuquyomi] There is no hint at the cursor.'
endif
endfunction
" #### Balloon }}}
" #### Rename {{{
function! tsuquyomi#renameSymbol()
return s:renameSymbolWithOptions(0, 0)
endfunction
function! tsuquyomi#renameSymbolWithComments()
return s:renameSymbolWithOptions(1, 0)
endfunction
function! tsuquyomi#renameSymbolWithStrings()
return s:renameSymbolWithOptions(0, 1)
endfunction
function! tsuquyomi#renameSymbolWithCommentsStrings()
return s:renameSymbolWithOptions(1, 1)
endfunction
function! s:renameSymbolWithOptions(findInComments, findInString)
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
call s:flush()
let l:filename = expand('%:p')
let l:line = line('.')
let l:offset = col('.')
" * Make a list of locations of symbols to be replaced.
let l:res_dict = tsuquyomi#tsClient#tsRename(l:filename, l:line, l:offset, a:findInComments, a:findInString)
" * Check the symbol is renameable
if !has_key(l:res_dict, 'info')
echom '[Tsuquyomi] No symbol to be renamed'
return
elseif !l:res_dict.info.canRename
echom '[Tsuquyomi] '.l:res_dict.info.localizedErrorMessage
return
endif
" * Check affection only current buffer.
if len(l:res_dict.locs) != 1 || s:normalizePath(expand('%:p')) != l:res_dict.locs[0].file
let file_list = map(copy(l:res_dict.locs), 'v:val.file')
let dirty_file_list = tsuquyomi#bufManager#whichDirty(file_list)
call s:reloadFromList(dirty_file_list)
endif
" * Question user what new symbol name.
echohl String
let renameTo = input('[Tsuquyomi] New symbol name : ')
echohl none
if !s:is_valid_identifier(renameTo)
echo ' '
echom '[Tsuquyomi] It is a not valid identifer.'
return
endif
let s:locs_dict = {}
let s:rename_to = renameTo
let s:other_buf_list = []
" * Execute to replace symbols by location, by buffer
for fileLoc in l:res_dict.locs
let is_open = tsuquyomi#bufManager#isOpened(fileLoc.file)
if !is_open
let s:locs_dict[s:normalizePath(fileLoc.file)] = fileLoc.locs
call add(s:other_buf_list, s:normalizePath(fileLoc.file))
continue
endif
let buffer_name = tsuquyomi#bufManager#bufName(fileLoc.file)
let s:locs_dict[buffer_name] = fileLoc.locs
"echom 'fileLoc.file '.fileLoc.file.', '.buffer_name
let changed_count = 0
if buffer_name != expand('%:p')
call add(s:other_buf_list, buffer_name)
continue
endif
endfor
if !g:tsuquyomi_save_onrename
let changed_count = s:renameLocal(0)
echohl String
echo ' '
echo 'Changed '.changed_count.' locations.'
echohl none
for otherbuf in s:other_buf_list
execute('silent split +call\ s:renameLocal(0) '.otherbuf)
endfor
else
echohl String
let l:confirm = input('[Tsuquyomi] The symbol is located in '.(len(s:other_buf_list) + 1).' files. Really replace them? [Y/n]')
echohl none
if l:confirm != 'n' && l:confirm != 'no'
call s:renameLocalSeq(-1)
endif
endif
endfunction
function! s:renameLocal(should_save)
let changed_count = 0
let filename = expand('%:p')
let locations_in_buf = s:locs_dict[expand('%:p')]
let renameTo = s:rename_to
for span in locations_in_buf
if span.start.line != span.end.line
echom '[Tsuquyomi] this span is across multiple lines. '
return
endif
let lineidx = span.start.line
let linestr = getline(lineidx)
if span.start.offset - 1
let pre = linestr[:(span.start.offset - 2)]
let post = linestr[(span.end.offset - 1):]
let linestr = pre.renameTo.post
else
let post = linestr[(span.end.offset - 1):]
let linestr = renameTo.post
endif
call setline(lineidx, linestr)
let changed_count = changed_count + 1
endfor
call tsuquyomi#reload()
if a:should_save
write
endif
return changed_count
endfunction
function! s:renameLocalSeq(index)
call s:renameLocal(1)
if a:index + 1 < len(s:other_buf_list)
let l:next = s:other_buf_list[a:index + 1]
execute('silent edit +call\ s:renameLocalSeq('.(a:index + 1).') '.l:next)
else
echohl String
echo ' '
echo 'Changed '.(a:index + 2).' files successfuly.'
echohl none
endif
endfunction
" #### Rename }}}
" #### NavBar {{{
function! tsuquyomi#navBar()
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return [[], 0]
endif
call s:flush()
let l:filename = expand('%:p')
let result_list = tsuquyomi#tsClient#tsNavBar(tsuquyomi#bufManager#normalizePath(l:filename))
if len(result_list)
return [result_list, 1]
else
return [[], 0]
endif
endfunction
" #### NavBar }}}
" #### Navto {{{
function! tsuquyomi#navto(term, kindModifiers, matchKindType)
if len(a:term) < g:tsuquyomi_search_term_min_length
echom "[Tsuquyomi] search term's length should be greater than ".g:tsuquyomi_search_term_min_length."."
return [[], 0]
endif
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return [[], 0]
endif
call s:flush()
let l:filename = expand('%:p')
let result_list = tsuquyomi#tsClient#tsNavto(tsuquyomi#bufManager#normalizePath(l:filename), a:term, 100)
if len(result_list)
let list = []
for result in result_list
let flg = 1
if a:matchKindType == 1
let flg = flg && (result.matchKind=='prefix' || result.matchKind=='exact')
elseif a:matchKindType == 2
let flg = flg && (result.matchKind=='exact')
endif
if a:kindModifiers != ''
let flg = flg && has_key(result, 'kindModifiers') && result.kindModifiers=~a:kindModifiers
endif
if flg
call add(list, result)
endif
endfor
return [list, 1]
else
echohl Error
echom "[Tsuquyomi] Nothing was hit."
echohl none
return [[], 0]
endif
endfunction
function! tsuquyomi#navtoByLoclist(term, kindModifiers, matchKindType)
let [result_list, res_code] = tsuquyomi#navto(a:term, a:kindModifiers, a:matchKindType)
if res_code
let l:location_list = []
for navtoItem in result_list
let text = navtoItem.kind.' '.navtoItem.name
if has_key(navtoItem, 'kindModifiers')
let text = navtoItem.kindModifiers.' '.text
endif
if has_key(navtoItem, 'containerName')
if has_key(navtoItem, 'containerKind')
let text = text.' in '.navtoItem.containerKind.' '.navtoItem.containerName
else
let text = text.' in '.navtoItem.containerName
endif
endif
let l:location_info = {
\'filename': navtoItem.file,
\'lnum': navtoItem.start.line,
\'col': navtoItem.start.offset,
\'text': text
\}
call add(l:location_list, l:location_info)
endfor
if(len(l:location_list) > 0)
call setloclist(0, l:location_list, 'r')
lwindow
endif
endif
endfunction
function! tsuquyomi#navtoByLoclistContain(term)
call tsuquyomi#navtoByLoclist(a:term, '', 0)
endfunction
function! tsuquyomi#navtoByLoclistPrefix(term)
call tsuquyomi#navtoByLoclist(a:term, '', 1)
endfunction
function! tsuquyomi#navtoByLoclistExact(term)
call tsuquyomi#navtoByLoclist(a:term, '', 2)
endfunction
" #### Navto }}}
" #### Configure {{{
function! tsuquyomi#sendConfigure()
let l:file = expand('%:p')
let l:hostInfo = &viminfo
let l:formatOptions = { }
let l:extraFileExtensions = []
if exists('&shiftwidth')
let l:formatOptions.baseIndentSize = &shiftwidth
let l:formatOptions.indentSize = &shiftwidth
endif
if exists('&expandtab')
let l:formatOptions.convertTabsToSpaces = &expandtab
endif
call tsuquyomi#tsClient#tsConfigure(l:file, l:hostInfo, l:formatOptions, l:extraFileExtensions)
endfunction
" #### }}}
" #### CodeFixes {{{
function! s:sortQfItemByColdiff(a, b)
if a:a.coldiff < a:b.coldiff
return -1
endif
if a:a.coldiff == a:b.coldiff
return 0
endif
if a:a.coldiff > a:b.coldiff
return 1
endif
endfunction
let s:supportedCodeFixes = []
function! tsuquyomi#getSupportedCodeFixes()
if !tsuquyomi#config#isHigher(210)
return []
endif
if len(s:supportedCodeFixes)
return s:supportedCodeFixes
endif
try
let s:supportedCodeFixes = tsuquyomi#tsClient#tsGetSupportedCodeFixes()
return s:supportedCodeFixes
catch
return []
endtry
endfunction
function! tsuquyomi#quickFix()
if !tsuquyomi#config#isHigher(210)
echom '[Tsuquyomi] This feature requires TypeScript@2.1.0 or higher'
return
endif
if len(s:checkOpenAndMessage([expand('%:p')])[1])
return
endif
call s:flush()
let l:file = expand('%:p')
let l:line = line('.')
let l:col = col('.')
let l:qfList = tsuquyomi#createFixlist()
call filter(l:qfList, 'v:val.lnum == l:line')
if !len(l:qfList)
echom '[Tsuquyomi] There is no error to fix'
return
endif
if len(l:qfList) > 1
let l:temp = []
for qfItem in qfList
let qfItem.coldiff = abs(qfItem.col - l:col)
call add(l:temp, qfItem)
endfor
call sort(l:temp, function('s:sortQfItemByColdiff'))
let l:target = l:temp[0]
else
let l:target = l:qfList[0]
endif
let l:supportedCodes = copy(tsuquyomi#getSupportedCodeFixes())
call filter(l:supportedCodes, 'v:val == l:target.code')
if !len(l:supportedCodes)
echom '[Tsuquyomi] '.l:target.code.' has no quick fixes...'
return
endif
let l:result_list = tsuquyomi#tsClient#tsGetCodeFixes(file, l:target.lnum, l:target.col, l:target.lnum, l:target.col, [l:target.code])
if !len(l:result_list)
echom '[Tsuquyomi] '.l:target.code.' has no quick fixes...'
return
endif
let s:available_qf_descriptions = map(copy(l:result_list), 'v:val.description')
let [description, isSelect] = tsuquyomi#selectQfDescription()
if !isSelect
return
endif
let l:changes = filter(l:result_list, 'v:val.description ==# description')[0].changes
" TODO
" allow other file
for fileChange in l:changes
if tsuquyomi#bufManager#normalizePath(l:file) !=# fileChange.fileName
echom '[Tsuquyomi] Tsuquyomi does not support this code fix...'
return
endif
endfor
call tsuquyomi#applyQfChanges(l:changes)
endfunction
function! tsuquyomi#applyQfChanges(changes)
for fileChange in a:changes
" TODO
" allow fileChange.fileName
for textChange in fileChange.textChanges
let linesCountForReplacement = textChange.end.line - textChange.start.line + 1
let preSpan = strpart(getline(textChange.start.line), 0, textChange.start.offset - 1)
let postSpan = strpart(getline(textChange.end.line), textChange.end.offset - 1)
let repList = split(preSpan.textChange.newText.postSpan, '\n')
let l:count = textChange.start.line
for rLine in repList
if l:count <= textChange.end.line
call setline(l:count, rLine)
else
call append(l:count - 1, rLine)
endif
let l:count = l:count + 1
endfor
endfor
endfor
endfunction
let s:available_qf_descriptions = []
function! tsuquyomi#selectQfComplete(arg_lead, cmd_line, cursor_pos)
return join(s:available_qf_descriptions, "\n")
endfunction
function! tsuquyomi#selectQfDescription()
echohl String
if len(s:available_qf_descriptions) == 1
let l:yn = input('[Tsuquyomi] Apply: "'.s:available_qf_descriptions[0].'" [y/N]')
echohl none
echo ' '
if l:yn =~ 'N'
return ['', 0]
else
return [s:available_qf_descriptions[0], 1]
endif
endif
let l:selected_desc = input('[Tsuquyomi] You can apply 2 more than quick fixes. Select one (candidates are shown using TAB): ', '', 'custom,tsuquyomi#selectQfComplete')
echohl none
echo ' '
if len(filter(copy(s:available_qf_descriptions), 'v:val==#l:selected_desc'))
return [l:selected_desc, 1]
else
echohl Error
echom '[Tsuquyomi] Invalid selection.'
echohl none
return ['', 0]
endif
endfunction
"#### CodeFixes }}}
" ### Public functions }}}
let &cpo = s:save_cpo
unlet s:save_cpo