first commit
This commit is contained in:
commit
9420badb70
34 changed files with 3571 additions and 0 deletions
264
pack/kite/start/vim-plugin/autoload/kite.vim
Normal file
264
pack/kite/start/vim-plugin/autoload/kite.vim
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
let s:status_poll_interval = 5 * 1000 " 5sec in milliseconds
|
||||
let s:timer = -1
|
||||
let s:watch_timer = -1
|
||||
|
||||
if !kite#utils#windows()
|
||||
let s:kite_symbol = nr2char(printf('%d', '0x27E0'))
|
||||
else
|
||||
let s:kite_symbol = '[k]'
|
||||
endif
|
||||
|
||||
let s:inited = 0
|
||||
let s:kite_auto_launched = 0
|
||||
|
||||
|
||||
function kite#enable_auto_start()
|
||||
call kite#utils#set_setting('start_kited_at_startup', 1)
|
||||
call s:launch_kited()
|
||||
call kite#utils#info('Kite: auto-start enabled')
|
||||
endfunction
|
||||
|
||||
function kite#disable_auto_start()
|
||||
call kite#utils#set_setting('start_kited_at_startup', 0)
|
||||
call kite#utils#info('Kite: auto-start disabled')
|
||||
endfunction
|
||||
|
||||
function kite#symbol()
|
||||
return s:kite_symbol
|
||||
endfunction
|
||||
|
||||
|
||||
function kite#statusline()
|
||||
if exists('b:kite_status')
|
||||
return b:kite_status
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#max_file_size()
|
||||
" Fallback to 1MB
|
||||
return get(b:, 'kite_max_file_size', 1048576)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#configure_completeopt()
|
||||
" If the user has configured completeopt, leave it alone.
|
||||
redir => output
|
||||
silent verbose set completeopt
|
||||
redir END
|
||||
if len(split(output, '\n')) > 1 | return | endif
|
||||
|
||||
set completeopt=menuone,noinsert
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:setup_options()
|
||||
let s:pumheight = &pumheight
|
||||
if &pumheight == 0
|
||||
set pumheight=10
|
||||
endif
|
||||
|
||||
let s:updatetime = &updatetime
|
||||
if &updatetime == 4000
|
||||
set updatetime=100
|
||||
endif
|
||||
|
||||
let s:shortmess = &shortmess
|
||||
set shortmess+=c
|
||||
|
||||
if kite#utils#windows()
|
||||
" Avoid taskbar flashing on Windows when executing system() calls.
|
||||
let s:shelltemp = &shelltemp
|
||||
set noshelltemp
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:restore_options()
|
||||
if !exists('s:pumheight') | return | endif
|
||||
|
||||
let &pumheight = s:pumheight
|
||||
unlet s:pumheight
|
||||
let &updatetime = s:updatetime
|
||||
let &shortmess = s:shortmess
|
||||
if kite#utils#windows()
|
||||
let &shelltemp = s:shelltemp
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#bufenter()
|
||||
if kite#languages#supported_by_plugin()
|
||||
call s:launch_kited()
|
||||
|
||||
if !kite#utils#kite_running()
|
||||
call kite#status#status()
|
||||
call s:start_status_timer()
|
||||
|
||||
call s:start_watching_for_kited()
|
||||
return
|
||||
endif
|
||||
|
||||
call s:stop_watching_for_kited()
|
||||
|
||||
if kite#languages#supported_by_kited()
|
||||
if g:kite_completions
|
||||
call s:disable_completion_plugins()
|
||||
endif
|
||||
call s:setup_options()
|
||||
call s:setup_events()
|
||||
call s:setup_mappings()
|
||||
call s:set_max_file_size()
|
||||
|
||||
if g:kite_completions
|
||||
setlocal completefunc=kite#completion#complete
|
||||
endif
|
||||
|
||||
call kite#events#event('focus')
|
||||
call kite#status#status()
|
||||
call s:start_status_timer()
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
" Buffer is not a supported language.
|
||||
call s:restore_options()
|
||||
call s:stop_status_timer()
|
||||
endfunction
|
||||
|
||||
|
||||
function s:setup_events()
|
||||
augroup KiteEvents
|
||||
autocmd! * <buffer>
|
||||
|
||||
autocmd CursorHold,CursorHoldI <buffer> call kite#events#event('selection')
|
||||
autocmd TextChanged,TextChangedI <buffer> call kite#events#event('edit')
|
||||
autocmd FocusGained <buffer> call kite#events#event('focus')
|
||||
|
||||
if g:kite_completions
|
||||
autocmd InsertCharPre <buffer> call kite#completion#insertcharpre()
|
||||
autocmd TextChangedI <buffer> call kite#completion#autocomplete()
|
||||
|
||||
autocmd CompleteDone <buffer> call kite#completion#replace_range()
|
||||
|
||||
if &ft == 'go'
|
||||
autocmd CompleteDone <buffer> call kite#completion#expand_newlines()
|
||||
endif
|
||||
if &ft == 'python'
|
||||
autocmd CompleteDone <buffer> call kite#snippet#complete_done()
|
||||
endif
|
||||
endif
|
||||
|
||||
if exists('g:kite_documentation_continual') && g:kite_documentation_continual
|
||||
autocmd CursorHold,CursorHoldI <buffer> call kite#docs#docs()
|
||||
endif
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:setup_mappings()
|
||||
if exists('g:kite_tab_complete') && g:kite_completions
|
||||
imap <buffer> <expr> <Tab> pumvisible() ? "\<C-y>" : "\<Tab>"
|
||||
endif
|
||||
|
||||
if empty(maparg('K', 'n')) && !hasmapto('(kite-docs)', 'n')
|
||||
nmap <silent> <buffer> K <Plug>(kite-docs)
|
||||
endif
|
||||
|
||||
if empty(maparg('<C-]>', 'n'))
|
||||
nmap <silent> <buffer> <C-]> :KiteGotoDefinition<CR>
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:set_max_file_size()
|
||||
let max_file_size = kite#client#max_file_size()
|
||||
if max_file_size != -1
|
||||
let b:kite_max_file_size = max_file_size
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:start_status_timer()
|
||||
if s:timer == -1
|
||||
let s:timer = timer_start(s:status_poll_interval,
|
||||
\ function('kite#status#status'),
|
||||
\ {'repeat': -1}
|
||||
\ )
|
||||
else
|
||||
call timer_pause(s:timer, 0) " unpause
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:stop_status_timer()
|
||||
call timer_pause(s:timer, 1)
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:launch_kited()
|
||||
if !s:kite_auto_launched && kite#utils#get_setting('start_kited_at_startup', 1)
|
||||
call kite#utils#launch_kited()
|
||||
let s:kite_auto_launched = 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:start_watching_for_kited()
|
||||
if s:watch_timer == -1
|
||||
let s:watch_timer = timer_start(s:status_poll_interval,
|
||||
\ function('kite#activate_when_ready'),
|
||||
\ {'repeat': -1}
|
||||
\ )
|
||||
else
|
||||
call timer_pause(s:watch_timer, 0) " unpause
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! kite#activate_when_ready(...)
|
||||
if kite#utils#kite_running()
|
||||
call kite#bufenter()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:stop_watching_for_kited()
|
||||
call timer_pause(s:watch_timer, 1)
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:disable_completion_plugins()
|
||||
" coc.nvim
|
||||
if exists('g:did_coc_loaded')
|
||||
let b:coc_suggest_disable = 1
|
||||
" Alternatively:
|
||||
" autocmd BufEnter *.python :CocDisable
|
||||
" autocmd BufLeave *.python :CocEnable
|
||||
call kite#utils#warn("disabling coc.nvim's completions in this buffer")
|
||||
endif
|
||||
|
||||
" Jedi
|
||||
if exists('*jedi#setup_completion')
|
||||
" This may not be enough: https://github.com/davidhalter/jedi-vim/issues/614
|
||||
let g:jedi#completions_enabled = 0
|
||||
call kite#utils#warn("disabling jedi-vim's completions")
|
||||
" Alternatively:
|
||||
" call kite#utils#warn('please uninstall jedi-vim and restart vim/nvim')
|
||||
" finish
|
||||
endif
|
||||
|
||||
" YouCompleteMe
|
||||
if exists('g:loaded_youcompleteme') && !exists('g:ycm_filetype_blacklist.python')
|
||||
let g:ycm_filetype_blacklist.python = 1
|
||||
call kite#utils#warn("disabling YouCompleteMe's completions for python files")
|
||||
endif
|
||||
|
||||
" Deoplete
|
||||
if exists('*deoplete#disable')
|
||||
call deoplete#disable()
|
||||
call kite#utils#warn("disabling deoplete's completions")
|
||||
endif
|
||||
endfunction
|
||||
|
||||
123
pack/kite/start/vim-plugin/autoload/kite/async.vim
Normal file
123
pack/kite/start/vim-plugin/autoload/kite/async.vim
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
let s:async_sync_id = 0
|
||||
let s:async_sync_outputs = {}
|
||||
|
||||
function! s:next_async_sync_id()
|
||||
let async_sync_id = s:async_sync_id
|
||||
let s:async_sync_id += 1
|
||||
return async_sync_id
|
||||
endfunction
|
||||
|
||||
function! s:async_sync_output(async_sync_id, output)
|
||||
if type(a:output) == v:t_list
|
||||
" Ensure empty list becomes an empty string.
|
||||
let output = join(a:output, "\n")
|
||||
else
|
||||
" Assume this is a string
|
||||
let output = a:output
|
||||
endif
|
||||
let s:async_sync_outputs[a:async_sync_id] = output " job can now be garbage collected
|
||||
endfunction
|
||||
|
||||
|
||||
" Executes `cmd` asynchronously but looks synchronous to the caller.
|
||||
function! kite#async#sync(cmd)
|
||||
let async_sync_id = s:next_async_sync_id()
|
||||
let job_handle = kite#async#execute(a:cmd, function('s:async_sync_output', [async_sync_id]))
|
||||
let s:async_sync_outputs[async_sync_id] = job_handle
|
||||
let job_type = type(job_handle) " Assume not a string
|
||||
|
||||
let vim = !has('nvim')
|
||||
while type(s:async_sync_outputs[async_sync_id]) == job_type
|
||||
if vim | call job_status(job_handle) | endif
|
||||
sleep 5m
|
||||
endwhile
|
||||
|
||||
let output = s:async_sync_outputs[async_sync_id]
|
||||
unlet s:async_sync_outputs[async_sync_id]
|
||||
|
||||
return output
|
||||
endfunction
|
||||
|
||||
|
||||
" Optional argument is data (JSON) to pass to cmd's stdin.
|
||||
" Returns the job / job id.
|
||||
function! kite#async#execute(cmd, handler, ...)
|
||||
let options = {
|
||||
\ 'stdoutbuffer': [],
|
||||
\ 'handler': a:handler,
|
||||
\ }
|
||||
let command = s:build_command(a:cmd)
|
||||
|
||||
if has('nvim')
|
||||
let jobid = jobstart(command, extend(options, {
|
||||
\ 'on_stdout': function('s:on_stdout_nvim'),
|
||||
\ 'on_exit': function('s:on_exit_nvim')
|
||||
\ }))
|
||||
if a:0
|
||||
call chansend(jobid, a:1)
|
||||
call chanclose(jobid, 'stdin')
|
||||
endif
|
||||
return jobid
|
||||
else
|
||||
let job = job_start(command, {
|
||||
\ 'out_cb': function('s:on_stdout_vim', options),
|
||||
\ 'exit_cb': function('s:on_exit_vim', options)
|
||||
\ })
|
||||
if a:0
|
||||
let channel = job_getchannel(job)
|
||||
call ch_sendraw(channel, a:1)
|
||||
call ch_close_in(channel)
|
||||
endif
|
||||
return job
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:build_command(cmd)
|
||||
if has('nvim')
|
||||
if has('unix')
|
||||
return ['sh', '-c', a:cmd]
|
||||
elseif has('win64') || has('win32')
|
||||
return ['cmd.exe', '/c', a:cmd]
|
||||
else
|
||||
throw 'unknown os'
|
||||
endif
|
||||
else
|
||||
if has('unix')
|
||||
return ['sh', '-c', a:cmd]
|
||||
elseif has('win64') || has('win32')
|
||||
return 'cmd.exe /c '.a:cmd
|
||||
else
|
||||
throw 'unknown os'
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:on_stdout_vim(_channel, data) dict
|
||||
" a:data - an output line
|
||||
call add(self.stdoutbuffer, a:data)
|
||||
endfunction
|
||||
|
||||
function! s:on_exit_vim(job, exit_status) dict
|
||||
" Allow time for any buffered data to trigger out_cb.
|
||||
" 5m is an educated guess.
|
||||
sleep 5m
|
||||
call self.handler(self.stdoutbuffer)
|
||||
endfunction
|
||||
|
||||
function! s:on_stdout_nvim(_job_id, data, event) dict
|
||||
if empty(self.stdoutbuffer)
|
||||
let self.stdoutbuffer = a:data
|
||||
else
|
||||
let self.stdoutbuffer = self.stdoutbuffer[:-2] +
|
||||
\ [self.stdoutbuffer[-1] . a:data[0]] +
|
||||
\ a:data[1:]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:on_exit_nvim(_job_id, _data, _event) dict
|
||||
call map(self.stdoutbuffer, 'substitute(v:val, "\r$", "", "")')
|
||||
call self.handler(self.stdoutbuffer)
|
||||
endfunction
|
||||
329
pack/kite/start/vim-plugin/autoload/kite/client.vim
Normal file
329
pack/kite/start/vim-plugin/autoload/kite/client.vim
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
let s:port = empty($KITED_TEST_PORT) ? 46624 : $KITED_TEST_PORT
|
||||
let s:channel_base = 'localhost:'.s:port
|
||||
let s:base_url = 'http://127.0.0.1:'.s:port
|
||||
let s:editor_path = '/clientapi/editor'
|
||||
let s:onboarding_path = '/clientapi/plugins/onboarding_file?editor=vim'
|
||||
let s:hover_path = '/api/buffer/vim'
|
||||
let s:docs_path = 'kite://docs/'
|
||||
let s:status_path = '/clientapi/status?filename='
|
||||
let s:languages_path = '/clientapi/languages'
|
||||
let s:copilot_path = 'kite://home'
|
||||
let s:counter_path = '/clientapi/metrics/counters'
|
||||
let s:settings_path = 'kite://settings'
|
||||
let s:permissions_path = 'kite://settings/permissions'
|
||||
let s:max_file_size_path = '/clientapi/settings/max_file_size_kb'
|
||||
let s:codenav_path = '/codenav/editor/related'
|
||||
|
||||
|
||||
function! kite#client#docs(word)
|
||||
let url = s:docs_path.a:word
|
||||
call kite#utils#browse(url)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#settings()
|
||||
call kite#utils#browse(s:settings_path)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#permissions()
|
||||
call kite#utils#browse(s:permissions_path)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#copilot()
|
||||
call kite#utils#browse(s:copilot_path)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#counter(json, handler)
|
||||
let path = s:counter_path
|
||||
if has('channel')
|
||||
call s:async(function('s:timer_post', [path, g:kite_long_timeout, a:json, a:handler]))
|
||||
else
|
||||
call kite#async#execute(s:external_http_cmd(s:base_url.path, g:kite_long_timeout, 1), a:handler, a:json)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#onboarding_file(handler)
|
||||
let path = s:onboarding_path
|
||||
if has('channel')
|
||||
let response = s:internal_http(path, g:kite_short_timeout)
|
||||
else
|
||||
let response = s:external_http(s:base_url.path, g:kite_short_timeout)
|
||||
endif
|
||||
return a:handler(s:parse_response(response))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#status(filename, handler)
|
||||
let path = s:status_path.kite#utils#url_encode(a:filename)
|
||||
if has('channel')
|
||||
let response = s:internal_http(path, g:kite_short_timeout)
|
||||
else
|
||||
let response = s:external_http(s:base_url.path, g:kite_short_timeout)
|
||||
endif
|
||||
return a:handler(s:parse_response(response))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#languages(handler)
|
||||
let path = s:languages_path
|
||||
if has('channel')
|
||||
let response = s:internal_http(path, g:kite_short_timeout)
|
||||
else
|
||||
let response = s:external_http(s:base_url.path, g:kite_short_timeout)
|
||||
endif
|
||||
return a:handler(s:parse_response(response))
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns max file size in bytes, or -1 if not available.
|
||||
function! kite#client#max_file_size()
|
||||
let path = s:max_file_size_path
|
||||
if has('channel')
|
||||
let response = s:internal_http(path, g:kite_short_timeout)
|
||||
else
|
||||
let response = s:external_http(s:base_url.path, g:kite_short_timeout)
|
||||
endif
|
||||
let result = s:parse_response(response)
|
||||
if result.status == 200
|
||||
return result.body * 1024
|
||||
else
|
||||
return -1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#hover(filename, hash, cursor, handler)
|
||||
call s:wait_for_pending_events()
|
||||
|
||||
let path = s:hover_path.'/'.a:filename.'/'.a:hash.'/hover?cursor_runes='.a:cursor
|
||||
if has('channel')
|
||||
call s:async(function('s:timer_get', [path, g:kite_long_timeout, a:handler]))
|
||||
else
|
||||
call kite#async#execute(s:external_http_cmd(s:base_url.path, g:kite_long_timeout, 0),
|
||||
\ function('s:parse_and_handle', [a:handler]))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#signatures(json, handler)
|
||||
let path = s:editor_path.'/signatures'
|
||||
if has('channel')
|
||||
call s:async(function('s:timer_post', [path, g:kite_long_timeout, a:json, a:handler]))
|
||||
else
|
||||
call kite#async#execute(s:external_http_cmd(s:base_url.path, g:kite_long_timeout, 1),
|
||||
\ function('s:parse_and_handle', [a:handler]), a:json)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#completions(json, handler)
|
||||
let path = s:editor_path.'/complete'
|
||||
if has('channel')
|
||||
call s:async(function('s:timer_post', [path, g:kite_long_timeout, a:json, a:handler]))
|
||||
else
|
||||
call kite#async#execute(s:external_http_cmd(s:base_url.path, g:kite_long_timeout, 1),
|
||||
\ function('s:parse_and_handle', [a:handler]), a:json)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#request_related(json, handler)
|
||||
let path = s:codenav_path
|
||||
let timeout = 10000 "10s
|
||||
if has('channel')
|
||||
call s:async(function('s:timer_post', [path, timeout, a:json, a:handler]))
|
||||
else
|
||||
call kite#async#execute(s:external_http_cmd(s:base_url.path, timeout, 1),
|
||||
\ function('s:parse_and_handle', [a:handler]), a:json)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#client#post_event(json, handler)
|
||||
let path = s:editor_path.'/event'
|
||||
if has('channel')
|
||||
call s:async(function('s:timer_post', [path, g:kite_short_timeout, a:json, a:handler]))
|
||||
else
|
||||
call kite#async#execute(s:external_http_cmd(s:base_url.path, g:kite_short_timeout, 1),
|
||||
\ function('s:parse_and_handle', [a:handler]), a:json)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:timer_get(path, timeout, handler, timer)
|
||||
call a:handler(s:parse_response(s:internal_http(a:path, a:timeout)))
|
||||
endfunction
|
||||
|
||||
function! s:timer_post(path, timeout, json, handler, timer)
|
||||
call a:handler(s:parse_response(s:internal_http(a:path, a:timeout, a:json)))
|
||||
endfunction
|
||||
|
||||
function! s:async(callback)
|
||||
call timer_start(0, a:callback)
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:on_std_out(_channel, message) dict
|
||||
let self.stdoutbuffer .= a:message
|
||||
endfunction
|
||||
|
||||
|
||||
" Optional argument is json to be posted
|
||||
function! s:internal_http(path, timeout, ...)
|
||||
" Use HTTP 1.0 (not 1.1) to avoid having to parse chunked responses.
|
||||
if a:0
|
||||
let str = 'POST '.a:path." HTTP/1.0\nHost: localhost\nContent-Type: application/x-www-form-urlencoded\nContent-Length: ".len(a:1)."\n\n".a:1
|
||||
else
|
||||
let str = 'GET '.a:path." HTTP/1.0\nHost: localhost\n\n"
|
||||
endif
|
||||
call kite#utils#log('')
|
||||
call kite#utils#log(map(split(str, '\n', 1), '"> ".v:val'))
|
||||
|
||||
let options = {'stdoutbuffer': ''}
|
||||
try
|
||||
let channel = ch_open(s:channel_base, {
|
||||
\ 'mode': 'raw',
|
||||
\ 'callback': function('s:on_std_out', options)
|
||||
\ })
|
||||
catch /E898\|E901\|E902/
|
||||
call kite#utils#log('| Cannot open channel: '.str)
|
||||
return ''
|
||||
endtry
|
||||
|
||||
try
|
||||
call ch_sendraw(channel, str)
|
||||
catch /E630\|E631\|E906/
|
||||
call kite#utils#log('| Cannot send over channel: '.str)
|
||||
return ''
|
||||
endtry
|
||||
|
||||
let start = reltime()
|
||||
|
||||
while ch_status(channel) !=# 'closed'
|
||||
if reltimefloat(reltime(start))*1000 > a:timeout
|
||||
call kite#utils#log('| Timed out waiting for response (timeout: '.a:timeout.'ms)')
|
||||
try
|
||||
call ch_close(channel)
|
||||
catch /E906/
|
||||
" noop
|
||||
endtry
|
||||
return ''
|
||||
endif
|
||||
|
||||
sleep 5m
|
||||
endwhile
|
||||
|
||||
call kite#utils#log('| Received complete response: '.string(reltimefloat(reltime(start))*1000).'ms')
|
||||
|
||||
return options.stdoutbuffer
|
||||
endfunction
|
||||
|
||||
|
||||
" Optional argument is json to be posted
|
||||
function! s:external_http(url, timeout, ...)
|
||||
let cmd = s:external_http_cmd(a:url, a:timeout, a:0)
|
||||
if a:0
|
||||
return system(cmd, a:1)
|
||||
else
|
||||
return system(cmd)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" data argument is a boolean
|
||||
function! s:external_http_cmd(endpoint, timeout, data)
|
||||
let cmd = s:http_binary
|
||||
let cmd .= ' --timeout '.a:timeout.'ms'
|
||||
if a:data
|
||||
let cmd .= ' -'
|
||||
endif
|
||||
let cmd .= ' '.s:shellescape(a:endpoint)
|
||||
call kite#utils#log('')
|
||||
call kite#utils#log('> '.cmd)
|
||||
return cmd
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:parse_and_handle(handler, out)
|
||||
call a:handler(s:parse_response(a:out))
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the integer HTTP response code and the string body in a dictionary.
|
||||
"
|
||||
" lines - either a list (from async commands) or a string (from sync)
|
||||
function! s:parse_response(lines)
|
||||
if empty(a:lines)
|
||||
return {'status': 0, 'body': ''}
|
||||
endif
|
||||
|
||||
if type(a:lines) == v:t_string
|
||||
let lines = split(a:lines, '\r\?\n', 1)
|
||||
else
|
||||
let lines = a:lines
|
||||
endif
|
||||
call kite#utils#log(map(copy(lines), '"< ".v:val'))
|
||||
|
||||
if type(a:lines) == v:t_string
|
||||
let lines = split(a:lines, '\r\?\n')
|
||||
else
|
||||
let lines = a:lines
|
||||
endif
|
||||
|
||||
" Ignore occasional 100 Continue.
|
||||
let i = match(lines, '^HTTP/1.[01] [2345]\d\d ')
|
||||
if i == -1
|
||||
return {'status': 0, 'body': ''}
|
||||
endif
|
||||
let status = split(lines[i], ' ')[1]
|
||||
|
||||
let sep = match(lines, '^$', i)
|
||||
let body = join(lines[sep+1:], "\n")
|
||||
|
||||
return {'status': status, 'body': body}
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:wait_for_pending_events()
|
||||
while kite#events#any_events_pending()
|
||||
sleep 5m
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
|
||||
" Only used with NeoVim on not-Windows, in async jobs.
|
||||
function! s:shellescape(str)
|
||||
let [_shell, &shell] = [&shell, 'sh']
|
||||
let escaped = shellescape(a:str)
|
||||
let &shell = _shell
|
||||
return escaped
|
||||
endfunction
|
||||
|
||||
|
||||
let s:http_binary = kite#utils#lib('kite-http')
|
||||
|
||||
|
||||
if !empty($KITED_TEST_PORT)
|
||||
function! kite#client#request_history()
|
||||
let ret = json_decode(
|
||||
\ s:parse_response(
|
||||
\ s:internal_http('/testapi/request-history', 500)
|
||||
\ ).body
|
||||
\ )
|
||||
|
||||
if type(ret) != type([])
|
||||
throw '/testapi/request-history did not return a list (type '.type(ret).')'
|
||||
endif
|
||||
|
||||
return ret
|
||||
endfunction
|
||||
|
||||
function! kite#client#reset_request_history()
|
||||
call s:internal_http('/testapi/request-history/reset', 500)
|
||||
endfunction
|
||||
endif
|
||||
39
pack/kite/start/vim-plugin/autoload/kite/codenav.vim
Normal file
39
pack/kite/start/vim-plugin/autoload/kite/codenav.vim
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
function! kite#codenav#from_file()
|
||||
let filepath = kite#utils#filepath(0)
|
||||
call kite#codenav#request_related(filepath, v:null)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#codenav#from_line()
|
||||
let filepath = kite#utils#filepath(0)
|
||||
call kite#codenav#request_related(filepath, line("."))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#codenav#request_related(filename, line)
|
||||
let json = json_encode({
|
||||
\ 'editor': 'vim',
|
||||
\ 'location': {'filename': a:filename, 'line': a:line}
|
||||
\ })
|
||||
call kite#client#request_related(json, function('kite#codenav#handler'))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#codenav#handler(response) abort
|
||||
if a:response.status != 200
|
||||
if a:response.status == 0
|
||||
call kite#utils#warn("Kite could not be reached. Please check that Kite Engine is running.")
|
||||
return
|
||||
endif
|
||||
|
||||
|
||||
let err = json_decode(a:response.body)
|
||||
|
||||
if empty(err) || type(err.message) != v:t_string
|
||||
call kite#utils#warn("Oops! Something went wrong with Code Finder. Please try again later.")
|
||||
return
|
||||
endif
|
||||
|
||||
call kite#utils#warn(err.message)
|
||||
endif
|
||||
endfunction
|
||||
454
pack/kite/start/vim-plugin/autoload/kite/completion.vim
Normal file
454
pack/kite/start/vim-plugin/autoload/kite/completion.vim
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
let s:should_trigger_completion = 0
|
||||
let s:completion_counter = 0
|
||||
let s:begin = 0
|
||||
let s:end = 0
|
||||
|
||||
|
||||
function! kite#completion#replace_range()
|
||||
if empty(v:completed_item) | return | endif
|
||||
if !exists('s:startcol') | return | endif
|
||||
let startcol = s:startcol
|
||||
unlet s:startcol
|
||||
|
||||
if has_key(v:completed_item, 'user_data') && !empty(v:completed_item.user_data)
|
||||
let range = json_decode(v:completed_item.user_data).range
|
||||
let placeholders = json_decode(v:completed_item.user_data).placeholders
|
||||
elseif exists('b:kite_completions') && has_key(b:kite_completions, v:completed_item.word)
|
||||
let range = json_decode(b:kite_completions[v:completed_item.word]).range
|
||||
let placeholders = json_decode(b:kite_completions[v:completed_item.word]).placeholders
|
||||
else
|
||||
return
|
||||
endif
|
||||
|
||||
" The range seems to be wrong when placeholders are involved so stop here.
|
||||
if !empty(placeholders) | return | endif
|
||||
|
||||
let col = col('.')
|
||||
let _col = col
|
||||
|
||||
" end of range
|
||||
let n = range.end - s:offset_before_completion
|
||||
if n > 0
|
||||
execute 'normal! "_'.n.'x'
|
||||
let col -= n
|
||||
endif
|
||||
|
||||
" start of range
|
||||
let range_begin_col = col('.') - (kite#utils#character_offset() - range.begin)
|
||||
let n = startcol - range_begin_col
|
||||
if n > 0
|
||||
call kite#utils#goto_character(range.begin + 1)
|
||||
execute 'normal! "_'.n.'x'
|
||||
let col -= n
|
||||
endif
|
||||
|
||||
" restore cursor position
|
||||
if col != _col
|
||||
execute 'normal!' (col+1).'|'
|
||||
call s:feedkeys("\<Esc>la")
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#completion#expand_newlines()
|
||||
if empty(v:completed_item) | return | endif
|
||||
if match(v:completed_item.word, '\n') == -1 | return | endif
|
||||
|
||||
let parts = split(getline('.'), '\n', 1)
|
||||
delete _
|
||||
call append(line('.')-1, parts)
|
||||
-1
|
||||
" startinsert! doesn't seem to work with: package main^@import ""^@
|
||||
call s:feedkeys("\<Esc>A")
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#completion#insertcharpre()
|
||||
let s:should_trigger_completion = 1
|
||||
|
||||
" Trigger a fresh completion after every keystroke when the popup menu
|
||||
" is visible (by calling the function which TextChangedI would call
|
||||
" (TextChangedI is not triggered when the popup menu is visible)).
|
||||
if pumvisible()
|
||||
call kite#utils#log('# Trigger autocomplete because of pumvisible(): '.v:char)
|
||||
call kite#completion#autocomplete()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#completion#autocomplete()
|
||||
if !g:kite_auto_complete | return | endif
|
||||
if exists('b:kite_skip') && b:kite_skip | return | endif
|
||||
if wordcount().bytes > kite#max_file_size() | return | endif
|
||||
|
||||
if s:should_trigger_completion
|
||||
let s:should_trigger_completion = 0
|
||||
call s:feedkeys("\<C-X>\<C-U>")
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Manual invocation calls this method.
|
||||
function! kite#completion#complete(findstart, base)
|
||||
if a:findstart
|
||||
if !s:completeopt_suitable()
|
||||
let g:kite_auto_complete = 0
|
||||
return -3
|
||||
endif
|
||||
|
||||
" Store the buffer contents and cursor position here because when Vim
|
||||
" calls this function the second time (with a:findstart == 0) Vim has
|
||||
" already deleted the text between `start` and the cursor position.
|
||||
let s:cursor = kite#utils#character_offset()
|
||||
let s:text = kite#utils#buffer_contents()
|
||||
|
||||
let s:startcol = s:findstart()
|
||||
return s:startcol
|
||||
else
|
||||
" Leave CTRL-X submode so user can invoke other completion methods.
|
||||
call s:feedkeys("\<C-e>")
|
||||
call s:get_completions()
|
||||
if has('patch-8.1.0716')
|
||||
return v:none
|
||||
else
|
||||
return []
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:findstart()
|
||||
let line = getline('.')
|
||||
let start = col('.') - 1
|
||||
|
||||
let s:signature = s:before_function_call_argument(line[:start-1]) && s:begin == 0
|
||||
|
||||
if !s:signature
|
||||
while start > 0 && line[start - 1] =~ '\w'
|
||||
let start -= 1
|
||||
endwhile
|
||||
endif
|
||||
|
||||
return start
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:get_completions()
|
||||
if s:signature
|
||||
call kite#signature#increment_completion_counter()
|
||||
else
|
||||
let s:completion_counter = s:completion_counter + 1
|
||||
endif
|
||||
|
||||
let filename = kite#utils#filepath(0)
|
||||
|
||||
if s:signature
|
||||
let params = {
|
||||
\ 'filename': filename,
|
||||
\ 'editor': 'vim',
|
||||
\ 'text': s:text,
|
||||
\ 'cursor_runes': s:cursor,
|
||||
\ 'offset_encoding': 'utf-32'
|
||||
\ }
|
||||
else
|
||||
let params = {
|
||||
\ 'no_snippets': (g:kite_snippets ? v:false : v:true),
|
||||
\ 'no_unicode': (kite#utils#windows() ? v:true : v:false),
|
||||
\ 'filename': filename,
|
||||
\ 'editor': 'vim',
|
||||
\ 'text': s:text,
|
||||
\ 'position': {
|
||||
\ 'begin': (s:begin > 0 ? s:begin : s:cursor),
|
||||
\ 'end': (s:end > 0 ? s:end : s:cursor),
|
||||
\ },
|
||||
\ 'offset_encoding': 'utf-32',
|
||||
\ 'placeholders': []
|
||||
\ }
|
||||
let s:begin = 0
|
||||
let s:end = 0
|
||||
endif
|
||||
|
||||
let json = json_encode(params)
|
||||
|
||||
if s:signature
|
||||
call kite#client#signatures(json, function('kite#signature#handler', [kite#signature#completion_counter(), s:startcol]))
|
||||
else
|
||||
call kite#client#completions(json, function('kite#completion#handler', [s:completion_counter, s:startcol]))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#completion#handler(counter, startcol, response) abort
|
||||
call kite#utils#log('completion: '.a:response.status)
|
||||
|
||||
" Ignore old completion results.
|
||||
if a:counter != s:completion_counter
|
||||
return
|
||||
endif
|
||||
|
||||
if a:response.status != 200
|
||||
return
|
||||
endif
|
||||
|
||||
" This should not happen but evidently it sometimes does (#107).
|
||||
if empty(a:response.body)
|
||||
return
|
||||
endif
|
||||
|
||||
let json = json_decode(a:response.body)
|
||||
|
||||
" API should return 404 status when no completions but it sometimes
|
||||
" return 200 status and an empty response body, or "completions":"null".
|
||||
if empty(json) || type(json.completions) != v:t_list
|
||||
return
|
||||
endif
|
||||
|
||||
" 'display' is the LHS of each option in the completion menu
|
||||
let max_display_length = s:max_display_length(json.completions, 0)
|
||||
" 'hint' is the RHS of each option in the completion menu
|
||||
" Add 1 for leading space we add
|
||||
let max_hint_length = s:max_hint_length(json.completions) + 1
|
||||
|
||||
let available_win_width = s:winwidth() - a:startcol
|
||||
let max_width = available_win_width > g:kite_completion_max_width
|
||||
\ ? g:kite_completion_max_width : available_win_width
|
||||
|
||||
" pad LHS text gap RHS text gap kite branding pad scrollbar
|
||||
" | | | | | | | |
|
||||
let menu_width = 1 + max_display_length + 1 + max_hint_length + 1 + strdisplaywidth(kite#symbol()) + 2 + 1
|
||||
|
||||
if menu_width < max_width " no truncation
|
||||
let lhs_width = max_display_length
|
||||
let rhs_width = max_hint_length
|
||||
|
||||
elseif menu_width - 1 - max_hint_length < max_width " truncate rhs
|
||||
let lhs_width = max_display_length
|
||||
let rhs_width = max_width - (1 + max_display_length + 1 + strdisplaywidth(kite#symbol()) + 2 + 1)
|
||||
|
||||
else " drop rhs and truncate lhs
|
||||
let lhs_width = max_width - (1 + 1 + strdisplaywidth(kite#symbol()) + 2 + 1)
|
||||
let rhs_width = 0
|
||||
endif
|
||||
|
||||
let matches = []
|
||||
for c in json.completions
|
||||
call add(matches, s:adapt(c, lhs_width, rhs_width, 0))
|
||||
|
||||
if has_key(c, 'children')
|
||||
for child in c.children
|
||||
call add(matches, s:adapt(child, lhs_width, rhs_width, 1))
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
|
||||
if !has('patch-8.0.1493')
|
||||
let b:kite_completions = {}
|
||||
for item in filter(copy(matches), 'has_key(v:val, "user_data")')
|
||||
let b:kite_completions[item.word] = item.user_data
|
||||
endfor
|
||||
endif
|
||||
|
||||
if mode(1) ==# 'i'
|
||||
let s:startcol = a:startcol+1
|
||||
let s:offset_before_completion = kite#utils#character_offset()
|
||||
call complete(a:startcol+1, matches)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:adapt(completion_option, lhs_width, rhs_width, nesting)
|
||||
let display = s:indent(a:nesting) . a:completion_option.display
|
||||
let display = kite#utils#truncate(display, a:lhs_width)
|
||||
|
||||
" Ensure a minimum separation between abbr and menu of two spaces.
|
||||
" (Vim lines up the menus so that they are left-aligned 1 space after the longest abbr).
|
||||
let hint = ' ' . a:completion_option.hint
|
||||
|
||||
let hint = kite#utils#ralign(hint, a:rhs_width)
|
||||
|
||||
" Add the branding
|
||||
let hint .= ' '.kite#symbol()
|
||||
|
||||
return {
|
||||
\ 'word': a:completion_option.snippet.text,
|
||||
\ 'abbr': display,
|
||||
\ 'info': a:completion_option.documentation.text,
|
||||
\ 'menu': hint,
|
||||
\ 'equal': 1,
|
||||
\ 'user_data': json_encode({'placeholders': a:completion_option.snippet.placeholders, 'range': a:completion_option.replace})
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:max_hint_length(completions)
|
||||
let max = 0
|
||||
|
||||
for e in a:completions
|
||||
let len = strdisplaywidth(e.hint)
|
||||
if len > max
|
||||
let max = len
|
||||
endif
|
||||
|
||||
if has_key(e, 'children')
|
||||
let len = s:max_hint_length(e.children)
|
||||
if len > max
|
||||
let max = len
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
return max
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:max_display_length(completions, nesting)
|
||||
let max = 0
|
||||
|
||||
for e in a:completions
|
||||
let len = strdisplaywidth(s:indent(a:nesting) . e.display)
|
||||
if len > max
|
||||
let max = len
|
||||
endif
|
||||
|
||||
if has_key(e, 'children')
|
||||
let len = s:max_display_length(e.children, a:nesting+1)
|
||||
if len > max
|
||||
let max = len
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
return max
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:indent(nesting)
|
||||
return repeat(' ', a:nesting)
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns truthy if the cursor is:
|
||||
"
|
||||
" - just after an open parenthesis; or
|
||||
" - just after a comma inside a function call; or
|
||||
" - just after an equals sign inside a function call.
|
||||
"
|
||||
" Note this differs from all the other editor plugins. They can all show both
|
||||
" a signature popup and a completions popup at the same time, whereas Vim can
|
||||
" only show one popup. Therefore we need to switch its purpose between
|
||||
" signature info and completions info at appropriate points inside a function
|
||||
" call's arguments.
|
||||
"
|
||||
" line - the line up to the cursor position
|
||||
function! s:before_function_call_argument(line)
|
||||
" Other editors basically do this:
|
||||
" return a:line =~ '\v[(][^)]*$'
|
||||
|
||||
return a:line =~ '\v[(]([^)]+[=,])?\s*$'
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the width of the part of the current window which holds the buffer contents.
|
||||
function! s:winwidth()
|
||||
let w = winwidth(0)
|
||||
|
||||
if &number
|
||||
let w -= &numberwidth
|
||||
endif
|
||||
|
||||
let w -= &foldcolumn
|
||||
|
||||
if &signcolumn == 'yes' || (&signcolumn == 'auto' && s:signs_in_buffer())
|
||||
" TODO: neovim multiple sign columns
|
||||
let w -= 2
|
||||
endif
|
||||
|
||||
return w
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns 1 if the current buffer has any signs, 0 otherwise.
|
||||
function! s:signs_in_buffer()
|
||||
let bufinfo = getbufinfo(bufnr(''))[0]
|
||||
let signs = has_key(bufinfo, 'signs') ? bufinfo.signs : []
|
||||
return !empty(signs)
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:completeopt_suitable()
|
||||
let copts = split(&completeopt, ',')
|
||||
|
||||
if g:kite_auto_complete
|
||||
if index(copts, 'menuone') == -1
|
||||
call s:popup_warn("Kite: completeopt must contain 'menuone'")
|
||||
return 0
|
||||
endif
|
||||
|
||||
if index(copts, 'noinsert') == -1 && index(copts, 'noselect') == -1
|
||||
call s:popup_warn("Kite: completeopt must contain 'noinsert' and/or 'noselect'")
|
||||
return 0
|
||||
endif
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
|
||||
" feedkeys() by default adds keys to the end of the typeahead buffer. Any
|
||||
" keys already in the buffer will be processed first and may change Vim's
|
||||
" state, making the queued keys no longer appropriate (e.g. an insert mode key
|
||||
" combo being applied in normal mode). To avoid this we use the 'i' flag
|
||||
" which ensures the keys are processed immediately.
|
||||
function s:feedkeys(keys)
|
||||
call feedkeys(a:keys, 'i')
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:popup_warn(msg)
|
||||
if exists('*popup_notification')
|
||||
call popup_notification(a:msg, {
|
||||
\ 'pos': 'botleft',
|
||||
\ 'line': 'cursor-1',
|
||||
\ 'col': 'cursor',
|
||||
\ 'moved': 'any',
|
||||
\ 'time': 2000
|
||||
\ })
|
||||
elseif exists('*nvim_open_win')
|
||||
let lines = s:border(a:msg)
|
||||
let buf = nvim_create_buf(v:false, v:true)
|
||||
call nvim_buf_set_lines(buf, 0, -1, v:true, lines)
|
||||
let winid = nvim_open_win(buf, v:false, {
|
||||
\ 'relative': 'cursor',
|
||||
\ 'anchor': 'SW',
|
||||
\ 'row': 0,
|
||||
\ 'col': 0,
|
||||
\ 'width': strdisplaywidth(lines[0]),
|
||||
\ 'height': len(lines),
|
||||
\ 'focusable': v:false,
|
||||
\ 'style': 'minimal'
|
||||
\ })
|
||||
call nvim_win_set_option(winid, 'winhighlight', 'Normal:WarningMsg')
|
||||
call timer_start(2000, {-> execute("call nvim_win_close(".winid.", v:true)")})
|
||||
else
|
||||
call kite#utils#warn(a:msg)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Converts:
|
||||
"
|
||||
" A quick brown fox.
|
||||
"
|
||||
" Into:
|
||||
"
|
||||
" +--------------------+
|
||||
" | A quick brown fox. |
|
||||
" +--------------------+
|
||||
"
|
||||
function! s:border(text)
|
||||
return [
|
||||
\ '+'.repeat('-', strdisplaywidth(a:text)+2).'+',
|
||||
\ '| '.a:text.' |',
|
||||
\ '+'.repeat('-', strdisplaywidth(a:text)+2).'+'
|
||||
\ ]
|
||||
endfunction
|
||||
24
pack/kite/start/vim-plugin/autoload/kite/docs.vim
Normal file
24
pack/kite/start/vim-plugin/autoload/kite/docs.vim
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
function! kite#docs#docs()
|
||||
if &filetype != 'python'
|
||||
call kite#utils#warn('Docs are only available for Python')
|
||||
return
|
||||
endif
|
||||
|
||||
if empty(expand('<cword>')) | return | endif
|
||||
|
||||
let b:kite_id = ''
|
||||
|
||||
call kite#hover#hover()
|
||||
|
||||
while b:kite_id == ''
|
||||
sleep 5m
|
||||
endwhile
|
||||
|
||||
if b:kite_id == -1
|
||||
call kite#utils#info('No documentation available.')
|
||||
return
|
||||
endif
|
||||
|
||||
call kite#client#docs(b:kite_id)
|
||||
endfunction
|
||||
|
||||
42
pack/kite/start/vim-plugin/autoload/kite/document.vim
Normal file
42
pack/kite/start/vim-plugin/autoload/kite/document.vim
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
let kite#document#Document = {}
|
||||
|
||||
|
||||
" NOTE: this has to be called with a g: prefix.
|
||||
function! kite#document#Document.New(dict)
|
||||
let newDocument = copy(self)
|
||||
let newDocument.dict = a:dict
|
||||
return newDocument
|
||||
endfunction
|
||||
|
||||
|
||||
" Query the document, returning `default` if `key` does not exist
|
||||
" or if the value at `key` is not the same type as `default`.
|
||||
function! kite#document#Document.dig(key, default)
|
||||
let v = copy(self.dict)
|
||||
|
||||
for k in split(a:key, '\.')
|
||||
let matchlist = matchlist(k, '\v(\w+)\[(-?\d+)\]') " foo[42]
|
||||
|
||||
if !empty(matchlist)
|
||||
let kk = matchlist[1] " foo
|
||||
if has_key(v, kk)
|
||||
let v = get(v[kk], str2nr(matchlist[2]), a:default)
|
||||
else
|
||||
return a:default
|
||||
endif
|
||||
|
||||
elseif type(v) == v:t_dict && has_key(v, k)
|
||||
let v = v[k]
|
||||
|
||||
else
|
||||
return a:default
|
||||
endif
|
||||
endfor
|
||||
|
||||
if type(v) == type(a:default)
|
||||
return v
|
||||
endif
|
||||
|
||||
return a:default
|
||||
endfunction
|
||||
|
||||
46
pack/kite/start/vim-plugin/autoload/kite/events.vim
Normal file
46
pack/kite/start/vim-plugin/autoload/kite/events.vim
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
let s:events_pending = 0
|
||||
|
||||
function! kite#events#any_events_pending()
|
||||
return s:events_pending > 0
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#events#event(action)
|
||||
let filename = kite#utils#filepath(0)
|
||||
|
||||
if wordcount().bytes < kite#max_file_size()
|
||||
let action = a:action
|
||||
let text = kite#utils#buffer_contents()
|
||||
else
|
||||
let action = 'skip'
|
||||
let text = ''
|
||||
endif
|
||||
|
||||
let [sel_start, sel_end] = kite#utils#selected_region_characters()
|
||||
if [sel_start, sel_end] == [-1, -1]
|
||||
return
|
||||
endif
|
||||
let selections = [{ 'start': sel_start, 'end': sel_end, 'encoding': 'utf-32' }]
|
||||
|
||||
let json = json_encode({
|
||||
\ 'source': 'vim',
|
||||
\ 'filename': filename,
|
||||
\ 'text': text,
|
||||
\ 'action': action,
|
||||
\ 'selections': selections,
|
||||
\ 'editor_version': kite#utils#vim_version(),
|
||||
\ 'plugin_version': kite#utils#plugin_version()
|
||||
\ })
|
||||
|
||||
let s:events_pending += 1
|
||||
|
||||
call kite#client#post_event(json, function('kite#events#handler', [bufnr('')]))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#events#handler(bufnr, response)
|
||||
let s:events_pending -= 1
|
||||
|
||||
call setbufvar(a:bufnr, 'kite_skip', (a:response.status == 0 || a:response.status == 403))
|
||||
endfunction
|
||||
|
||||
66
pack/kite/start/vim-plugin/autoload/kite/hover.vim
Normal file
66
pack/kite/start/vim-plugin/autoload/kite/hover.vim
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
function! kite#hover#hover()
|
||||
if exists('b:kite_skip') && b:kite_skip | return | endif
|
||||
if wordcount().bytes > kite#max_file_size() | return | endif
|
||||
|
||||
let filename = kite#utils#filepath(1)
|
||||
let hash = kite#utils#buffer_md5()
|
||||
let cursor = kite#utils#cursor_characters()
|
||||
|
||||
call kite#client#hover(filename, hash, cursor, function('kite#hover#handler'))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#hover#goto_definition()
|
||||
if &filetype != 'python'
|
||||
call kite#utils#warn('Go to definition is only available for Python')
|
||||
return
|
||||
endif
|
||||
|
||||
if exists('b:kite_skip') && b:kite_skip | return | endif
|
||||
if wordcount().bytes > kite#max_file_size() | return | endif
|
||||
|
||||
let filename = kite#utils#filepath(1)
|
||||
let hash = kite#utils#buffer_md5()
|
||||
let cursor = kite#utils#cursor_characters()
|
||||
|
||||
call kite#client#hover(filename, hash, cursor, function('kite#hover#goto_definition_handler'))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#hover#handler(response)
|
||||
if a:response.status == 200
|
||||
let json = json_decode(a:response.body)
|
||||
let sym = type(json.symbol) == v:t_list ? json.symbol[0] : json.symbol
|
||||
let id = sym.id
|
||||
if empty(id)
|
||||
let b:kite_id = -1
|
||||
else
|
||||
let b:kite_id = id
|
||||
endif
|
||||
|
||||
else
|
||||
let b:kite_id = -1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#hover#goto_definition_handler(response)
|
||||
if a:response.status != 200
|
||||
call kite#utils#warn('unable to find a definition.')
|
||||
return
|
||||
endif
|
||||
|
||||
let json = json_decode(a:response.body)
|
||||
let definition = json.report.definition
|
||||
|
||||
if type(definition) != type({})
|
||||
call kite#utils#warn('unable to find a definition.')
|
||||
return
|
||||
endif
|
||||
|
||||
if definition.filename !=# expand('%:p')
|
||||
execute 'edit' definition.filename
|
||||
end
|
||||
execute definition.line
|
||||
normal! zz
|
||||
endfunction
|
||||
65
pack/kite/start/vim-plugin/autoload/kite/languages.vim
Normal file
65
pack/kite/start/vim-plugin/autoload/kite/languages.vim
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
let s:languages_supported_by_kited = []
|
||||
|
||||
" Returns true if we want Kite completions for the current buffer, false otherwise.
|
||||
function! kite#languages#supported_by_plugin()
|
||||
" Return false if the file extension is not recognised by kited.
|
||||
let recognised_extensions = [
|
||||
\ 'c',
|
||||
\ 'cc',
|
||||
\ 'cpp',
|
||||
\ 'cs',
|
||||
\ 'css',
|
||||
\ 'go',
|
||||
\ 'h',
|
||||
\ 'hpp',
|
||||
\ 'html',
|
||||
\ 'java',
|
||||
\ 'js',
|
||||
\ 'jsx',
|
||||
\ 'kt',
|
||||
\ 'less',
|
||||
\ 'm',
|
||||
\ 'php',
|
||||
\ 'py',
|
||||
\ 'pyw',
|
||||
\ 'rb',
|
||||
\ 'scala',
|
||||
\ 'sh',
|
||||
\ 'ts',
|
||||
\ 'tsx',
|
||||
\ 'vue',
|
||||
\ ]
|
||||
if index(recognised_extensions, expand('%:e')) == -1
|
||||
return 0
|
||||
endif
|
||||
|
||||
if g:kite_supported_languages == ['*']
|
||||
return 1
|
||||
endif
|
||||
|
||||
" Return false if the buffer's language is not one for which we want Kite completions.
|
||||
if index(g:kite_supported_languages, &filetype) == -1
|
||||
return 0
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns true if the current buffer's language is supported by kited, false otherwise.
|
||||
function! kite#languages#supported_by_kited()
|
||||
" Only check kited's languages once.
|
||||
if empty(s:languages_supported_by_kited)
|
||||
" A list of language names, e.g. ['bash', 'c', 'javascript', 'ruby', ...]
|
||||
let s:languages_supported_by_kited = kite#client#languages(function('kite#languages#handler'))
|
||||
endif
|
||||
|
||||
return index(s:languages_supported_by_kited, &filetype) != -1
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#languages#handler(response)
|
||||
if a:response.status != 200 | return [] | endif
|
||||
|
||||
return json_decode(a:response.body)
|
||||
endfunction
|
||||
29
pack/kite/start/vim-plugin/autoload/kite/metrics.vim
Normal file
29
pack/kite/start/vim-plugin/autoload/kite/metrics.vim
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"
|
||||
" Editor feature metrics
|
||||
"
|
||||
|
||||
let s:prompted = 0
|
||||
|
||||
|
||||
" Optional argument is value by which to increment named metric.
|
||||
" Defaults to 1.
|
||||
function! kite#metrics#requested(name)
|
||||
call s:increment('vim_'.a:name.'_requested')
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#metrics#fulfilled(name)
|
||||
call s:increment('vim_'.a:name.'_fulfilled')
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:increment(name)
|
||||
let json = json_encode({'name': a:name, 'value': 1})
|
||||
call kite#client#counter(json, function('kite#metrics#handler'))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#metrics#handler(response)
|
||||
" Noop
|
||||
endfunction
|
||||
|
||||
77
pack/kite/start/vim-plugin/autoload/kite/onboarding.vim
Normal file
77
pack/kite/start/vim-plugin/autoload/kite/onboarding.vim
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
let s:text = [
|
||||
\ 'Kite is now integrated with Vim',
|
||||
\ '',
|
||||
\ 'Kite is an AI-powered programming assistant',
|
||||
\ 'that shows you the right information at the right',
|
||||
\ 'time to keep you in the flow.',
|
||||
\ '',
|
||||
\ 'Please choose:',
|
||||
\ '',
|
||||
\ 'Learn how to use Kite',
|
||||
\ 'Hide',
|
||||
\ 'Hide forever',
|
||||
\ ]
|
||||
let s:option = 'onboarding_required'
|
||||
|
||||
|
||||
function! kite#onboarding#call(force)
|
||||
if !a:force
|
||||
if !kite#utils#get_setting(s:option, 1)
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
call kite#client#onboarding_file(function('kite#onboarding#handler'))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#onboarding#handler(response) abort
|
||||
if a:response.status == 200
|
||||
silent execute 'tabedit' json_decode(a:response.body)
|
||||
call kite#utils#set_setting(s:option, 0)
|
||||
|
||||
else
|
||||
if exists('*popup_menu')
|
||||
if !has('patch-8.1.1799')
|
||||
call s:unmap_menu_keys()
|
||||
endif
|
||||
let title = s:text[0]
|
||||
let winid = popup_menu(s:text[1:], {
|
||||
\ 'title': ' '.title.' ',
|
||||
\ 'callback': 'kite#onboarding#popup_callback',
|
||||
\ })
|
||||
call win_execute(winid, "normal! ".repeat('j', len(s:text[1:])-3))
|
||||
|
||||
else
|
||||
let s:text[-3] = '1. '.s:text[-3]
|
||||
let s:text[-2] = '2. '.s:text[-2]
|
||||
let s:text[-1] = '3. '.s:text[-1]
|
||||
|
||||
call s:handle_choice(inputlist(s:text)-1)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Invoked when popup closes.
|
||||
function! kite#onboarding#popup_callback(_, result)
|
||||
call s:handle_choice(2 - len(s:text[1:]) + a:result)
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:handle_choice(index)
|
||||
if a:index == 0 " learn now
|
||||
call kite#utils#browse('https://help.kite.com/category/47-vim-integration')
|
||||
elseif a:index == 2 " hide forever
|
||||
call kite#utils#set_setting(s:option, 0)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:unmap_menu_keys()
|
||||
silent! nunmap <CR>
|
||||
silent! nunmap <Space>
|
||||
silent! nunmap j
|
||||
silent! nunmap k
|
||||
silent! nunmap x
|
||||
endfunction
|
||||
209
pack/kite/start/vim-plugin/autoload/kite/signature.vim
Normal file
209
pack/kite/start/vim-plugin/autoload/kite/signature.vim
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
let s:completion_counter = 0
|
||||
|
||||
function! kite#signature#increment_completion_counter()
|
||||
let s:completion_counter = s:completion_counter + 1
|
||||
endfunction
|
||||
|
||||
function! kite#signature#completion_counter()
|
||||
return s:completion_counter
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#signature#handler(counter, startcol, response) abort
|
||||
call kite#utils#log('signature: '.a:response.status)
|
||||
|
||||
" Ignore old completion results.
|
||||
if a:counter != s:completion_counter
|
||||
return
|
||||
endif
|
||||
|
||||
if a:response.status != 200
|
||||
return
|
||||
endif
|
||||
|
||||
let json = json_decode(a:response.body)
|
||||
let call = g:kite#document#Document.New(json.calls[0])
|
||||
|
||||
let function_name = call.dig('func_name', '')
|
||||
if empty(function_name)
|
||||
let function_name = call.dig('callee.repr', '')
|
||||
endif
|
||||
let function_name = split(function_name, '\.')[-1]
|
||||
|
||||
let spacer = {'word': '', 'empty': 1, 'dup': 1}
|
||||
let indent = ' '
|
||||
let completions = []
|
||||
let wrap_width = 50
|
||||
|
||||
|
||||
"
|
||||
" Signature
|
||||
"
|
||||
let parameters = []
|
||||
let return_type = ''
|
||||
let [current_arg, in_kwargs] = [call.dig('arg_index', 0), call.dig('language_details.python.in_kwargs', 0)]
|
||||
let kind = call.dig('callee.kind', '')
|
||||
|
||||
" 1. Name of function with parameters.
|
||||
if kind ==# 'function'
|
||||
" 1.b.1. Parameters
|
||||
for parameter in call.dig('callee.details.function.parameters', [])
|
||||
" 1.b.1.a. Name
|
||||
let name = parameter.name
|
||||
" 1.b.1.b. Default value
|
||||
if kite#utils#present(parameter.language_details.python, 'default_value')
|
||||
let name .= '='.parameter.language_details.python.default_value[0].repr
|
||||
endif
|
||||
" 2. Highlight current argument
|
||||
if !in_kwargs && len(parameters) == current_arg
|
||||
let name = '*'.name.'*'
|
||||
endif
|
||||
call add(parameters, name)
|
||||
endfor
|
||||
" 1.b.2. vararg indicator
|
||||
let vararg = call.dig('callee.details.function.language_details.python.vararg', {})
|
||||
if !empty(vararg)
|
||||
call add(parameters, '*'.vararg.name)
|
||||
endif
|
||||
" 1.b.3. keyword arguments indicator
|
||||
let kwarg = call.dig('callee.details.function.language_details.python.kwarg', {})
|
||||
if !empty(kwarg)
|
||||
call add(parameters, '**'.kwarg.name)
|
||||
endif
|
||||
" 1.b.4. Return type
|
||||
let return_value = call.dig('callee.details.function.return_value', [])
|
||||
if !empty(return_value)
|
||||
let return_type = ' -> '.return_value[0].type
|
||||
endif
|
||||
|
||||
elseif kind ==# 'type'
|
||||
" 1.c.1. Parameters
|
||||
for parameter in call.dig('callee.details.type.language_details.python.constructor.parameters', [])
|
||||
" 1.c.1.a. Name
|
||||
let name = parameter.name
|
||||
" 1.c.1.b. Default value
|
||||
if kite#utils#present(parameter.language_details.python, 'default_value')
|
||||
let name .= '='.parameter.language_details.python.default_value[0].repr
|
||||
endif
|
||||
" 2. Highlight current argument
|
||||
if !in_kwargs && len(parameters) == current_arg
|
||||
let name = '*'.name.'*'
|
||||
endif
|
||||
call add(parameters, name)
|
||||
endfor
|
||||
" 1.c.2. vararg indicator
|
||||
let vararg = call.dig('callee.details.type.language_details.python.constructor.language_details.python.vararg', {})
|
||||
if !empty(vararg)
|
||||
call add(parameters, '*'.vararg.name)
|
||||
endif
|
||||
" 1.c.3. keyword arguments indicator
|
||||
let kwarg = call.dig('callee.details.type.language_details.python.constructor.language_details.python.kwarg', {})
|
||||
if !empty(kwarg)
|
||||
call add(parameters, '*'.kwarg.name)
|
||||
endif
|
||||
" 1.c.4. Return type
|
||||
let return_type = ' -> '.function_name
|
||||
endif
|
||||
|
||||
" The completion popup does not wrap long lines so we wrap manually.
|
||||
for line in kite#utils#wrap(kite#symbol().' '.function_name.'('.join(parameters, ', ').')'.return_type, wrap_width, 4)
|
||||
let completion = {
|
||||
\ 'word': '',
|
||||
\ 'abbr': line,
|
||||
\ 'empty': 1,
|
||||
\ 'dup': 1
|
||||
\ }
|
||||
call add(completions, completion)
|
||||
endfor
|
||||
|
||||
|
||||
" 3. Keyword arguments
|
||||
let kwarg_parameters = call.dig('callee.details.function.kwarg_parameters', [])
|
||||
if !empty(kwarg_parameters)
|
||||
call add(completions, spacer)
|
||||
call add(completions, s:heading('**kw'))
|
||||
|
||||
for kwarg in kwarg_parameters
|
||||
let name = kwarg.name
|
||||
let types = kite#utils#map_join(kwarg.inferred_value, 'repr', '|')
|
||||
if empty(types)
|
||||
let types = ''
|
||||
endif
|
||||
|
||||
call add(completions, {
|
||||
\ 'word': name.'=',
|
||||
\ 'abbr': indent.name,
|
||||
\ 'menu': types,
|
||||
\ 'empty': 1,
|
||||
\ 'dup': 1
|
||||
\ })
|
||||
endfor
|
||||
endif
|
||||
|
||||
|
||||
" 4. Popular patterns
|
||||
if kite#signature#should_show_popular_patterns()
|
||||
let signatures = call.dig('signatures', [])
|
||||
if len(signatures) > 0
|
||||
call add(completions, spacer)
|
||||
call add(completions, s:heading('How Others Used This'))
|
||||
endif
|
||||
|
||||
for signature in signatures
|
||||
let sigdoc = g:kite#document#Document.New(signature)
|
||||
|
||||
" b. Arguments
|
||||
let arguments = []
|
||||
for arg in sigdoc.dig('args', [])
|
||||
call add(arguments, arg.name)
|
||||
endfor
|
||||
|
||||
" c. Keyword arguments
|
||||
for kwarg in sigdoc.dig('language_details.python.kwargs', [])
|
||||
let name = kwarg.name
|
||||
let examples = kite#utils#coerce(kwarg.types[0], 'examples', [])
|
||||
if len(examples) > 0
|
||||
let name .= '='.examples[0]
|
||||
endif
|
||||
call add(arguments, name)
|
||||
endfor
|
||||
|
||||
|
||||
for line in kite#utils#wrap(function_name.'('.join(arguments, ', ').')', wrap_width, 2)
|
||||
let completion = {
|
||||
\ 'word': '',
|
||||
\ 'abbr': indent.line,
|
||||
\ 'empty': 1,
|
||||
\ 'equal': 1,
|
||||
\ 'dup': 1
|
||||
\ }
|
||||
call add(completions, completion)
|
||||
endfor
|
||||
endfor
|
||||
endif
|
||||
|
||||
if mode(1) ==# 'i'
|
||||
call complete(a:startcol+1, completions)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#signature#should_show_popular_patterns()
|
||||
return kite#utils#get_setting('show_popular_patterns', 0)
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
function! kite#signature#show_popular_patterns()
|
||||
call kite#utils#set_setting('show_popular_patterns', 1)
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#signature#hide_popular_patterns()
|
||||
call kite#utils#set_setting('show_popular_patterns', 0)
|
||||
endfunction
|
||||
|
||||
|
||||
function s:heading(text)
|
||||
return {'abbr': a:text.':', 'word': '', 'empty': 1, 'dup': 1}
|
||||
endfunction
|
||||
413
pack/kite/start/vim-plugin/autoload/kite/snippet.vim
Normal file
413
pack/kite/start/vim-plugin/autoload/kite/snippet.vim
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
function! s:setup_stack()
|
||||
if exists('b:kite_stack') | return | endif
|
||||
|
||||
" stack:
|
||||
" [
|
||||
" { index: 0, placeholders: { ... } }, <-- depth 0
|
||||
" { index: 0, placeholders: { ... } }, <-- depth 1
|
||||
" ... <-- depth n
|
||||
" ]
|
||||
"
|
||||
" index - the currently active placeholder at that depth
|
||||
let b:kite_stack = {'stack': []}
|
||||
|
||||
function! b:kite_stack.pop()
|
||||
return remove(self.stack, -1)
|
||||
endfunction
|
||||
|
||||
function! b:kite_stack.peek()
|
||||
return get(self.stack, -1)
|
||||
endfunction
|
||||
|
||||
function! b:kite_stack.push(item)
|
||||
call add(self.stack, a:item)
|
||||
endfunction
|
||||
|
||||
function! b:kite_stack.is_empty()
|
||||
return empty(self.stack)
|
||||
endfunction
|
||||
|
||||
function! b:kite_stack.empty()
|
||||
let self.stack = []
|
||||
endfunction
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#snippet#complete_done()
|
||||
if empty(v:completed_item) | return | endif
|
||||
call s:setup_stack()
|
||||
|
||||
if has_key(v:completed_item, 'user_data') && !empty(v:completed_item.user_data)
|
||||
let placeholders = json_decode(v:completed_item.user_data).placeholders
|
||||
elseif exists('b:kite_completions') && has_key(b:kite_completions, v:completed_item.word)
|
||||
let placeholders = json_decode(b:kite_completions[v:completed_item.word]).placeholders
|
||||
let b:kite_completions = {}
|
||||
else
|
||||
return
|
||||
endif
|
||||
|
||||
" Send the edit event. Normally this is sent automatically on TextChanged(I).
|
||||
" But for some reason this doesn't fire when a completion has a snippet placeholder.
|
||||
call kite#events#event('edit')
|
||||
|
||||
if empty(placeholders)
|
||||
if b:kite_stack.is_empty()
|
||||
return
|
||||
else
|
||||
call kite#snippet#next_placeholder()
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
let b:kite_linenr = line('.')
|
||||
let b:kite_line_length = col('$')
|
||||
|
||||
call s:setup_maps()
|
||||
call s:setup_autocmds()
|
||||
|
||||
" Calculate column number (col_begin) of start of each placeholder, and placeholder length.
|
||||
let inserted_text = v:completed_item.word
|
||||
let insertion_start = col('.') - strdisplaywidth(inserted_text)
|
||||
let b:kite_insertion_end = col('.')
|
||||
|
||||
for ph in placeholders
|
||||
let ph.col_begin = insertion_start + ph.begin
|
||||
let ph.length = ph.end - ph.begin
|
||||
unlet ph.begin ph.end
|
||||
endfor
|
||||
|
||||
" Update placeholder locations.
|
||||
"
|
||||
" todo move this into the push() function?
|
||||
" note this is very similar to s:update_placeholder_locations()
|
||||
if !b:kite_stack.is_empty()
|
||||
" current placeholder which has just been completed
|
||||
let level = b:kite_stack.peek()
|
||||
let ph = level.placeholders[level.index]
|
||||
let ph_new_length = col('.') - ph.col_begin
|
||||
let ph_length_delta = ph_new_length - ph.length
|
||||
let ph.length = ph_new_length
|
||||
let marker = ph.col_begin
|
||||
|
||||
" following placeholders at same level
|
||||
for ph in level.placeholders[level.index+1:]
|
||||
let ph.col_begin += ph_length_delta
|
||||
endfor
|
||||
|
||||
" placeholders at outer levels
|
||||
for level in b:kite_stack.stack[:-2]
|
||||
for ph in level.placeholders
|
||||
if ph.col_begin > marker
|
||||
let ph.col_begin += ph_length_delta
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
endif
|
||||
|
||||
call b:kite_stack.push({'placeholders': placeholders, 'index': 0})
|
||||
|
||||
" Move to first placeholder.
|
||||
call s:placeholder(0)
|
||||
endfunction
|
||||
|
||||
|
||||
" Go to next placeholder at current level, if there is one, or first placeholder at next level otherwise.
|
||||
function! kite#snippet#next_placeholder()
|
||||
call s:update_placeholder_locations()
|
||||
call s:placeholder(b:kite_stack.peek().index + 1)
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
function! kite#snippet#previous_placeholder(...)
|
||||
call s:placeholder(b:kite_stack.peek().index - 1 - (a:0 ? a:1 : 0))
|
||||
endfunction
|
||||
|
||||
|
||||
" Move to the placeholder at index and select its text.
|
||||
function! s:placeholder(index)
|
||||
let index = a:index
|
||||
|
||||
let level = b:kite_stack.peek()
|
||||
let placeholders = level.placeholders
|
||||
|
||||
" Clear highlights before we pop the stack.
|
||||
call s:clear_all_placeholder_highlights()
|
||||
|
||||
if index < 0
|
||||
" If no other levels in stack
|
||||
if len(b:kite_stack.stack) == 1
|
||||
" Stay with first placeholder and proceed
|
||||
let index = 0
|
||||
else
|
||||
call b:kite_stack.pop()
|
||||
call s:placeholder(b:kite_stack.peek().index - 1)
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
" if navigating forward from last placeholder of current level
|
||||
if index == len(placeholders)
|
||||
" If no other levels in stack
|
||||
if len(b:kite_stack.stack) == 1
|
||||
call s:goto_initial_completion_end()
|
||||
else
|
||||
call b:kite_stack.pop()
|
||||
call s:placeholder(b:kite_stack.peek().index + 1)
|
||||
endif
|
||||
return
|
||||
endif
|
||||
|
||||
call s:highlight_current_level_placeholders()
|
||||
|
||||
let level.index = index
|
||||
let ph = placeholders[index]
|
||||
|
||||
" store line length before placeholder gets changed by user
|
||||
" let b:kite_line_length = col('$')
|
||||
|
||||
if ph.length == 0
|
||||
normal! h
|
||||
return
|
||||
endif
|
||||
|
||||
" insert mode -> normal mode
|
||||
stopinsert
|
||||
|
||||
let linenr = line('.')
|
||||
call setpos("'<", [0, linenr, ph.col_begin])
|
||||
call setpos("'>", [0, linenr, ph.col_begin + ph.length - (mode() == 'n' ? 1 : 0)])
|
||||
" normal mode -> visual mode -> select mode
|
||||
execute "normal! gv\<C-G>"
|
||||
if mode() ==# 'S'
|
||||
execute "normal! \<C-O>gh"
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:goto_initial_completion_end()
|
||||
" call setpos('.', [0, b:kite_linenr, b:kite_insertion_end + col('$') - b:kite_line_length - 1])
|
||||
call setpos('.', [0, b:kite_linenr, col('$')])
|
||||
startinsert!
|
||||
call s:teardown()
|
||||
endfunction
|
||||
|
||||
|
||||
" Adjust current and subsequent placeholders for the amount of text entered
|
||||
" at the placeholder we are leaving.
|
||||
function! s:update_placeholder_locations()
|
||||
if !exists('b:kite_line_length') | return | endif
|
||||
|
||||
let line_length_delta = col('$') - b:kite_line_length
|
||||
|
||||
" current placeholder
|
||||
let level = b:kite_stack.peek()
|
||||
let ph = level.placeholders[level.index]
|
||||
let marker = ph.col_begin
|
||||
let ph.length += line_length_delta
|
||||
|
||||
" subsequent placeholders at current level
|
||||
for ph in level.placeholders[level.index+1:]
|
||||
let ph.col_begin += line_length_delta
|
||||
endfor
|
||||
|
||||
" placeholders at outer levels
|
||||
for level in b:kite_stack.stack[:-2]
|
||||
for ph in level.placeholders
|
||||
if ph.col_begin > marker
|
||||
let ph.col_begin += line_length_delta
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
|
||||
let b:kite_line_length = col('$')
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:highlight_current_level_placeholders()
|
||||
let group = s:highlight_group_for_placeholders()
|
||||
if empty(group) | return | endif
|
||||
|
||||
let linenr = line('.')
|
||||
for ph in b:kite_stack.peek().placeholders
|
||||
let ph.matchid = matchaddpos(group, [[linenr, ph.col_begin, ph.length]])
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
" Clears highlights of placeholders in the stack.
|
||||
"
|
||||
" Note: if we need a way to clear highlights of placeholders which are no
|
||||
" longer in the stack (because they have been popped) we could use a custom
|
||||
" highlight group (e.g. KiteUnderline linked to Underline), call getmatches(),
|
||||
" and remove all matches using the custom highlight group.
|
||||
function! s:clear_all_placeholder_highlights()
|
||||
for level in b:kite_stack.stack
|
||||
for ph in level.placeholders
|
||||
if has_key(ph, 'matchid')
|
||||
call matchdelete(ph.matchid)
|
||||
unlet ph.matchid
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
" Many plugins use vmap for visual-mode mappings but vmap maps both
|
||||
" visual-mode and select-mode (they should use xmap instead). Assume any
|
||||
" visual-mode mappings for printable characters are not wanted and remove them
|
||||
" (but remember them so we can restore them afterwards). Similarly for map.
|
||||
" Assume any select-only-mode maps are deliberate.
|
||||
"
|
||||
" :help mapmode-s
|
||||
" :help Select-mode-mapping
|
||||
function! s:remove_smaps_for_printable_characters()
|
||||
let b:kite_maps = []
|
||||
let printable_keycodes = [
|
||||
\ '<Space>',
|
||||
\ '<Bslash>',
|
||||
\ '<Tab>',
|
||||
\ '<C-Tab>',
|
||||
\ '<NL>',
|
||||
\ '<CR>',
|
||||
\ '<BS>',
|
||||
\ '<Leader>',
|
||||
\ '<LocalLeader>'
|
||||
\ ]
|
||||
|
||||
" Get a list of maps active in select mode.
|
||||
for scope in ['<buffer>', '']
|
||||
redir => maps | silent execute 'smap' scope | redir END
|
||||
|
||||
let mappings = split(maps, "\n")
|
||||
|
||||
" 'No mapping found' or localised equivalent (starts with capital letter).
|
||||
if len(mappings) == 1 && mappings[0][0] =~ '\u' | continue | endif
|
||||
|
||||
" Assume select-mode maps are deliberate and ignore them.
|
||||
call filter(mappings, 'v:val[0:2] !~# "s"')
|
||||
|
||||
for mapping in mappings
|
||||
let lhs = matchlist(mapping, '\v^...(\S+)\s.*')[1]
|
||||
" ^^^ ^^^
|
||||
" mode lhs
|
||||
|
||||
" Ignore keycodes for non-printable characters, e.g. <Left>
|
||||
if lhs[0] == '<' && index(printable_keycodes, lhs) == -1 | continue | endif
|
||||
|
||||
" Remember the mapping so we can restore it later.
|
||||
call add(b:kite_maps, maparg(lhs, 's', 0, 1))
|
||||
|
||||
" Remove the mapping.
|
||||
silent! execute 'sunmap' scope lhs
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:restore_smaps()
|
||||
for mapping in b:kite_maps
|
||||
silent! execute mapping.mode . (mapping.noremap ? 'nore' : '') . 'map '
|
||||
\ . (mapping.buffer ? '<buffer> ' : '')
|
||||
\ . (mapping.expr ? '<expr> ' : '')
|
||||
\ . (mapping.nowait ? '<nowait> ' : '')
|
||||
\ . (mapping.silent ? '<silent> ' : '')
|
||||
\ . mapping.lhs . ' '
|
||||
\ . substitute(mapping.rhs, '<SID>', '<SNR>'.mapping.sid.'_', 'g')
|
||||
endfor
|
||||
|
||||
unlet! b:kite_maps
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:setup_maps()
|
||||
execute 'inoremap <buffer> <silent> <expr>' g:kite_next_placeholder 'pumvisible() ? "<C-Y>" : "<C-\><C-O>:call kite#snippet#next_placeholder()<CR>"'
|
||||
execute 'inoremap <buffer> <silent> <expr>' g:kite_previous_placeholder 'pumvisible() ? "<C-Y><C-G>:<C-U>call kite#snippet#previous_placeholder(2)<CR>" : "<C-\><C-O>:call kite#snippet#previous_placeholder()<CR>"'
|
||||
execute 'snoremap <buffer> <silent>' g:kite_next_placeholder '<Esc>:call kite#snippet#next_placeholder()<CR>'
|
||||
execute 'snoremap <buffer> <silent>' g:kite_previous_placeholder '<Esc>:call kite#snippet#previous_placeholder()<CR>'
|
||||
|
||||
call s:remove_smaps_for_printable_characters()
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#snippet#teardown_maps()
|
||||
execute 'silent! iunmap <buffer>' g:kite_next_placeholder
|
||||
execute 'silent! iunmap <buffer>' g:kite_previous_placeholder
|
||||
execute 'silent! sunmap <buffer>' g:kite_next_placeholder
|
||||
execute 'silent! sunmap <buffer>' g:kite_previous_placeholder
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:setup_autocmds()
|
||||
augroup KiteSnippets
|
||||
autocmd! * <buffer>
|
||||
|
||||
autocmd CursorMovedI <buffer>
|
||||
\ call s:update_placeholder_locations() |
|
||||
\ call s:clear_all_placeholder_highlights() |
|
||||
\ call s:highlight_current_level_placeholders()
|
||||
autocmd CursorMoved,CursorMovedI <buffer> call s:cursormoved()
|
||||
autocmd InsertLeave <buffer> call s:insertleave()
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:teardown_autocmds()
|
||||
autocmd! KiteSnippets * <buffer>
|
||||
endfunction
|
||||
|
||||
|
||||
" Called to deactivate all placeholders.
|
||||
function! s:teardown()
|
||||
call s:clear_all_placeholder_highlights()
|
||||
call kite#snippet#teardown_maps()
|
||||
call s:teardown_autocmds()
|
||||
call s:restore_smaps()
|
||||
call b:kite_stack.empty()
|
||||
unlet! b:kite_linenr b:kite_line_length b:kite_insertion_end
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:highlight_group_for_placeholders()
|
||||
for group in ['Special', 'SpecialKey', 'Underline', 'DiffChange']
|
||||
if hlexists(group)
|
||||
return group
|
||||
endif
|
||||
endfor
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:cursormoved()
|
||||
if !exists('b:kite_linenr') | return | endif
|
||||
if b:kite_linenr == line('.') | return | endif
|
||||
|
||||
" TODO check whether the cursor is outside the bounds of the completion?
|
||||
|
||||
call s:teardown()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:insertleave()
|
||||
" Modes established by experimentation.
|
||||
if mode(1) !=# 's' && mode(1) !=# ((has('patch-8.1.0225') || has('nvim-0.4.0')) ? 'niI' : 'n')
|
||||
call s:teardown()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:debug_stack()
|
||||
if b:kite_stack.is_empty()
|
||||
echom 'stack empty'
|
||||
return
|
||||
endif
|
||||
let i = 0
|
||||
for level in b:kite_stack.stack
|
||||
echom 'level' i
|
||||
echom ' index' level.index
|
||||
for pholder in level.placeholders
|
||||
echom ' '.string(pholder)
|
||||
endfor
|
||||
let i += 1
|
||||
endfor
|
||||
endfunction
|
||||
62
pack/kite/start/vim-plugin/autoload/kite/status.vim
Normal file
62
pack/kite/start/vim-plugin/autoload/kite/status.vim
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
" Updates the status of the current buffer.
|
||||
"
|
||||
" Optional argument is a timer id (when called by a timer).
|
||||
function! kite#status#status(...)
|
||||
if !s:status_in_status_line() | return | endif
|
||||
|
||||
let buf = bufnr('')
|
||||
let msg = 'NOT SET'
|
||||
|
||||
" Check kited status (installed / running) every 10 file status checks.
|
||||
let counter = getbufvar(buf, 'kite_status_counter', 0)
|
||||
if counter == 0
|
||||
if !kite#utils#kite_running()
|
||||
let msg = 'Kite: not running'
|
||||
if !kite#utils#kite_installed()
|
||||
let msg = 'Kite: not installed'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
call setbufvar(buf, 'kite_status_counter', (counter + 1) % 10)
|
||||
|
||||
if wordcount().bytes > kite#max_file_size()
|
||||
let msg = 'Kite: file too large'
|
||||
endif
|
||||
|
||||
if msg !=# 'NOT SET'
|
||||
if msg !=# getbufvar(buf, 'kite_status')
|
||||
call setbufvar(buf, 'kite_status', msg)
|
||||
redrawstatus
|
||||
endif
|
||||
return
|
||||
endif
|
||||
|
||||
let filename = kite#utils#filepath(0)
|
||||
call kite#client#status(filename, function('kite#status#handler', [buf]))
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#status#handler(buffer, response)
|
||||
call kite#utils#log('kite status status: '.a:response.status.', body: '.a:response.body)
|
||||
if a:response.status != 200 | return | endif
|
||||
|
||||
let json = json_decode(a:response.body)
|
||||
|
||||
let msg = ''
|
||||
|
||||
let suffix = get(json, 'short', 'FIELD MISSING')
|
||||
if suffix !=# 'FIELD MISSING'
|
||||
let msg = join(['Kite: ', suffix], '')
|
||||
endif
|
||||
|
||||
if msg !=# getbufvar(a:buffer, 'kite_status')
|
||||
call setbufvar(a:buffer, 'kite_status', msg)
|
||||
redrawstatus
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:status_in_status_line()
|
||||
return stridx(&statusline, 'kite#statusline()') != -1
|
||||
endfunction
|
||||
|
||||
611
pack/kite/start/vim-plugin/autoload/kite/utils.vim
Normal file
611
pack/kite/start/vim-plugin/autoload/kite/utils.vim
Normal file
|
|
@ -0,0 +1,611 @@
|
|||
" Values for s:os are used in plugin directory structure
|
||||
" and also metric values.
|
||||
if has('win64') || has('win32') || has('win32unix')
|
||||
let s:os = 'windows'
|
||||
else
|
||||
let s:os = empty(findfile('/sbin/launchd')) ? 'linux' : 'macos'
|
||||
endif
|
||||
|
||||
function! kite#utils#windows()
|
||||
return s:os ==# 'windows'
|
||||
endfunction
|
||||
|
||||
function! kite#utils#macos()
|
||||
return s:os ==# 'macos'
|
||||
endfunction
|
||||
|
||||
let s:separator = !exists('+shellslash') || &shellslash ? '/' : '\'
|
||||
let s:plugin_dir = expand('<sfile>:p:h:h:h')
|
||||
let s:doc_dir = s:plugin_dir.s:separator.'doc'
|
||||
let s:lib_dir = s:plugin_dir.s:separator.'lib'
|
||||
let s:lib_subdir = s:lib_dir.s:separator.(s:os)
|
||||
let s:vim_version = ''
|
||||
let s:plugin_version = ''
|
||||
|
||||
|
||||
function! kite#utils#vim_version()
|
||||
if !empty(s:vim_version)
|
||||
return s:vim_version
|
||||
endif
|
||||
let s:vim_version = kite#utils#normalise_version(execute('version'))
|
||||
return s:vim_version
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#normalise_version(version)
|
||||
let lines = split(a:version, '\n')
|
||||
|
||||
if lines[0] =~ 'NVIM'
|
||||
" Or use api_info().version.
|
||||
return lines[0] " e.g. NVIM v0.2.2
|
||||
else
|
||||
let [major, minor] = matchlist(lines[0], '\v(\d)\.(\d+)')[1:2]
|
||||
|
||||
let patch_line = match(lines, ': \d')
|
||||
if patch_line == -1
|
||||
let patches = '0'
|
||||
else
|
||||
let patches = substitute(split(lines[patch_line], ': ')[1], ' ', '', 'g')
|
||||
endif
|
||||
return join([major, minor, patches], '.') " e.g. 8.1.1-582
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#plugin_version()
|
||||
if !empty(s:plugin_version)
|
||||
return s:plugin_version
|
||||
endif
|
||||
|
||||
let s:plugin_version = readfile(s:plugin_dir.s:separator.'VERSION')[0]
|
||||
|
||||
return s:plugin_version
|
||||
endfunction
|
||||
|
||||
|
||||
" From tpope/vim-fugitive
|
||||
function! s:winshell()
|
||||
return kite#utils#windows() && &shellcmdflag !~# '^-'
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#browse(url)
|
||||
if kite#utils#windows()
|
||||
let cmd = 'cmd /c start "" "'.a:url.'"'
|
||||
else
|
||||
let cmd = 'open "'.a:url.'"'
|
||||
endif
|
||||
silent call system(cmd)
|
||||
endfunction
|
||||
|
||||
|
||||
" From tpope/vim-fugitive
|
||||
function! s:shellescape(arg)
|
||||
if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
|
||||
return a:arg
|
||||
elseif s:winshell()
|
||||
return '"'.substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g').'"'
|
||||
else
|
||||
return shellescape(a:arg)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
if kite#utils#windows()
|
||||
let s:settings_dir = join([$LOCALAPPDATA, 'Kite'], s:separator)
|
||||
else
|
||||
let s:settings_dir = expand('~/.kite')
|
||||
endif
|
||||
if !isdirectory(s:settings_dir)
|
||||
call mkdir(s:settings_dir, 'p')
|
||||
endif
|
||||
let s:settings_path = s:settings_dir.s:separator.'vim-plugin.json'
|
||||
|
||||
|
||||
" Get the value for the given key.
|
||||
" If the key has not been set, returns the default value if given
|
||||
" (i.e. the optional argument) or -1 otherwise.
|
||||
function! kite#utils#get_setting(key, ...)
|
||||
let settings = s:settings()
|
||||
return get(settings, a:key, (a:0 ? a:1 : -1))
|
||||
endfunction
|
||||
|
||||
" Sets the value for the key.
|
||||
function! kite#utils#set_setting(key, value)
|
||||
let settings = s:settings()
|
||||
let settings[a:key] = a:value
|
||||
let json_str = json_encode(settings)
|
||||
call writefile([json_str], s:settings_path)
|
||||
endfunction
|
||||
|
||||
function! s:settings()
|
||||
if filereadable(s:settings_path)
|
||||
let json_str = join(readfile(s:settings_path), '')
|
||||
return json_decode(json_str)
|
||||
else
|
||||
return {}
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#os()
|
||||
return s:os
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#lib(filename)
|
||||
return s:lib_subdir.s:separator.a:filename
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#kite_installed()
|
||||
return !empty(s:kite_install_path())
|
||||
endfunction
|
||||
|
||||
" Returns the kite installation path including the filename, or an empty string if not installed.
|
||||
function! s:kite_install_path()
|
||||
if kite#utils#windows()
|
||||
let output = kite#async#sync('reg query HKEY_LOCAL_MACHINE\Software\Kite\AppData /v InstallPath /s /reg:64')
|
||||
let lines = filter(split(output, '\n'), 'v:val =~ "InstallPath"')
|
||||
if empty(lines)
|
||||
return ''
|
||||
endif
|
||||
return substitute(lines[0], '\v^\s+InstallPath\s+REG_\w+\s+', '', '').s:separator.'kited.exe'
|
||||
elseif kite#utils#macos()
|
||||
return kite#async#sync('mdfind ''kMDItemCFBundleIdentifier = "com.kite.Kite" || kMDItemCFBundleIdentifier = "enterprise.kite.Kite"''')
|
||||
else
|
||||
let path = exepath('/opt/kite/kited')
|
||||
if !empty(path)
|
||||
return path
|
||||
endif
|
||||
let path = exepath(expand('~/.local/share/kite/kited'))
|
||||
if !empty(path)
|
||||
return path
|
||||
endif
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#kite_running()
|
||||
if kite#utils#windows()
|
||||
let [cmd, process] = ['tasklist /FI "IMAGENAME eq kited.exe"', '^kited.exe']
|
||||
elseif kite#utils#macos()
|
||||
let [cmd, process] = ['ps -axco command', '^Kite$']
|
||||
else
|
||||
let process_name = empty($KITED_TEST_PORT) ? 'kited' : 'kited-test'
|
||||
let [cmd, process] = ['ps -axco command', '^'.process_name.'$']
|
||||
endif
|
||||
|
||||
return match(split(kite#async#sync(cmd), '\n'), process) > -1
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#launch_kited()
|
||||
if kite#utils#kite_running()
|
||||
return
|
||||
endif
|
||||
|
||||
let path = s:kite_install_path()
|
||||
|
||||
if empty(path)
|
||||
return
|
||||
endif
|
||||
|
||||
if kite#utils#windows()
|
||||
let $KITE_SKIP_ONBOARDING = 1
|
||||
silent execute "!start" s:shellescape(path)
|
||||
elseif kite#utils#macos()
|
||||
call system('open -a '.path.' --args "--plugin-launch"')
|
||||
else
|
||||
silent execute '!'.path.' --plugin-launch >/dev/null 2>&1 &'
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" msg - a list or a string
|
||||
function! kite#utils#log(msg)
|
||||
if g:kite_log
|
||||
if type(a:msg) == v:t_string
|
||||
let msg = [a:msg]
|
||||
else
|
||||
let msg = a:msg
|
||||
endif
|
||||
call writefile(msg, 'kite-vim.log', 'a')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#warn(msg)
|
||||
echohl WarningMsg
|
||||
echom 'Kite: '.a:msg
|
||||
echohl None
|
||||
let v:warningmsg = a:msg
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#info(msg)
|
||||
echohl Question
|
||||
echom a:msg
|
||||
echohl None
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the absolute path to the current file after resolving symlinks.
|
||||
"
|
||||
" url_format - when truthy, return the path in a URL-compatible format.
|
||||
function! kite#utils#filepath(url_format)
|
||||
let path = resolve(expand('%:p'))
|
||||
|
||||
if a:url_format
|
||||
let path = substitute(path, '[\/]', ':', 'g')
|
||||
if kite#utils#windows()
|
||||
let path = substitute(path, '^\(\a\)::', '\1:', '')
|
||||
let path = ':windows:'.path
|
||||
endif
|
||||
let path = kite#utils#url_encode(path)
|
||||
endif
|
||||
|
||||
return path
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns a 2-element list of 0-based character indices into the buffer.
|
||||
"
|
||||
" When no text is selected, both elements are the cursor position.
|
||||
"
|
||||
" When text is selected, the elements are the start (inclusive) and
|
||||
" end (exclusive) of the selection.
|
||||
"
|
||||
" Returns [-1, -1] when not in normal, insert, or visual mode.
|
||||
function! kite#utils#selected_region_characters()
|
||||
return s:selected_region('c')
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns a 2-element list of 0-based byte indices into the buffer.
|
||||
"
|
||||
" When no text is selected, both elements are the cursor position.
|
||||
"
|
||||
" When text is selected, the elements are the start (inclusive) and
|
||||
" end (exclusive) of the selection.
|
||||
"
|
||||
" Returns [-1, -1] when not in normal, insert, or visual mode.
|
||||
function! kite#utils#selected_region_bytes()
|
||||
return s:selected_region('b')
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns a 2-element list of 0-based indices into the buffer.
|
||||
"
|
||||
" When no text is selected, both elements are the cursor position.
|
||||
"
|
||||
" When text is selected, the elements are the start (inclusive) and
|
||||
" end (exclusive) of the selection.
|
||||
"
|
||||
" Returns [-1, -1] when not in normal, insert, or visual mode.
|
||||
"
|
||||
" param type (String) - 'c' for character indices, 'b' for byte indices
|
||||
"
|
||||
" NOTE: the cursor is moved during the function (but finishes where it started).
|
||||
function! s:selected_region(type)
|
||||
if mode() ==# 'n' || mode() ==# 'i'
|
||||
if a:type == 'c'
|
||||
let offset = kite#utils#character_offset()
|
||||
else
|
||||
let offset = kite#utils#byte_offset_start()
|
||||
endif
|
||||
return [offset, offset]
|
||||
endif
|
||||
|
||||
if mode() ==? 'v'
|
||||
let pos_start = getpos('v')
|
||||
let pos_end = getpos('.')
|
||||
|
||||
if (pos_start[1] > pos_end[1]) || (pos_start[1] == pos_end[1] && pos_start[2] > pos_end[2])
|
||||
let [pos_start, pos_end] = [pos_end, pos_start]
|
||||
endif
|
||||
|
||||
" switch to normal mode
|
||||
execute "normal! \<Esc>"
|
||||
|
||||
call setpos('.', pos_start)
|
||||
if a:type == 'c'
|
||||
let offset1 = kite#utils#character_offset()
|
||||
else
|
||||
let offset1 = kite#utils#byte_offset_start()
|
||||
endif
|
||||
|
||||
call setpos('.', pos_end)
|
||||
" end position is exclusive
|
||||
if a:type == 'c'
|
||||
let offset2 = kite#utils#character_offset() + 1
|
||||
else
|
||||
let offset2 = kite#utils#byte_offset_end() + 1
|
||||
endif
|
||||
|
||||
" restore visual selection
|
||||
normal! gv
|
||||
|
||||
return [offset1, offset2]
|
||||
endif
|
||||
|
||||
return [-1, -1]
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the 0-based index of the cursor in the buffer.
|
||||
"
|
||||
" Returns -1 when the buffer is empty.
|
||||
function! kite#utils#cursor_characters()
|
||||
if mode() ==? 'v'
|
||||
" switch to normal mode
|
||||
execute "normal! \<Esc>"
|
||||
|
||||
let cursor = kite#utils#character_offset()
|
||||
|
||||
" restore visual selection
|
||||
normal! gv
|
||||
|
||||
return cursor
|
||||
endif
|
||||
|
||||
return kite#utils#character_offset()
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the 0-based index into the buffer of the cursor position.
|
||||
" Returns -1 when the buffer is empty.
|
||||
"
|
||||
" Does not work in visual mode.
|
||||
function! kite#utils#character_offset()
|
||||
" wordcount().cursor_chars is 1-based so we need to subtract 1.
|
||||
let offset = wordcount().cursor_chars - 1
|
||||
|
||||
" In insert mode the cursor isn't really between two characters;
|
||||
" it is actually on the second character, but that's what we want
|
||||
" anyway.
|
||||
|
||||
" If the cursor is just before (i.e. on) the end of the line, and
|
||||
" the file has dos line endings, wordcount().cursor_chars will
|
||||
" regard the cursor as on the second character of the \r\n. In this
|
||||
" case we want the offset of the first, i.e. the \r.
|
||||
|
||||
if col('.') == col('$') && &ff ==# 'dos'
|
||||
let offset -= 1
|
||||
endif
|
||||
|
||||
return offset
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the 0-based index into the buffer of the cursor position.
|
||||
" If the cursor is on a multibyte character, it reports the character's
|
||||
" first byte.
|
||||
function! kite#utils#byte_offset_start()
|
||||
let offset = line2byte(line('.')) - 1 + col('.') - 1
|
||||
if offset < 0
|
||||
let offset = 0
|
||||
endif
|
||||
return offset
|
||||
endfunction
|
||||
|
||||
" Returns the 0-based index into the buffer of the cursor position.
|
||||
" If the cursor is on a multibyte character, it reports the character's
|
||||
" last byte.
|
||||
function! kite#utils#byte_offset_end()
|
||||
let offset = wordcount().cursor_bytes - 1
|
||||
if offset < 0
|
||||
let offset = 0
|
||||
endif
|
||||
return offset
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#buffer_contents()
|
||||
let line_ending = {"unix": "\n", "dos": "\r\n", "mac": "\r"}[&fileformat]
|
||||
return join(getline(1, '$'), line_ending).(&eol ? line_ending : '')
|
||||
endfunction
|
||||
|
||||
|
||||
" Similar to the goto command, but for characters.
|
||||
" index is 1-based, the start of the file.
|
||||
function! kite#utils#goto_character(index)
|
||||
call search('\m\%^\_.\{'.a:index.'}', 'es')
|
||||
|
||||
" The search() function above counts a newline as 1 character even if it is
|
||||
" actually 2. Therefore we need to adjust the cursor position when newlines
|
||||
" are 2 characters.
|
||||
if &ff ==# 'dos'
|
||||
let [_whichwrap, &whichwrap] = [&whichwrap, "h,l"]
|
||||
let delta = wordcount().cursor_chars - a:index
|
||||
while delta != 0
|
||||
" Cannot land on a newline character.
|
||||
if (delta == -1 || delta == -2) && col('.') == col('$') - 1
|
||||
break
|
||||
endif
|
||||
execute "normal! ".delta.(delta > 0 ? 'h' : 'l')
|
||||
let delta = wordcount().cursor_chars - a:index
|
||||
endwhile
|
||||
let &whichwrap = _whichwrap
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns the MD5 hash of the buffer contents.
|
||||
function! kite#utils#buffer_md5()
|
||||
return s:MD5(kite#utils#buffer_contents())
|
||||
endfunction
|
||||
|
||||
|
||||
" https://github.com/tpope/vim-unimpaired/blob/3a7759075cca5b0dc29ce81f2747489b6c8e36a7/plugin/unimpaired.vim#L327-L329
|
||||
function! kite#utils#url_encode(str)
|
||||
return substitute(a:str,'[^A-Za-z0-9_.~-]','\="%".printf("%02X",char2nr(submatch(0)))','g')
|
||||
endfunction
|
||||
|
||||
|
||||
" Capitalises the first letter of str.
|
||||
function! kite#utils#capitalize(str)
|
||||
return substitute(a:str, '^.', '\u\0', '')
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#map_join(list, prop, sep)
|
||||
return join(map(copy(a:list), {_,v -> v[a:prop]}), a:sep)
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns a list of lines, each no longer than length.
|
||||
" The last line may be longer than length if it has no spaces.
|
||||
" Assumes str is a constructor or function call.
|
||||
"
|
||||
" Example: json.dumps
|
||||
"
|
||||
" dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, encoding, default, sort_keys, *args, **kwargs)
|
||||
"
|
||||
" - becomes when wrapped:
|
||||
"
|
||||
" dumps(obj, skipkeys, ensure_ascii, check_circular,
|
||||
" allow_nan, cls, indent, separators, encoding,
|
||||
" default, sort_keys, *args, **kwargs)
|
||||
"
|
||||
function! kite#utils#wrap(str, length, indent)
|
||||
let lines = []
|
||||
|
||||
let str = a:str
|
||||
let [prefix; str] = split(a:str, '(\zs')
|
||||
let str = join(str)
|
||||
|
||||
while v:true
|
||||
let line = prefix.str
|
||||
|
||||
if len(line) <= a:length
|
||||
call add(lines, line)
|
||||
break
|
||||
endif
|
||||
|
||||
let i = strridx(str[0:a:length-len(prefix)], ' ')
|
||||
|
||||
if i == -1
|
||||
call add(lines, line)
|
||||
break
|
||||
endif
|
||||
|
||||
let line = prefix . str[0:i-1]
|
||||
call add(lines, line)
|
||||
let str = str[i+1:]
|
||||
|
||||
let prefix = repeat(' ', a:indent)
|
||||
endwhile
|
||||
|
||||
return lines
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#coerce(dict, key, default)
|
||||
if has_key(a:dict, a:key)
|
||||
let v = a:dict[a:key]
|
||||
if type(v) == type(a:default) " check type in case of null
|
||||
return v
|
||||
endif
|
||||
endif
|
||||
return a:default
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#present(dict, key)
|
||||
return has_key(a:dict, a:key) && !empty(a:dict[a:key])
|
||||
endfunction
|
||||
|
||||
|
||||
" Returns a string of the given length.
|
||||
"
|
||||
" If length is 0 or negative, returns an empty string.
|
||||
"
|
||||
" If text is less than length, it is padded with leading spaces so that it is
|
||||
" right-aligned.
|
||||
"
|
||||
" If text is greater than length, it is truncated with an ellipsis.
|
||||
" If there isn't room for an ellipsis, or room for only an ellipsis, empty spaces are used.
|
||||
function! kite#utils#ralign(text, length)
|
||||
if a:length <= 0
|
||||
return ''
|
||||
endif
|
||||
|
||||
let text_width = strdisplaywidth(a:text)
|
||||
|
||||
" The required length
|
||||
if text_width == a:length
|
||||
return a:text
|
||||
endif
|
||||
|
||||
" Less than the required length: left-pad
|
||||
if text_width < a:length
|
||||
return repeat(' ', a:length-text_width) . a:text
|
||||
endif
|
||||
|
||||
" Greater than the required length: truncate
|
||||
|
||||
if kite#utils#windows()
|
||||
let ellipsis = '...'
|
||||
else
|
||||
let ellipsis = '…'
|
||||
endif
|
||||
let ellipsis_width = strdisplaywidth(ellipsis)
|
||||
|
||||
if ellipsis_width >= a:length
|
||||
return repeat(' ', a:length)
|
||||
endif
|
||||
|
||||
return a:text[: a:length-ellipsis_width-1] . ellipsis
|
||||
endfunction
|
||||
|
||||
|
||||
function! kite#utils#truncate(text, length)
|
||||
let text_width = strdisplaywidth(a:text)
|
||||
|
||||
if text_width <= a:length
|
||||
return a:text
|
||||
endif
|
||||
|
||||
if kite#utils#windows()
|
||||
let ellipsis = '...'
|
||||
else
|
||||
let ellipsis = '…'
|
||||
endif
|
||||
let ellipsis_width = strdisplaywidth(ellipsis)
|
||||
|
||||
if ellipsis_width >= a:length
|
||||
return a:text[0] . ellipsis[0: a:length-2]
|
||||
endif
|
||||
|
||||
return a:text[: a:length-ellipsis_width-1] . ellipsis
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:chomp(str)
|
||||
return substitute(a:str, '\n$', '', '')
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:md5(text)
|
||||
return s:chomp(system('md5', a:text))
|
||||
endfunction
|
||||
|
||||
function! s:md5sum(text)
|
||||
return split(system('md5sum', a:text), ' ')[0]
|
||||
endfunction
|
||||
|
||||
function! s:md5bin(text)
|
||||
return s:chomp(system(s:md5_binary, a:text))
|
||||
endfunction
|
||||
|
||||
|
||||
if executable('md5')
|
||||
let s:MD5 = function('s:md5')
|
||||
elseif executable('md5sum')
|
||||
let s:MD5 = function('s:md5sum')
|
||||
else
|
||||
let s:md5_binary = kite#utils#lib('md5Sum.exe')
|
||||
let s:MD5 = function('s:md5bin')
|
||||
endif
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue