1
0
mirror of https://github.com/amix/vimrc synced 2025-07-27 15:04:59 +08:00

Add support for Coc.nvim.

This commit is contained in:
Kurtis Moxley
2022-05-19 01:29:28 +08:00
parent 4ef73a3898
commit 9e29fd54b4
37 changed files with 16228 additions and 0 deletions

View File

@ -0,0 +1,241 @@
scriptencoding utf-8
let g:coc#_context = {'start': 0, 'preselect': -1,'candidates': []}
let g:coc_user_config = get(g:, 'coc_user_config', {})
let g:coc_global_extensions = get(g:, 'coc_global_extensions', [])
let g:coc_selected_text = ''
let g:coc_vim_commands = []
let s:watched_keys = []
let s:is_vim = !has('nvim')
let s:error_sign = get(g:, 'coc_status_error_sign', has('mac') ? '❌ ' : 'E')
let s:warning_sign = get(g:, 'coc_status_warning_sign', has('mac') ? '⚠️ ' : 'W')
let s:select_api = exists('*nvim_select_popupmenu_item')
let s:complete_info_api = exists('*complete_info')
let s:callbacks = {}
let s:hide_pum = has('nvim-0.6.1') || has('patch-8.2.3389')
function! coc#expandable() abort
return coc#rpc#request('snippetCheck', [1, 0])
endfunction
function! coc#jumpable() abort
return coc#rpc#request('snippetCheck', [0, 1])
endfunction
function! coc#expandableOrJumpable() abort
return coc#rpc#request('snippetCheck', [1, 1])
endfunction
" add vim command to CocCommand list
function! coc#add_command(id, cmd, ...)
let config = {'id':a:id, 'cmd':a:cmd, 'title': get(a:,1,'')}
call add(g:coc_vim_commands, config)
if !coc#rpc#ready() | return | endif
call coc#rpc#notify('addCommand', [config])
endfunction
function! coc#refresh() abort
return "\<c-r>=coc#start()\<CR>"
endfunction
function! coc#on_enter()
call coc#rpc#notify('CocAutocmd', ['Enter', bufnr('%')])
return ''
endfunction
function! coc#_insert_key(method, key, ...) abort
let prefix = ''
if get(a:, 1, 1)
if pumvisible()
let g:coc_hide_pum = 1
if s:hide_pum
let prefix = "\<C-x>\<C-z>"
else
let g:coc_disable_space_report = 1
let prefix = "\<space>\<bs>"
endif
endif
endif
return prefix."\<c-r>=coc#rpc#".a:method."('doKeymap', ['".a:key."'])\<CR>"
endfunction
function! coc#_complete() abort
let items = get(g:coc#_context, 'candidates', [])
let preselect = get(g:coc#_context, 'preselect', -1)
let startcol = g:coc#_context.start + 1
if s:select_api && len(items) && preselect != -1
noa call complete(startcol, items)
call nvim_select_popupmenu_item(preselect, v:false, v:false, {})
" use <cmd> specific key to preselect item at once
call feedkeys("\<Cmd>\<CR>" , 'i')
else
if pumvisible()
let g:coc_disable_complete_done = 1
endif
call complete(startcol, items)
endif
return ''
endfunction
function! coc#_do_complete(start, items, preselect, changedtick)
if b:changedtick != a:changedtick
return
endif
let g:coc#_context = {
\ 'start': a:start,
\ 'candidates': a:items,
\ 'preselect': a:preselect
\}
if mode() =~# 'i'
if s:is_vim
" when the completeopt has longest, the input would be removed sometimes when not use feedkeys!
call feedkeys("\<Plug>CocRefresh", 'i')
else
call coc#_complete()
endif
endif
endfunction
function! coc#_select_confirm() abort
if !exists('*complete_info')
throw 'coc#_select_confirm requires complete_info function to work'
endif
let selected = complete_info()['selected']
if selected != -1
return "\<C-y>"
elseif pumvisible()
return "\<down>\<C-y>"
endif
return ''
endfunction
function! coc#_selected()
if !pumvisible() | return 0 | endif
return coc#rpc#request('hasSelected', [])
endfunction
" Deprecated
function! coc#_hide() abort
if pumvisible()
call feedkeys("\<C-e>", 'in')
endif
endfunction
function! coc#_cancel(...)
" hack for close pum
" Use of <C-e> could cause bad insert when cursor just moved.
let g:coc#_context = {'start': 0, 'preselect': -1,'candidates': []}
if pumvisible()
let g:coc_hide_pum = 1
if get(a:, 1, 0)
" Avoid delayed CompleteDone cancel new completion
let g:coc_disable_complete_done = 1
endif
if s:hide_pum
call feedkeys("\<C-x>\<C-z>", 'in')
else
let g:coc_disable_space_report = 1
call feedkeys("\<space>\<bs>", 'in')
endif
endif
for winid in coc#float#get_float_win_list()
if getwinvar(winid, 'kind', '') ==# 'pum'
call coc#float#close(winid)
endif
endfor
let opt = get(a:, 2, '')
if !empty(opt)
execute 'noa set completeopt='.opt
endif
endfunction
function! coc#_select() abort
if !pumvisible() | return | endif
call feedkeys("\<C-y>", 'in')
endfunction
function! coc#start(...)
let opt = coc#util#get_complete_option()
call CocActionAsync('startCompletion', extend(opt, get(a:, 1, {})))
return ''
endfunction
" used for statusline
function! coc#status()
let info = get(b:, 'coc_diagnostic_info', {})
let msgs = []
if !empty(info) && get(info, 'error', 0)
call add(msgs, s:error_sign . info['error'])
endif
if !empty(info) && get(info, 'warning', 0)
call add(msgs, s:warning_sign . info['warning'])
endif
return coc#compat#trim(join(msgs, ' ') . ' ' . get(g:, 'coc_status', ''))
endfunction
function! coc#config(section, value)
let g:coc_user_config[a:section] = a:value
call coc#rpc#notify('updateConfig', [a:section, a:value])
endfunction
function! coc#add_extension(...)
if a:0 == 0 | return | endif
call extend(g:coc_global_extensions, a:000)
endfunction
function! coc#_watch(key)
if s:is_vim | return | endif
if index(s:watched_keys, a:key) == -1
call add(s:watched_keys, a:key)
call dictwatcheradd(g:, a:key, function('s:GlobalChange'))
endif
endfunction
function! coc#_unwatch(key)
if s:is_vim | return | endif
let idx = index(s:watched_keys, a:key)
if idx != -1
call remove(s:watched_keys, idx)
call dictwatcherdel(g:, a:key, function('s:GlobalChange'))
endif
endfunction
function! s:GlobalChange(dict, key, val)
call coc#rpc#notify('GlobalChange', [a:key, get(a:val, 'old', v:null), get(a:val, 'new', v:null)])
endfunction
function! coc#on_notify(id, method, Cb)
let key = a:id. '-'.a:method
let s:callbacks[key] = a:Cb
call coc#rpc#notify('registNotification', [a:id, a:method])
endfunction
function! coc#do_notify(id, method, result)
let key = a:id. '-'.a:method
let Fn = s:callbacks[key]
if !empty(Fn)
call Fn(a:result)
endif
endfunction
function! coc#complete_indent() abort
let curpos = getcurpos()
let indent_len = len(matchstr(getline('.'), '^\s*'))
let startofline = &startofline
let virtualedit = &virtualedit
set nostartofline
set virtualedit=all
normal! ==
let &startofline = startofline
let &virtualedit = virtualedit
let shift = len(matchstr(getline('.'), '^\s*')) - indent_len
let curpos[2] += shift
let curpos[4] += shift
call cursor(curpos[1:])
if shift != 0
if s:is_vim
doautocmd TextChangedP
endif
return 1
endif
return 0
endfunction

View File

@ -0,0 +1,666 @@
" ============================================================================
" Description: Client api used by vim8
" Author: Qiming Zhao <chemzqm@gmail.com>
" Licence: Anti 996 licence
" Last Modified: Mar 08, 2022
" ============================================================================
if has('nvim') | finish | endif
scriptencoding utf-8
let s:funcs = {}
let s:prop_offset = get(g:, 'coc_text_prop_offset', 1000)
let s:namespace_id = 1
let s:namespace_cache = {}
" helper {{
function! s:buf_line_count(bufnr) abort
if bufnr('%') == a:bufnr
return line('$')
endif
if exists('*getbufinfo')
let info = getbufinfo(a:bufnr)
if empty(info)
return 0
endif
" vim 8.1 has getbufinfo but no linecount
if has_key(info[0], 'linecount')
return info[0]['linecount']
endif
endif
if exists('*getbufline')
let lines = getbufline(a:bufnr, 1, '$')
return len(lines)
endif
let curr = bufnr('%')
execute 'noa buffer '.a:bufnr
let n = line('$')
execute 'noa buffer '.curr
return n
endfunction
function! s:execute(cmd)
if a:cmd =~# '^echo'
execute a:cmd
else
silent! execute a:cmd
endif
endfunction
" }}"
" nvim client methods {{
function! s:funcs.set_current_dir(dir) abort
execute 'cd '.a:dir
endfunction
function! s:funcs.set_var(name, value) abort
execute 'let g:'.a:name.'= a:value'
endfunction
function! s:funcs.del_var(name) abort
execute 'unlet g:'.a:name
endfunction
function! s:funcs.set_option(name, value) abort
execute 'let &'.a:name.' = a:value'
endfunction
function! s:funcs.set_current_buf(bufnr) abort
if !bufexists(a:bufnr) | return | endif
execute 'buffer '.a:bufnr
endfunction
function! s:funcs.set_current_win(win_id) abort
let [tabnr, winnr] = win_id2tabwin(a:win_id)
if tabnr == 0 | return | endif
execute 'normal! '.tabnr.'gt'
execute winnr.' wincmd w'
endfunction
function! s:funcs.set_current_tabpage(tabnr) abort
execute 'normal! '.a:tabnr.'gt'
endfunction
function! s:funcs.list_wins() abort
return map(getwininfo(), 'v:val["winid"]')
endfunction
function s:inspect_type(v) abort
let types = ['Number', 'String', 'Funcref', 'List', 'Dictionary', 'Float', 'Boolean', 'Null']
return get(types, type(a:v), 'Unknown')
endfunction
function! s:funcs.call_atomic(calls)
let res = []
for i in range(len(a:calls))
let [key, arglist] = a:calls[i]
let name = key[5:]
try
call add(res, call(s:funcs[name], arglist))
catch /.*/
return [res, [i, "VimException(".s:inspect_type(v:exception).")", v:exception]]
endtry
endfor
return [res, v:null]
endfunction
function! s:funcs.set_client_info(...) abort
endfunction
function! s:funcs.subscribe(...) abort
endfunction
function! s:funcs.unsubscribe(...) abort
endfunction
function! s:funcs.call_function(method, args) abort
return call(a:method, a:args)
endfunction
function! s:funcs.call_dict_function(dict, method, args) abort
return call(a:method, a:args, a:dict)
endfunction
function! s:funcs.command(command) abort
" command that could cause cursor vanish
if a:command =~# '^echo' || a:command =~# '^redraw' || a:command =~# '^sign place'
call timer_start(0, {-> s:execute(a:command)})
else
execute a:command
let err = get(g:, 'errmsg', '')
" get error from python script run.
if !empty(err)
unlet g:errmsg
throw err
endif
endif
endfunction
function! s:funcs.eval(expr) abort
return eval(a:expr)
endfunction
function! s:funcs.get_api_info()
let names = coc#api#func_names()
return [1, {'functions': map(names, '{"name": "nvim_".v:val}')}]
endfunction
function! s:funcs.list_bufs()
return map(getbufinfo({'bufloaded': 1}), 'v:val["bufnr"]')
endfunction
function! s:funcs.feedkeys(keys, mode, escape_csi)
call feedkeys(a:keys, a:mode)
endfunction
function! s:funcs.list_runtime_paths()
return globpath(&runtimepath, '', 0, 1)
endfunction
function! s:funcs.command_output(cmd)
return execute(a:cmd)
endfunction
function! s:funcs.get_current_line()
return getline('.')
endfunction
function! s:funcs.set_current_line(line)
call setline('.', a:line)
endfunction
function! s:funcs.del_current_line(line)
execute 'normal! dd'
endfunction
function! s:funcs.get_var(var)
return get(g:, a:var, v:null)
endfunction
function! s:funcs.get_vvar(var)
return get(v:, a:var, v:null)
endfunction
function! s:funcs.get_option(name)
return eval('&'.a:name)
endfunction
function! s:funcs.get_current_buf()
return bufnr('%')
endfunction
function! s:funcs.get_current_win()
return win_getid()
endfunction
function! s:funcs.get_current_tabpage()
return tabpagenr()
endfunction
function! s:funcs.list_tabpages()
return range(1, tabpagenr('$'))
endfunction
function! s:funcs.get_mode()
return {'blocking': v:false, 'mode': mode()}
endfunction
function! s:funcs.strwidth(str)
return strwidth(a:str)
endfunction
function! s:funcs.out_write(str)
echon a:str
call timer_start(0, {-> s:execute('redraw')})
endfunction
function! s:funcs.err_write(str)
"echoerr a:str
endfunction
function! s:funcs.err_writeln(str)
echohl ErrorMsg
echom a:str
echohl None
call timer_start(0, {-> s:execute('redraw')})
endfunction
function! s:funcs.create_namespace(name) abort
if empty(a:name)
let id = s:namespace_id
let s:namespace_id = s:namespace_id + 1
return id
endif
let id = get(s:namespace_cache, a:name, 0)
if !id
let id = s:namespace_id
let s:namespace_id = s:namespace_id + 1
let s:namespace_cache[a:name] = id
endif
return id
endfunction
" }}
" buffer methods {{
function! s:funcs.buf_set_option(bufnr, name, val)
let val = a:val
if val is v:true
let val = 1
elseif val is v:false
let val = 0
endif
return setbufvar(a:bufnr, '&'.a:name, val)
endfunction
function! s:funcs.buf_get_changedtick(bufnr)
return getbufvar(a:bufnr, 'changedtick')
endfunction
function! s:funcs.buf_is_valid(bufnr)
return bufloaded(a:bufnr) ? v:true : v:false
endfunction
function! s:funcs.buf_get_mark(bufnr, name)
let nr = bufnr('%')
if a:bufnr != 0 || a:bufnr != nr
throw 'buf_get_mark support current buffer only'
endif
return [line("'" . a:name), col("'" . a:name)]
endfunction
function! s:funcs.buf_add_highlight(bufnr, srcId, hlGroup, line, colStart, colEnd, ...) abort
if !has('patch-8.1.1719')
return
endif
let bufnr = a:bufnr == 0 ? bufnr('%') : a:bufnr
let type = 'CocHighlight'.a:hlGroup
if empty(prop_type_get(type))
let opts = get(a:, 1, 0)
let priority = get(opts, 'priority', 0)
call prop_type_add(type, {
\ 'highlight': a:hlGroup,
\ 'priority': type(priority) == 0 ? priority : 0,
\ 'combine': get(opts, 'combine', 1),
\ 'start_incl': get(opts, 'start_incl', 0),
\ 'end_incl': get(opts, 'end_incl', 0),
\ })
endif
let total = strlen(getbufline(bufnr, a:line + 1)[0])
let end = a:colEnd
if end == -1
let end = total
else
let end = min([end, total])
endif
if end <= a:colStart
return
endif
let srcId = a:srcId
if srcId == 0
while v:true
let srcId = srcId + 1
if empty(prop_find({'id': s:prop_offset + srcId, 'lnum' : 1}))
break
endif
endwhile
" generate srcId
endif
let id = srcId == -1 ? 0 : s:prop_offset + srcId
try
call prop_add(a:line + 1, a:colStart + 1, {'length': end - a:colStart, 'bufnr': bufnr, 'type': type, 'id': id})
catch /^Vim\%((\a\+)\)\=:E967/
" ignore 967
endtry
if a:srcId == 0
" return generated srcId
return srcId
endif
endfunction
function! s:funcs.buf_clear_namespace(bufnr, srcId, startLine, endLine) abort
if !has('patch-8.1.1719')
return
endif
let bufnr = a:bufnr == 0 ? bufnr('%') : a:bufnr
let start = a:startLine + 1
let end = a:endLine == -1 ? len(getbufline(bufnr, 1, '$')) : a:endLine
if a:srcId == -1
call prop_clear(start, end, {'bufnr' : bufnr})
else
try
call prop_remove({'bufnr': bufnr, 'all': 1, 'id': s:prop_offset + a:srcId}, start, end)
catch /^Vim\%((\a\+)\)\=:E968/
" ignore 968
endtry
endif
endfunction
function! s:funcs.buf_line_count(bufnr) abort
return s:buf_line_count(a:bufnr)
endfunction
function! s:funcs.buf_attach(...)
" not supported
return 1
endfunction
function! s:funcs.buf_detach()
" not supported
return 1
endfunction
function! s:funcs.buf_get_lines(bufnr, start, end, strict) abort
let lines = getbufline(a:bufnr, 1, '$')
let start = a:start < 0 ? a:start + 1 : a:start
let end = a:end < 0 ? a:end + 1 : a:end
if a:strict && end > len(lines)
throw 'line number out of range: '. end
endif
return lines[start : end - 1]
endfunction
function! s:funcs.buf_set_lines(bufnr, start, end, strict, ...) abort
if !bufloaded(a:bufnr)
return
endif
let replacement = get(a:, 1, [])
let lineCount = s:buf_line_count(a:bufnr)
let startLnum = a:start >= 0 ? a:start + 1 : lineCount + a:start + 2
let end = a:end >= 0 ? a:end : lineCount + a:end + 1
if end == lineCount + 1
let end = lineCount
endif
let delCount = end - (startLnum - 1)
let changeBuffer = 0
let curr = bufnr('%')
if a:bufnr != curr && !exists('*setbufline')
let changeBuffer = 1
exe 'buffer '.a:bufnr
endif
if a:bufnr == curr || changeBuffer
" replace
let storeView = winsaveview()
if delCount == len(replacement)
call setline(startLnum, replacement)
else
if len(replacement)
call append(startLnum - 1, replacement)
endif
if delCount
let start = startLnum + len(replacement)
let saved_reg = @"
let system_reg = @*
if exists('*deletebufline')
silent call deletebufline(curr, start, start + delCount - 1)
else
silent execute start . ','.(start + delCount - 1).'d'
endif
let @" = saved_reg
let @* = system_reg
endif
endif
call winrestview(storeView)
if changeBuffer
exe 'buffer '.curr
endif
elseif exists('*setbufline')
" replace
if delCount == len(replacement)
" 8.0.1039
call setbufline(a:bufnr, startLnum, replacement)
else
if len(replacement)
" 8.10037
call appendbufline(a:bufnr, startLnum - 1, replacement)
endif
if delCount
let start = startLnum + len(replacement)
let saved_reg = @"
let system_reg = @*
"8.1.0039
silent call deletebufline(a:bufnr, start, start + delCount - 1)
let @" = saved_reg
let @* = system_reg
endif
endif
endif
endfunction
function! s:funcs.buf_set_name(bufnr, name) abort
let nr = bufnr('%')
if a:bufnr != nr
throw 'buf_set_name support current buffer only'
else
execute '0f'
execute 'file '.fnameescape(a:name)
endif
endfunction
function! s:funcs.buf_get_var(bufnr, name)
return getbufvar(a:bufnr, a:name)
endfunction
function! s:funcs.buf_set_var(bufnr, name, val)
if !bufloaded(a:bufnr) | return | endif
call setbufvar(a:bufnr, a:name, a:val)
endfunction
function! s:funcs.buf_del_var(bufnr, name)
if bufnr == bufnr('%')
execute 'unlet! b:'.a:name
elseif exists('*win_execute')
let winid = coc#compat#buf_win_id(a:bufnr)
if winid != -1
call win_execute(winid, 'unlet! b:'.a:name)
endif
endif
endfunction
function! s:funcs.buf_get_option(bufnr, name)
return getbufvar(a:bufnr, '&'.a:name)
endfunction
function! s:funcs.buf_get_name(bufnr)
return bufname(a:bufnr)
endfunction
" }}
" window methods {{
function! s:funcs.win_get_buf(winid)
return winbufnr(a:winid)
endfunction
function! s:funcs.win_get_position(win_id) abort
let [row, col] = win_screenpos(a:win_id)
if row == 0 && col == 0
throw 'Invalid window '.a:win_id
endif
return [row - 1, col - 1]
endfunction
function! s:funcs.win_get_height(win_id) abort
return winheight(a:win_id)
endfunction
function! s:funcs.win_get_width(win_id) abort
return winwidth(a:win_id)
endfunction
if exists('*win_execute')
function! s:win_execute(win_id, cmd, ...) abort
let ref = get(a:000, 0, v:null)
let cmd = ref is v:null ? a:cmd : 'let ref["out"] = ' . a:cmd
call win_execute(a:win_id, cmd)
endfunction
else
function! s:win_execute(win_id, cmd, ...) abort
let ref = get(a:000, 0, v:null)
let cmd = ref is v:null ? a:cmd : 'let ref["out"] = ' . a:cmd
let winid = win_getid()
if winid == a:win_id
execute cmd
else
let goto_status = win_gotoid(a:win_id)
if !goto_status
return
endif
execute cmd
call win_gotoid(winid)
endif
endfunction
endif
function! s:get_tabnr(winid) abort
let ref = {}
call s:win_execute(a:winid, 'tabpagenr()', ref)
return get(ref, 'out', 0)
endfunction
function! s:funcs.win_get_cursor(win_id) abort
let ref = {}
call s:win_execute(a:win_id, "[line('.'), col('.')-1]", ref)
return get(ref, 'out', 0)
endfunction
function! s:funcs.win_get_var(win_id, name, ...) abort
let tabnr = s:get_tabnr(a:win_id)
if tabnr
return gettabwinvar(tabnr, a:win_id, a:name, get(a:, 1, v:null))
endif
throw 'window '.a:win_id. ' not a visible window'
endfunction
function! s:funcs.win_set_width(win_id, width) abort
call s:win_execute(a:win_id, 'vertical resize '.a:width)
endfunction
function! s:funcs.win_set_buf(win_id, buf_id) abort
call s:win_execute(a:win_id, 'buffer '.a:buf_id)
endfunction
function! s:funcs.win_get_option(win_id, name) abort
let tabnr = s:get_tabnr(a:win_id)
if tabnr
return gettabwinvar(tabnr, a:win_id, '&'.a:name)
endif
throw 'window '.a:win_id. ' not a valid window'
endfunction
function! s:funcs.win_set_height(win_id, height) abort
return s:win_execute(a:win_id, 'resize '.a:height)
endfunction
function! s:funcs.win_set_option(win_id, name, value) abort
let val = a:value
if val is v:true
let val = 1
elseif val is v:false
let val = 0
endif
let tabnr = s:get_tabnr(a:win_id)
if tabnr
call settabwinvar(tabnr, a:win_id, '&'.a:name, val)
else
throw 'window '.a:win_id. ' not a valid window'
endif
endfunction
function! s:funcs.win_set_var(win_id, name, value) abort
let tabnr = s:get_tabnr(a:win_id)
if tabnr
call settabwinvar(tabnr, a:win_id, a:name, a:value)
else
throw "Invalid window id ".a:win_id
endif
endfunction
function! s:funcs.win_del_var(win_id, name) abort
call s:win_execute(a:win_id, 'unlet! w:'.a:name)
endfunction
function! s:funcs.win_is_valid(win_id) abort
let info = getwininfo(a:win_id)
return empty(info) ? v:false : v:true
endfunction
function! s:funcs.win_get_number(win_id) abort
let info = getwininfo(a:win_id)
if empty(info)
throw 'Invalid window id '.a:win_id
endif
return info[0]['winnr']
endfunction
function! s:funcs.win_set_cursor(win_id, pos) abort
let [line, col] = a:pos
call s:win_execute(a:win_id, 'call cursor('.line.','.(col + 1).')')
endfunction
function! s:funcs.win_close(win_id, ...) abort
let force = get(a:, 1, 0)
call s:win_execute(a:win_id, 'close'.(force ? '!' : ''))
endfunction
function! s:funcs.win_get_tabpage(win_id) abort
let tabnr = s:get_tabnr(a:win_id)
if !tabnr
throw 'Invalid window id '.a:win_id
endif
return tabnr
endfunction
" }}
" tabpage methods {{
function! s:funcs.tabpage_get_number(id)
return a:id
endfunction
function! s:funcs.tabpage_list_wins(tabnr)
let info = getwininfo()
return map(filter(info, 'v:val["tabnr"] == a:tabnr'), 'v:val["winid"]')
endfunction
function! s:funcs.tabpage_get_var(tabnr, name)
return gettabvar(a:tabnr, a:name, v:null)
endfunction
function! s:funcs.tabpage_set_var(tabnr, name, value)
call settabvar(a:tabnr, a:name, a:value)
endfunction
function! s:funcs.tabpage_del_var(tabnr, name)
call settabvar(a:tabnr, a:name, v:null)
endfunction
function! s:funcs.tabpage_is_valid(tabnr)
let max = tabpagenr('$')
return a:tabnr <= max
endfunction
function! s:funcs.tabpage_get_win(tabnr)
let wnr = tabpagewinnr(a:tabnr)
return win_getid(wnr, a:tabnr)
endfunction
" }}
function! coc#api#func_names() abort
return keys(s:funcs)
endfunction
function! coc#api#call(method, args) abort
let err = v:null
let res = v:null
try
let res = call(s:funcs[a:method], a:args)
catch /.*/
let err = v:exception
endtry
return [err, res]
endfunction
function! coc#api#exec(method, args) abort
return call(s:funcs[a:method], a:args)
endfunction
function! coc#api#notify(method, args) abort
call call(s:funcs[a:method], a:args)
endfunction
" vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{,}} foldmethod=marker foldlevel=0:

View File

@ -0,0 +1,336 @@
scriptencoding utf-8
let s:root = expand('<sfile>:h:h:h')
let s:is_vim = !has('nvim')
let s:is_win = has("win32") || has("win64")
let s:clients = {}
if get(g:, 'node_client_debug', 0)
echohl WarningMsg | echon '[coc.nvim] Enable g:node_client_debug could impact your vim experience' | echohl None
let $NODE_CLIENT_LOG_LEVEL = 'debug'
if exists('$NODE_CLIENT_LOG_FILE')
let s:logfile = resolve($NODE_CLIENT_LOG_FILE)
else
let s:logfile = tempname()
let $NODE_CLIENT_LOG_FILE = s:logfile
endif
endif
" create a client
function! coc#client#create(name, command)
let client = {}
let client['command'] = a:command
let client['name'] = a:name
let client['running'] = 0
let client['async_req_id'] = 1
let client['async_callbacks'] = {}
" vim only
let client['channel'] = v:null
" neovim only
let client['chan_id'] = 0
let client['start'] = function('s:start', [], client)
let client['request'] = function('s:request', [], client)
let client['notify'] = function('s:notify', [], client)
let client['request_async'] = function('s:request_async', [], client)
let client['on_async_response'] = function('s:on_async_response', [], client)
let s:clients[a:name] = client
return client
endfunction
function! s:start() dict
if self.running | return | endif
if !isdirectory(getcwd())
echohl Error | echon '[coc.nvim] Current cwd is not a valid directory.' | echohl None
return
endif
let timeout = string(get(g:, 'coc_channel_timeout', 30))
let disable_warning = string(get(g:, 'coc_disable_startup_warning', 0))
let tmpdir = fnamemodify(tempname(), ':p:h')
if s:is_vim
let options = {
\ 'in_mode': 'json',
\ 'out_mode': 'json',
\ 'err_mode': 'nl',
\ 'err_cb': {channel, message -> s:on_stderr(self.name, split(message, "\n"))},
\ 'exit_cb': {channel, code -> s:on_exit(self.name, code)},
\ 'env': {
\ 'NODE_NO_WARNINGS': '1',
\ 'VIM_NODE_RPC': '1',
\ 'COC_NVIM': '1',
\ 'COC_CHANNEL_TIMEOUT': timeout,
\ 'TMPDIR': tmpdir,
\ }
\}
if has("patch-8.1.350")
let options['noblock'] = 1
endif
let job = job_start(self.command, options)
let status = job_status(job)
if status !=# 'run'
let self.running = 0
echohl Error | echom 'Failed to start '.self.name.' service' | echohl None
return
endif
let self['running'] = 1
let self['channel'] = job_getchannel(job)
else
let original = {}
let opts = {
\ 'rpc': 1,
\ 'on_stderr': {channel, msgs -> s:on_stderr(self.name, msgs)},
\ 'on_exit': {channel, code -> s:on_exit(self.name, code)},
\ }
if has('nvim-0.5.0')
" could use env option
let opts['env'] = {
\ 'COC_NVIM': '1',
\ 'NODE_NO_WARNINGS': '1',
\ 'COC_CHANNEL_TIMEOUT': timeout,
\ 'TMPDIR': tmpdir
\ }
else
if exists('*getenv')
let original = {
\ 'NODE_NO_WARNINGS': getenv('NODE_NO_WARNINGS'),
\ 'TMPDIR': getenv('TMPDIR'),
\ }
endif
if exists('*setenv')
call setenv('COC_NVIM', '1')
call setenv('NODE_NO_WARNINGS', '1')
call setenv('COC_CHANNEL_TIMEOUT', timeout)
call setenv('TMPDIR', tmpdir)
else
let $NODE_NO_WARNINGS = 1
let $TMPDIR = tmpdir
endif
endif
let chan_id = jobstart(self.command, opts)
if !empty(original)
if exists('*setenv')
for key in keys(original)
call setenv(key, original[key])
endfor
else
let $TMPDIR = original['TMPDIR']
endif
endif
if chan_id <= 0
echohl Error | echom 'Failed to start '.self.name.' service' | echohl None
return
endif
let self['chan_id'] = chan_id
let self['running'] = 1
endif
endfunction
function! s:on_stderr(name, msgs)
if get(g:, 'coc_vim_leaving', 0) | return | endif
if get(g:, 'coc_disable_uncaught_error', 0) | return | endif
let data = filter(copy(a:msgs), '!empty(v:val)')
if empty(data) | return | endif
let client = a:name ==# 'coc' ? '[coc.nvim]' : '['.a:name.']'
let data[0] = client.': '.data[0]
call coc#ui#echo_messages('Error', data)
endfunction
function! s:on_exit(name, code) abort
if get(g:, 'coc_vim_leaving', 0) | return | endif
let client = get(s:clients, a:name, v:null)
if empty(client) | return | endif
if client['running'] != 1 | return | endif
let client['running'] = 0
let client['chan_id'] = 0
let client['channel'] = v:null
let client['async_req_id'] = 1
if a:code != 0 && a:code != 143
echohl Error | echom 'client '.a:name. ' abnormal exit with: '.a:code | echohl None
endif
endfunction
function! coc#client#get_client(name) abort
return get(s:clients, a:name, v:null)
endfunction
function! coc#client#get_channel(client)
if s:is_vim
return a:client['channel']
endif
return a:client['chan_id']
endfunction
function! s:request(method, args) dict
let channel = coc#client#get_channel(self)
if empty(channel) | return '' | endif
try
if s:is_vim
let res = ch_evalexpr(channel, [a:method, a:args], {'timeout': 60 * 1000})
if type(res) == 1 && res ==# ''
throw 'request '.a:method. ' '.string(a:args).' timeout after 60s'
endif
let [l:errmsg, res] = res
if !empty(l:errmsg)
throw l:errmsg
else
return res
endif
else
return call('rpcrequest', [channel, a:method] + a:args)
endif
catch /.*/
if v:exception =~# 'E475'
if get(g:, 'coc_vim_leaving', 0) | return | endif
echohl Error | echom '['.self.name.'] server connection lost' | echohl None
let name = self.name
call s:on_exit(name, 0)
execute 'silent do User ConnectionLost'.toupper(name[0]).name[1:]
elseif v:exception =~# 'E12'
" neovim's bug, ignore it
else
echohl Error | echo 'Error on request ('.a:method.'): '.v:exception | echohl None
endif
endtry
endfunction
function! s:notify(method, args) dict
let channel = coc#client#get_channel(self)
if empty(channel)
return ''
endif
try
if s:is_vim
call ch_sendraw(channel, json_encode([0, [a:method, a:args]])."\n")
else
call call('rpcnotify', [channel, a:method] + a:args)
endif
catch /.*/
if v:exception =~# 'E475'
if get(g:, 'coc_vim_leaving', 0)
return
endif
echohl Error | echom '['.self.name.'] server connection lost' | echohl None
let name = self.name
call s:on_exit(name, 0)
execute 'silent do User ConnectionLost'.toupper(name[0]).name[1:]
elseif v:exception =~# 'E12'
" neovim's bug, ignore it
else
echohl Error | echo 'Error on notify ('.a:method.'): '.v:exception | echohl None
endif
endtry
endfunction
function! s:request_async(method, args, cb) dict
let channel = coc#client#get_channel(self)
if empty(channel) | return '' | endif
if type(a:cb) != 2
echohl Error | echom '['.self['name'].'] Callback should be function' | echohl None
return
endif
let id = self.async_req_id
let self.async_req_id = id + 1
let self.async_callbacks[id] = a:cb
call self['notify']('nvim_async_request_event', [id, a:method, a:args])
endfunction
function! s:on_async_response(id, resp, isErr) dict
let Callback = get(self.async_callbacks, a:id, v:null)
if empty(Callback)
" should not happen
echohl Error | echom 'callback not found' | echohl None
return
endif
call remove(self.async_callbacks, a:id)
if a:isErr
call call(Callback, [a:resp, v:null])
else
call call(Callback, [v:null, a:resp])
endif
endfunction
function! coc#client#is_running(name) abort
let client = get(s:clients, a:name, v:null)
if empty(client) | return 0 | endif
if !client['running'] | return 0 | endif
if s:is_vim
let status = job_status(ch_getjob(client['channel']))
return status ==# 'run'
else
let chan_id = client['chan_id']
let [code] = jobwait([chan_id], 10)
return code == -1
endif
endfunction
function! coc#client#stop(name) abort
let client = get(s:clients, a:name, v:null)
if empty(client) | return 1 | endif
let running = coc#client#is_running(a:name)
if !running
echohl WarningMsg | echom 'client '.a:name. ' not running.' | echohl None
return 1
endif
if s:is_vim
call job_stop(ch_getjob(client['channel']), 'term')
else
call jobstop(client['chan_id'])
endif
sleep 200m
if coc#client#is_running(a:name)
echohl Error | echom 'client '.a:name. ' stop failed.' | echohl None
return 0
endif
call s:on_exit(a:name, 0)
echohl MoreMsg | echom 'client '.a:name.' stopped!' | echohl None
return 1
endfunction
function! coc#client#request(name, method, args)
let client = get(s:clients, a:name, v:null)
if !empty(client)
return client['request'](a:method, a:args)
endif
endfunction
function! coc#client#notify(name, method, args)
let client = get(s:clients, a:name, v:null)
if !empty(client)
call client['notify'](a:method, a:args)
endif
endfunction
function! coc#client#request_async(name, method, args, cb)
let client = get(s:clients, a:name, v:null)
if !empty(client)
call client['request_async'](a:method, a:args, a:cb)
endif
endfunction
function! coc#client#on_response(name, id, resp, isErr)
let client = get(s:clients, a:name, v:null)
if !empty(client)
call client['on_async_response'](a:id, a:resp, a:isErr)
endif
endfunction
function! coc#client#restart(name) abort
let stopped = coc#client#stop(a:name)
if !stopped | return | endif
let client = get(s:clients, a:name, v:null)
if !empty(client)
call client['start']()
endif
endfunction
function! coc#client#restart_all()
for key in keys(s:clients)
call coc#client#restart(key)
endfor
endfunction
function! coc#client#open_log()
if !get(g:, 'node_client_debug', 0)
echohl Error | echon '[coc.nvim] use let g:node_client_debug = 1 in your vimrc to enabled debug mode.' | echohl None
return
endif
execute 'vs '.s:logfile
endfunction

View File

@ -0,0 +1,283 @@
scriptencoding utf-8
let s:activate = ""
let s:quit = ""
if has("gui_macvim") && has('gui_running')
let s:app = "MacVim"
elseif $TERM_PROGRAM ==# "Apple_Terminal"
let s:app = "Terminal"
elseif $TERM_PROGRAM ==# "iTerm.app"
let s:app = "iTerm2"
elseif has('mac')
let s:app = "System Events"
let s:quit = "quit"
let s:activate = 'activate'
endif
" Returns an approximate grey index for the given grey level
fun! s:grey_number(x)
if &t_Co == 88
if a:x < 23
return 0
elseif a:x < 69
return 1
elseif a:x < 103
return 2
elseif a:x < 127
return 3
elseif a:x < 150
return 4
elseif a:x < 173
return 5
elseif a:x < 196
return 6
elseif a:x < 219
return 7
elseif a:x < 243
return 8
else
return 9
endif
else
if a:x < 14
return 0
else
let l:n = (a:x - 8) / 10
let l:m = (a:x - 8) % 10
if l:m < 5
return l:n
else
return l:n + 1
endif
endif
endif
endfun
" Returns the actual grey level represented by the grey index
fun! s:grey_level(n)
if &t_Co == 88
if a:n == 0
return 0
elseif a:n == 1
return 46
elseif a:n == 2
return 92
elseif a:n == 3
return 115
elseif a:n == 4
return 139
elseif a:n == 5
return 162
elseif a:n == 6
return 185
elseif a:n == 7
return 208
elseif a:n == 8
return 231
else
return 255
endif
else
if a:n == 0
return 0
else
return 8 + (a:n * 10)
endif
endif
endfun
" Returns the palette index for the given grey index
fun! s:grey_colour(n)
if &t_Co == 88
if a:n == 0
return 16
elseif a:n == 9
return 79
else
return 79 + a:n
endif
else
if a:n == 0
return 16
elseif a:n == 25
return 231
else
return 231 + a:n
endif
endif
endfun
" Returns an approximate colour index for the given colour level
fun! s:rgb_number(x)
if &t_Co == 88
if a:x < 69
return 0
elseif a:x < 172
return 1
elseif a:x < 230
return 2
else
return 3
endif
else
if a:x < 75
return 0
else
let l:n = (a:x - 55) / 40
let l:m = (a:x - 55) % 40
if l:m < 20
return l:n
else
return l:n + 1
endif
endif
endif
endfun
" Returns the palette index for the given R/G/B colour indices
fun! s:rgb_colour(x, y, z)
if &t_Co == 88
return 16 + (a:x * 16) + (a:y * 4) + a:z
else
return 16 + (a:x * 36) + (a:y * 6) + a:z
endif
endfun
" Returns the actual colour level for the given colour index
fun! s:rgb_level(n)
if &t_Co == 88
if a:n == 0
return 0
elseif a:n == 1
return 139
elseif a:n == 2
return 205
else
return 255
endif
else
if a:n == 0
return 0
else
return 55 + (a:n * 40)
endif
endif
endfun
" Returns the palette index to approximate the given R/G/B colour levels
fun! s:colour(r, g, b)
" Get the closest grey
let l:gx = s:grey_number(a:r)
let l:gy = s:grey_number(a:g)
let l:gz = s:grey_number(a:b)
" Get the closest colour
let l:x = s:rgb_number(a:r)
let l:y = s:rgb_number(a:g)
let l:z = s:rgb_number(a:b)
if l:gx == l:gy && l:gy == l:gz
" There are two possibilities
let l:dgr = s:grey_level(l:gx) - a:r
let l:dgg = s:grey_level(l:gy) - a:g
let l:dgb = s:grey_level(l:gz) - a:b
let l:dgrey = (l:dgr * l:dgr) + (l:dgg * l:dgg) + (l:dgb * l:dgb)
let l:dr = s:rgb_level(l:gx) - a:r
let l:dg = s:rgb_level(l:gy) - a:g
let l:db = s:rgb_level(l:gz) - a:b
let l:drgb = (l:dr * l:dr) + (l:dg * l:dg) + (l:db * l:db)
if l:dgrey < l:drgb
" Use the grey
return s:grey_colour(l:gx)
else
" Use the colour
return s:rgb_colour(l:x, l:y, l:z)
endif
else
" Only one possibility
return s:rgb_colour(l:x, l:y, l:z)
endif
endfun
function! coc#color#rgb2term(rgb)
let l:r = ("0x" . strpart(a:rgb, 0, 2)) + 0
let l:g = ("0x" . strpart(a:rgb, 2, 2)) + 0
let l:b = ("0x" . strpart(a:rgb, 4, 2)) + 0
return s:colour(l:r, l:g, l:b)
endfun
" [r, g, b] ['255', '255', '255']
" return ['65535', '65535', '65535'] or return v:false to cancel
function! coc#color#pick_color(default_color)
if has('mac')
let default_color = map(a:default_color, {idx, val -> str2nr(val) * 65535 / 255 })
" This is the AppleScript magic:
let ascrpt = ['-e "tell application \"' . s:app . '\""',
\ '-e "' . s:activate . '"',
\ "-e \"set AppleScript's text item delimiters to {\\\",\\\"}\"",
\ '-e "set theColor to (choose color default color {' . default_color[0] . ", " . default_color[1] . ", " . default_color[2] . '}) as text"',
\ '-e "' . s:quit . '"',
\ '-e "end tell"',
\ '-e "return theColor"']
let res = trim(system("osascript " . join(ascrpt, ' ') . " 2>/dev/null"))
if empty(res)
return v:false
else
return split(trim(res), ',')
endif
endif
let hex_color = printf('#%02x%02x%02x', a:default_color[0], a:default_color[1], a:default_color[2])
if has('unix')
if executable('zenity')
let res = trim(system('zenity --title="Select a color" --color-selection --color="' . hex_color . '" 2> /dev/null'))
if empty(res)
return v:false
else
" res format is rgb(255,255,255)
return map(split(res[4:-2], ','), {idx, val -> string(str2nr(trim(val)) * 65535 / 255)})
endif
endif
endif
let rgb = v:false
if !has('python')
echohl Error | echom 'python support required, checkout :echo has(''python'')' | echohl None
return
endif
try
execute 'py import gtk'
catch /.*/
echohl Error | echom 'python gtk module not found' | echohl None
return
endtry
python << endpython
import vim
import gtk, sys
# message strings
wnd_title_insert = "Insert a color"
csd = gtk.ColorSelectionDialog(wnd_title_insert)
cs = csd.colorsel
cs.set_current_color(gtk.gdk.color_parse(vim.eval("hex_color")))
cs.set_current_alpha(65535)
cs.set_has_opacity_control(False)
# cs.set_has_palette(int(vim.eval("s:display_palette")))
if csd.run()==gtk.RESPONSE_OK:
c = cs.get_current_color()
s = [str(int(c.red)),',',str(int(c.green)),',',str(int(c.blue))]
thecolor = ''.join(s)
vim.command(":let rgb = split('%s',',')" % thecolor)
csd.destroy()
endpython
return rgb
endfunction

View File

@ -0,0 +1,244 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
" first window id for bufnr
" builtin bufwinid returns window of current tab only
function! coc#compat#buf_win_id(bufnr) abort
let info = filter(getwininfo(), 'v:val["bufnr"] =='.a:bufnr)
if empty(info)
return -1
endif
return info[0]['winid']
endfunction
function! coc#compat#buf_set_lines(bufnr, start, end, replacement) abort
if s:is_vim
call coc#api#notify('buf_set_lines', [a:bufnr, a:start, a:end, 0, a:replacement])
else
call nvim_buf_set_lines(a:bufnr, a:start, a:end, 0, a:replacement)
endif
endfunction
function! coc#compat#buf_line_count(bufnr) abort
if exists('*nvim_buf_line_count')
return nvim_buf_line_count(a:bufnr)
endif
if bufnr('%') == a:bufnr
return line('$')
endif
if exists('*getbufinfo')
let info = getbufinfo(a:bufnr)
if empty(info)
return 0
endif
" vim 8.1 has getbufinfo but no linecount
if has_key(info[0], 'linecount')
return info[0]['linecount']
endif
endif
if exists('*getbufline')
let lines = getbufline(a:bufnr, 1, '$')
return len(lines)
endif
let curr = bufnr('%')
execute 'noa buffer '.a:bufnr
let n = line('$')
execute 'noa buffer '.curr
return n
endfunction
function! coc#compat#prepend_lines(bufnr, replacement) abort
if exists('*appendbufline')
call appendbufline(a:bufnr, 0, a:replacement)
elseif !s:is_vim
call nvim_buf_set_lines(a:bufnr, 0, 0, 0, a:replacement)
else
throw 'appendbufline() required for prepend lines.'
endif
endfunction
function! coc#compat#win_is_valid(winid) abort
if exists('*nvim_win_is_valid')
return nvim_win_is_valid(a:winid)
endif
return !empty(getwininfo(a:winid))
endfunction
" clear matches by window id, not throw on none exists window.
" may not work on vim < 8.1.1084 & neovim < 0.4.0
function! coc#compat#clear_matches(winid) abort
if !coc#compat#win_is_valid(a:winid)
return
endif
let curr = win_getid()
if curr == a:winid
call clearmatches()
return
endif
if s:is_vim
if has('patch-8.1.1084')
call clearmatches(a:winid)
endif
else
if exists('*nvim_set_current_win')
noa call nvim_set_current_win(a:winid)
call clearmatches()
noa call nvim_set_current_win(curr)
endif
endif
endfunction
function! coc#compat#matchaddpos(group, pos, priority, winid) abort
let curr = win_getid()
if curr == a:winid
call matchaddpos(a:group, a:pos, a:priority, -1)
else
if s:is_vim
if has('patch-8.1.0218')
call matchaddpos(a:group, a:pos, a:priority, -1, {'window': a:winid})
endif
else
if has('nvim-0.4.0')
call matchaddpos(a:group, a:pos, a:priority, -1, {'window': a:winid})
elseif exists('*nvim_set_current_win')
noa call nvim_set_current_win(a:winid)
call matchaddpos(a:group, a:pos, a:priority, -1)
noa call nvim_set_current_win(curr)
endif
endif
endif
endfunction
function! coc#compat#buf_del_var(bufnr, name) abort
if !bufloaded(a:bufnr)
return
endif
if exists('*nvim_buf_del_var')
silent! call nvim_buf_del_var(a:bufnr, a:name)
else
if a:bufnr == bufnr('%')
execute 'unlet! b:'.a:name
elseif exists('*win_execute')
let winid = coc#compat#buf_win_id(a:bufnr)
if winid != -1
call win_execute(winid, 'unlet! b:'.a:name)
endif
endif
endif
endfunction
" hlGroup, pos, priority
function! coc#compat#matchaddgroups(winid, groups) abort
" add by winid
if has('patch-8.1.0218') || has('nvim-0.4.0')
for group in a:groups
call matchaddpos(group['hlGroup'], [group['pos']], group['priority'], -1, {'window': a:winid})
endfor
return
endif
let curr = win_getid()
if curr == a:winid
for group in a:groups
call matchaddpos(group['hlGroup'], [group['pos']], group['priority'], -1)
endfor
elseif exists('*nvim_set_current_win')
noa call nvim_set_current_win(a:winid)
for group in a:groups
call matchaddpos(group['hlGroup'], [group['pos']], group['priority'], -1)
endfor
noa call nvim_set_current_win(curr)
endif
endfunction
function! coc#compat#del_var(name) abort
if exists('*nvim_del_var')
silent! call nvim_del_var(a:name)
else
execute 'unlet! '.a:name
endif
endfunction
" remove keymap for specific buffer
function! coc#compat#buf_del_keymap(bufnr, mode, lhs) abort
if !bufloaded(a:bufnr)
return
endif
if exists('*nvim_buf_del_keymap')
try
call nvim_buf_del_keymap(a:bufnr, a:mode, a:lhs)
catch /^Vim\%((\a\+)\)\=:E5555/
" ignore keymap doesn't exist
endtry
return
endif
if bufnr == a:bufnr
execute 'silent! '.a:mode.'unmap <buffer> '.a:lhs
return
endif
if exists('*win_execute')
let winid = coc#compat#buf_win_id(a:bufnr)
if winid != -1
call win_execute(winid, a:mode.'unmap <buffer> '.a:lhs, 'silent!')
endif
endif
endfunction
function! coc#compat#buf_add_keymap(bufnr, mode, lhs, rhs, opts) abort
if !bufloaded(a:bufnr)
return
endif
if exists('*nvim_buf_set_keymap')
call nvim_buf_set_keymap(a:bufnr, a:mode, a:lhs, a:rhs, a:opts)
else
let cmd = a:mode . 'noremap '
for key in keys(a:opts)
if get(a:opts, key, 0)
let cmd .= '<'.key.'>'
endif
endfor
let cmd .= '<buffer> '.a:lhs.' '.a:rhs
if bufnr('%') == a:bufnr
execute cmd
elseif exists('*win_execute')
let winid = coc#compat#buf_win_id(a:bufnr)
if winid != -1
call win_execute(winid, cmd)
endif
endif
endif
endfunction
" execute command or list of commands in window
function! coc#compat#execute(winid, command, ...) abort
if exists('*win_execute')
if type(a:command) == v:t_string
keepalt call win_execute(a:winid, a:command, get(a:, 1, ''))
elseif type(a:command) == v:t_list
keepalt call win_execute(a:winid, join(a:command, "\n"), get(a:, 1, ''))
endif
elseif has('nvim')
if !nvim_win_is_valid(a:winid)
return
endif
let curr = nvim_get_current_win()
noa keepalt call nvim_set_current_win(a:winid)
if type(a:command) == v:t_string
exe get(a:, 1, '').' '.a:command
elseif type(a:command) == v:t_list
for cmd in a:command
exe get(a:, 1, '').' '.cmd
endfor
endif
noa keepalt call nvim_set_current_win(curr)
else
throw 'win_execute does not exist, please upgrade vim.'
endif
endfunc
function! coc#compat#trim(str)
if exists('*trim')
return trim(a:str)
endif
" TODO trim from beginning
return substitute(a:str, '\s\+$', '', '')
endfunction

View File

@ -0,0 +1,60 @@
scriptencoding utf-8
" Position of cursor relative to screen cell
function! coc#cursor#screen_pos() abort
let nr = winnr()
let [row, col] = win_screenpos(nr)
return [row + winline() - 2, col + wincol() - 2]
endfunction
function! coc#cursor#move_by_col(delta)
let pos = getcurpos()
call cursor(pos[1], pos[2] + a:delta)
endfunction
" Get cursor position.
function! coc#cursor#position()
return [line('.') - 1, strchars(strpart(getline('.'), 0, col('.') - 1))]
endfunction
" Move cursor to position.
function! coc#cursor#move_to(line, character) abort
let content = getline(a:line + 1)
let pre = strcharpart(content, 0, a:character)
let col = strlen(pre) + 1
call cursor(a:line + 1, col)
endfunction
" Character offset of current cursor, vim provide bytes offset only.
function! coc#cursor#char_offset() abort
let offset = 0
let lnum = line('.')
for i in range(1, lnum)
if i == lnum
let offset += strchars(strpart(getline('.'), 0, col('.')-1))
else
let offset += strchars(getline(i)) + 1
endif
endfor
return offset
endfunction
" Returns latest selection range
function! coc#cursor#get_selection(char) abort
let m = a:char ? 'char' : visualmode()
if empty(m)
return v:null
endif
let [_, sl, sc, soff] = getpos(m ==# 'char' ? "'[" : "'<")
let [_, el, ec, eoff] = getpos(m ==# 'char' ? "']" : "'>")
let start_idx = coc#string#get_character(getline(sl), sc)
if m ==# 'V'
return [sl - 1, start_idx, el, 0]
endif
let line = getline(el)
let end_idx = coc#string#get_character(line, ec)
if m !=# 'char'
let end_idx = end_idx == strchars(line) ? end_idx : end_idx + 1
endif
return [sl - 1, start_idx, el - 1, end_idx]
endfunction

View File

@ -0,0 +1,580 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:root = expand('<sfile>:h:h:h')
let s:prompt_win_bufnr = 0
let s:prompt_win_width = get(g:, 'coc_prompt_win_width', 32)
let s:float_supported = exists('*nvim_open_win') || has('patch-8.1.1719')
let s:frames = ['· ', '·· ', '···', ' ··', ' ·', ' ']
" Float window aside pum
function! coc#dialog#create_pum_float(winid, bufnr, lines, config) abort
if !pumvisible() || !s:float_supported
return v:null
endif
let pumbounding = a:config['pumbounding']
let pw = pumbounding['width'] + get(pumbounding, 'scrollbar', 0)
let rp = &columns - pumbounding['col'] - pw
let showRight = pumbounding['col'] > rp ? 0 : 1
let maxWidth = showRight ? coc#math#min(rp - 1, a:config['maxWidth']) : coc#math#min(pumbounding['col'] - 1, a:config['maxWidth'])
let border = get(a:config, 'border', [])
let bh = get(border, 0 ,0) + get(border, 2, 0)
let maxHeight = &lines - pumbounding['row'] - &cmdheight - 1 - bh
if maxWidth <= 2 || maxHeight < 1
return v:null
endif
let ch = 0
let width = 0
for line in a:lines
let dw = max([1, strdisplaywidth(line)])
let width = max([width, dw + 2])
let ch += float2nr(ceil(str2float(string(dw))/(maxWidth - 2)))
endfor
let width = float2nr(coc#math#min(maxWidth, width))
let height = float2nr(coc#math#min(maxHeight, ch))
let lines = map(a:lines, {_, s -> s =~# '^─' ? repeat('─', width - 2 + (s:is_vim && ch > height ? -1 : 0)) : s})
let opts = {
\ 'lines': lines,
\ 'highlights': get(a:config, 'highlights', []),
\ 'relative': 'editor',
\ 'col': showRight ? pumbounding['col'] + pw : pumbounding['col'] - width - 1,
\ 'row': pumbounding['row'],
\ 'height': height,
\ 'width': width - 2 + (s:is_vim && ch > height ? -1 : 0),
\ 'codes': get(a:config, 'codes', []),
\ }
for key in ['border', 'highlight', 'borderhighlight', 'winblend', 'focusable', 'shadow']
if has_key(a:config, key)
let opts[key] = a:config[key]
endif
endfor
call s:close_auto_hide_wins(a:winid)
let res = coc#float#create_float_win(a:winid, a:bufnr, opts)
if empty(res)
return v:null
endif
call setwinvar(res[0], 'kind', 'pum')
if has('nvim')
call coc#float#nvim_scrollbar(res[0])
endif
return res
endfunction
" Float window below/above cursor
function! coc#dialog#create_cursor_float(winid, bufnr, lines, config) abort
if !s:float_supported || coc#prompt#activated()
return v:null
endif
let pumAlignTop = get(a:config, 'pumAlignTop', 0)
let modes = get(a:config, 'modes', ['n', 'i', 'ic', 's'])
let mode = mode()
let currbuf = bufnr('%')
let pos = [line('.'), col('.')]
if index(modes, mode) == -1
return v:null
endif
if has('nvim') && mode ==# 'i'
" helps to fix undo issue, don't know why.
call feedkeys("\<C-g>u", 'n')
endif
let dimension = coc#dialog#get_config_cursor(a:lines, a:config)
if empty(dimension)
return v:null
endif
if pumvisible() && ((pumAlignTop && dimension['row'] <0)|| (!pumAlignTop && dimension['row'] > 0))
return v:null
endif
let width = dimension['width']
let lines = map(a:lines, {_, s -> s =~# '^─' ? repeat('─', width) : s})
let config = extend(extend({'lines': lines, 'relative': 'cursor'}, a:config), dimension)
call s:close_auto_hide_wins(a:winid)
let res = coc#float#create_float_win(a:winid, a:bufnr, config)
if empty(res)
return v:null
endif
let alignTop = dimension['row'] < 0
let winid = res[0]
let bufnr = res[1]
redraw
if has('nvim')
call coc#float#nvim_scrollbar(winid)
endif
return [currbuf, pos, winid, bufnr, alignTop]
endfunction
" Create float window for input
function! coc#dialog#create_prompt_win(title, default, opts) abort
call s:close_auto_hide_wins()
let bufnr = has('nvim') ? s:prompt_win_bufnr : 0
if s:is_vim
execute 'hi link CocPopupTerminal '.get(a:opts, 'highlight', 'CocFloating')
let node = expand(get(g:, 'coc_node_path', 'node'))
let bufnr = term_start([node, s:root . '/bin/prompt.js', a:default], {
\ 'term_highlight': 'CocPopupTerminal',
\ 'hidden': 1,
\ 'term_finish': 'close'
\ })
call term_setapi(bufnr, 'Coc')
call timer_start(100, { -> s:check_term_buffer(a:default, bufnr)})
endif
let config = s:get_prompt_dimension(a:title, a:default, a:opts)
let res = coc#float#create_float_win(0, bufnr, extend(config, {
\ 'style': 'minimal',
\ 'border': get(a:opts, 'border', [1,1,1,1]),
\ 'rounded': get(a:opts, 'rounded', 1),
\ 'prompt': 1,
\ 'title': a:title,
\ 'lines': s:is_vim ? v:null : [a:default],
\ 'highlight': get(a:opts, 'highlight', 'CocFloating'),
\ 'borderhighlight': [get(a:opts, 'borderhighlight', 'CocFloating')],
\ }))
if empty(res)
return
endif
let winid = res[0]
let bufnr = res[1]
if has('nvim')
let s:prompt_win_bufnr = res[1]
execute 'sign unplace 6 buffer='.s:prompt_win_bufnr
call nvim_set_current_win(winid)
inoremap <buffer> <C-a> <Home>
inoremap <buffer><expr><C-e> pumvisible() ? "\<C-e>" : "\<End>"
exe 'imap <silent><buffer> <esc> <esc><esc>'
exe 'nnoremap <silent><buffer> <esc> :call coc#float#close('.winid.')<CR>'
exe 'inoremap <silent><expr><nowait><buffer> <cr> "\<C-r>=coc#dialog#prompt_insert(getline(''.''))\<cr>\<esc>"'
call feedkeys('A', 'in')
endif
call coc#util#do_autocmd('CocOpenFloatPrompt')
if s:is_vim
let pos = popup_getpos(winid)
" width height row col
let dimension = [pos['width'], pos['height'], pos['line'], pos['col'] - 1]
else
let id = coc#float#get_related(winid, 'border')
let pos = nvim_win_get_position(id)
let dimension = [nvim_win_get_width(id), nvim_win_get_height(id), pos[0], pos[1]]
endif
return [bufnr, winid, dimension]
endfunction
" Create menu picker for pick single item
function! coc#dialog#create_menu(lines, config) abort
call s:close_auto_hide_wins()
let highlight = get(a:config, 'highlight', 'CocFloating')
let borderhighlight = get(a:config, 'borderhighlight', [highlight])
let relative = get(a:config, 'relative', 'cursor')
let opts = {
\ 'lines': a:lines,
\ 'highlight': highlight,
\ 'title': get(a:config, 'title', ''),
\ 'borderhighlight': borderhighlight,
\ 'maxWidth': get(a:config, 'maxWidth', 80),
\ 'maxHeight': get(a:config, 'maxHeight', 80),
\ 'rounded': get(a:config, 'rounded', 0),
\ 'border': [1, 1, 1, 1],
\ 'highlights': get(a:config, 'highlights', []),
\ 'relative': relative,
\ }
if s:is_vim
let opts['cursorline'] = 1
endif
if relative ==# 'editor'
let dimension = coc#dialog#get_config_editor(a:lines, opts)
else
let dimension = coc#dialog#get_config_cursor(a:lines, opts)
endif
call extend(opts, dimension)
let res = coc#float#create_float_win(0, s:prompt_win_bufnr, opts)
if empty(res)
return
endif
let s:prompt_win_bufnr = res[1]
redraw
if has('nvim')
call coc#float#nvim_scrollbar(res[0])
execute 'sign unplace 6 buffer='.s:prompt_win_bufnr
execute 'sign place 6 line=1 name=CocCurrentLine buffer='.s:prompt_win_bufnr
endif
return res
endfunction
" Create dialog at center of screen
function! coc#dialog#create_dialog(lines, config) abort
call s:close_auto_hide_wins()
" dialog always have borders
let title = get(a:config, 'title', '')
let buttons = get(a:config, 'buttons', [])
let highlight = get(a:config, 'highlight', 'CocFloating')
let borderhighlight = get(a:config, 'borderhighlight', [highlight])
let opts = {
\ 'title': title,
\ 'rounded': get(a:config, 'rounded', 0),
\ 'relative': 'editor',
\ 'border': [1,1,1,1],
\ 'close': get(a:config, 'close', 1),
\ 'highlight': highlight,
\ 'highlights': get(a:config, 'highlights', []),
\ 'buttons': buttons,
\ 'borderhighlight': borderhighlight,
\ 'getchar': get(a:config, 'getchar', 0)
\ }
if get(a:config, 'cursorline', 0)
let opts['cursorline'] = 1
endif
call extend(opts, coc#dialog#get_config_editor(a:lines, a:config))
let bufnr = coc#float#create_buf(0, a:lines)
let res = coc#float#create_float_win(0, bufnr, opts)
if empty(res)
return
endif
if has('nvim')
if get(a:config, 'cursorline', 0)
execute 'sign place 6 line=1 name=CocCurrentLine buffer='.bufnr
endif
redraw
call coc#float#nvim_scrollbar(res[0])
endif
return res
endfunction
function! coc#dialog#prompt_confirm(title, cb) abort
call s:close_auto_hide_wins()
if s:is_vim && exists('*popup_dialog')
try
call popup_dialog(a:title. ' (y/n)?', {
\ 'highlight': 'Normal',
\ 'filter': 'popup_filter_yesno',
\ 'callback': {id, res -> a:cb(v:null, res)},
\ 'borderchars': get(g:, 'coc_borderchars', ['─', '│', '─', '│', '╭', '╮', '╯', '╰']),
\ 'borderhighlight': ['MoreMsg']
\ })
catch /.*/
call a:cb(v:exception)
endtry
return
endif
if has('nvim-0.4.0')
let text = ' '. a:title . ' (y/n)? '
let maxWidth = coc#math#min(78, &columns - 2)
let width = coc#math#min(maxWidth, strdisplaywidth(text))
let maxHeight = &lines - &cmdheight - 1
let height = coc#math#min(maxHeight, float2nr(ceil(str2float(string(strdisplaywidth(text)))/width)))
let arr = coc#float#create_float_win(0, s:prompt_win_bufnr, {
\ 'col': &columns/2 - width/2 - 1,
\ 'row': maxHeight/2 - height/2 - 1,
\ 'width': width,
\ 'height': height,
\ 'border': [1,1,1,1],
\ 'focusable': v:false,
\ 'relative': 'editor',
\ 'highlight': 'Normal',
\ 'borderhighlight': ['MoreMsg'],
\ 'style': 'minimal',
\ 'lines': [text],
\ })
if empty(arr)
call a:cb('Window create failed!')
return
endif
let winid = arr[0]
let s:prompt_win_bufnr = arr[1]
let res = 0
redraw
" same result as vim
while 1
let key = nr2char(getchar())
if key == "\<C-c>"
let res = -1
break
elseif key == "\<esc>" || key == 'n' || key == 'N'
let res = 0
break
elseif key == 'y' || key == 'Y'
let res = 1
break
endif
endw
call coc#float#close(winid)
call a:cb(v:null, res)
" use relative editor since neovim doesn't support center position
elseif exists('*confirm')
let choice = confirm(a:title, "&Yes\n&No")
call a:cb(v:null, choice == 1)
else
echohl MoreMsg
echom a:title.' (y/n)'
echohl None
let confirm = nr2char(getchar())
redraw!
if !(confirm ==? "y" || confirm ==? "\r")
echohl Moremsg | echo 'Cancelled.' | echohl None
return 0
call a:cb(v:null, 0)
end
call a:cb(v:null, 1)
endif
endfunction
function! coc#dialog#get_config_editor(lines, config) abort
let title = get(a:config, 'title', '')
let maxheight = min([get(a:config, 'maxHeight', 78), &lines - &cmdheight - 6])
let maxwidth = min([get(a:config, 'maxWidth', 78), &columns - 2])
let buttons = get(a:config, 'buttons', [])
let minwidth = s:min_btns_width(buttons)
if maxheight <= 0 || maxwidth <= 0 || minwidth > maxwidth
throw 'Not enough spaces for float window'
endif
let ch = 0
let width = min([strdisplaywidth(title) + 1, maxwidth])
for line in a:lines
let dw = max([1, strdisplaywidth(line)])
if dw < maxwidth && dw > width
let width = dw
elseif dw >= maxwidth
let width = maxwidth
endif
let ch += float2nr(ceil(str2float(string(dw))/maxwidth))
endfor
let width = max([minwidth, width])
let height = coc#math#min(ch ,maxheight)
return {
\ 'row': &lines/2 - (height + 4)/2,
\ 'col': &columns/2 - (width + 2)/2,
\ 'width': width,
\ 'height': height,
\ }
endfunction
function! coc#dialog#prompt_insert(text) abort
call coc#rpc#notify('PromptInsert', [a:text, bufnr('%')])
return ''
endfunction
" Dimension of window with lines relative to cursor
" Width & height excludes border & padding
function! coc#dialog#get_config_cursor(lines, config) abort
let preferTop = get(a:config, 'preferTop', 0)
let title = get(a:config, 'title', '')
let border = get(a:config, 'border', [])
if empty(border) && len(title)
let border = [1, 1, 1, 1]
endif
let bh = get(border, 0, 0) + get(border, 2, 0)
let vh = &lines - &cmdheight - 1
if vh <= 0
return v:null
endif
let maxWidth = coc#math#min(get(a:config, 'maxWidth', &columns - 1), &columns - 1)
if maxWidth < 3
return v:null
endif
let maxHeight = coc#math#min(get(a:config, 'maxHeight', vh), vh)
let ch = 0
let width = coc#math#min(40, strdisplaywidth(title)) + 3
for line in a:lines
let dw = max([1, strdisplaywidth(line)])
let width = max([width, dw + 2])
let ch += float2nr(ceil(str2float(string(dw))/(maxWidth - 2)))
endfor
let width = coc#math#min(maxWidth, width)
let [lineIdx, colIdx] = coc#cursor#screen_pos()
" How much we should move left
let offsetX = coc#math#min(get(a:config, 'offsetX', 0), colIdx)
let showTop = 0
let hb = vh - lineIdx -1
if lineIdx > bh + 2 && (preferTop || (lineIdx > hb && hb < ch + bh))
let showTop = 1
endif
let height = coc#math#min(maxHeight, ch + bh, showTop ? lineIdx - 1 : hb)
if height <= bh
return v:null
endif
let col = - max([offsetX, colIdx - (&columns - 1 - width)])
let row = showTop ? - height + bh : 1
return {
\ 'row': row,
\ 'col': col,
\ 'width': width - 2,
\ 'height': height - bh
\ }
endfunction
function! coc#dialog#change_border_hl(winid, hlgroup) abort
if !hlexists(a:hlgroup)
return
endif
if s:is_vim
if coc#float#valid(a:winid)
call popup_setoptions(a:winid, {'borderhighlight': repeat([a:hlgroup], 4)})
redraw
endif
else
let winid = coc#float#get_related(a:winid, 'border')
if winid > 0
call setwinvar(winid, '&winhl', 'Normal:'.a:hlgroup.',NormalNC:'.a:hlgroup)
endif
endif
endfunction
function! coc#dialog#change_title(winid, title) abort
if s:is_vim
if coc#float#valid(a:winid)
call popup_setoptions(a:winid, {'title': a:title})
redraw
endif
else
let winid = coc#float#get_related(a:winid, 'border')
if winid > 0
let bufnr = winbufnr(winid)
let line = getbufline(bufnr, 1)[0]
let top = strcharpart(line, 0, 1)
\.repeat('─', strchars(line) - 2)
\.strcharpart(line, strchars(line) - 1, 1)
if !empty(a:title)
let top = coc#string#compose(top, 1, a:title.' ')
endif
call nvim_buf_set_lines(bufnr, 0, 1, v:false, [top])
endif
endif
endfunction
function! coc#dialog#change_loading(winid, loading) abort
if coc#float#valid(a:winid)
let winid = coc#float#get_related(a:winid, 'loading')
if !a:loading && winid > 0
call coc#float#close(winid)
endif
if a:loading && winid == 0
let bufnr = s:create_loading_buf()
if s:is_vim
let pos = popup_getpos(a:winid)
let winid = popup_create(bufnr, {
\ 'line': pos['line'] + 1,
\ 'col': pos['col'] + pos['width'] - 4,
\ 'maxheight': 1,
\ 'maxwidth': 3,
\ 'zindex': 999,
\ 'highlight': get(popup_getoptions(a:winid), 'highlight', 'CocFloating')
\ })
else
let pos = nvim_win_get_position(a:winid)
let width = nvim_win_get_width(a:winid)
let opts = {
\ 'relative': 'editor',
\ 'row': pos[0],
\ 'col': pos[1] + width - 3,
\ 'focusable': v:false,
\ 'width': 3,
\ 'height': 1,
\ 'style': 'minimal',
\ }
if has('nvim-0.5.1')
let opts['zindex'] = 900
endif
let winid = nvim_open_win(bufnr, v:false, opts)
call setwinvar(winid, '&winhl', getwinvar(a:winid, '&winhl'))
endif
call setwinvar(winid, 'kind', 'loading')
call setbufvar(bufnr, 'target', a:winid)
call setbufvar(bufnr, 'popup', winid)
call coc#float#add_related(winid, a:winid)
endif
endif
endfunction
" Could be center(with optional marginTop) or cursor
function! s:get_prompt_dimension(title, default, opts) abort
let relative = get(a:opts, 'position', 'cursor') ==# 'cursor' ? 'cursor' : 'editor'
let curr = win_screenpos(winnr())[1] + wincol() - 2
let minWidth = get(a:opts, 'minWidth', s:prompt_win_width)
let width = min([max([strwidth(a:default) + 2, strwidth(a:title) + 2, minWidth]), &columns - 2])
if get(a:opts, 'maxWidth', 0)
let width = min([width, a:opts['maxWidth']])
endif
if relative ==# 'cursor'
let [lineIdx, colIdx] = coc#cursor#screen_pos()
if width == &columns - 2
let col = 0 - curr
else
let col = curr + width <= &columns - 2 ? 0 : curr + width - &columns + 2
endif
let config = {
\ 'row': lineIdx == 0 ? 1 : 0,
\ 'col': colIdx == 0 ? 0 : col - 1,
\ }
else
let marginTop = get(a:opts, 'marginTop', v:null)
if marginTop is v:null
let row = (&lines - &cmdheight - 2) / 2
else
let row = marginTop < 2 ? 1 : min([marginTop, &columns - &cmdheight])
endif
let config = {
\ 'col': float2nr((&columns - width) / 2),
\ 'row': row,
\ }
endif
return extend(config, {'relative': relative, 'width': width, 'height': 1})
endfunction
function! s:check_term_buffer(current, bufnr) abort
if bufloaded(a:bufnr)
let text = term_getline(a:bufnr, '.')
if text !=# a:current
let cursor = term_getcursor(a:bufnr)
let info = {
\ 'lnum': cursor[0],
\ 'col': cursor[1],
\ 'line': text ==# ' ' && cursor[1] == 1 ? '' : text,
\ 'changedtick': 0
\ }
call coc#rpc#notify('CocAutocmd', ['TextChangedI', a:bufnr, info])
endif
call timer_start(50, { -> s:check_term_buffer(text, a:bufnr)})
endif
endfunction
function! s:min_btns_width(buttons) abort
if empty(a:buttons)
return 0
endif
let minwidth = len(a:buttons)*3 - 1
for txt in a:buttons
let minwidth = minwidth + strdisplaywidth(txt)
endfor
return minwidth
endfunction
" Close windows that should auto hide
function! s:close_auto_hide_wins(...) abort
let winids = coc#float#get_float_win_list()
let except = get(a:, 1, 0)
for id in winids
if except && id == except
continue
endif
if coc#window#get_var(id, 'autohide', 0)
call coc#float#close(id)
endif
endfor
endfunction
function! s:create_loading_buf() abort
let bufnr = coc#float#create_buf(0)
call s:change_loading_buf(bufnr, 0)
return bufnr
endfunction
function! s:change_loading_buf(bufnr, idx) abort
if bufloaded(a:bufnr)
let target = getbufvar(a:bufnr, 'target', v:null)
if !empty(target) && !coc#float#valid(target)
call coc#float#close(getbufvar(a:bufnr, 'popup'))
return
endif
let line = get(s:frames, a:idx, ' ')
call setbufline(a:bufnr, 1, line)
call coc#highlight#add_highlight(a:bufnr, -1, 'CocNotificationProgress', 0, 0, -1)
let idx = a:idx == len(s:frames) - 1 ? 0 : a:idx + 1
call timer_start(100, { -> s:change_loading_buf(a:bufnr, idx)})
endif
endfunction

View File

@ -0,0 +1,32 @@
scriptencoding utf-8
function! coc#dict#equal(one, two) abort
for key in keys(a:one)
if a:one[key] != a:two[key]
return 0
endif
endfor
return 1
endfunction
" Return new dict with keys removed
function! coc#dict#omit(dict, keys) abort
let res = {}
for key in keys(a:dict)
if index(a:keys, key) == -1
let res[key] = a:dict[key]
endif
endfor
return res
endfunction
" Return new dict with keys only
function! coc#dict#pick(dict, keys) abort
let res = {}
for key in keys(a:dict)
if index(a:keys, key) != -1
let res[key] = a:dict[key]
endif
endfor
return res
endfunction

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
scriptencoding utf-8
" Helper methods for viml
function! coc#helper#get_charactor(line, col) abort
return strchars(strpart(a:line, 0, a:col - 1))
endfunction
function! coc#helper#last_character(line) abort
return strcharpart(a:line, strchars(a:line) - 1, 1)
endfunction
function! coc#helper#obj_equal(one, two) abort
for key in keys(a:one)
if a:one[key] != a:two[key]
return 0
endif
endfor
return 1
endfunction
" get change between two lines
function! coc#helper#str_diff(curr, previous, col) abort
let end = strpart(a:curr, a:col - 1)
let start = strpart(a:curr, 0, a:col -1)
let endOffset = 0
let startOffset = 0
let currLen = strchars(a:curr)
let prevLen = strchars(a:previous)
if len(end)
let endLen = strchars(end)
for i in range(min([prevLen, endLen]))
if strcharpart(end, endLen - 1 - i, 1) ==# strcharpart(a:previous, prevLen -1 -i, 1)
let endOffset = endOffset + 1
else
break
endif
endfor
endif
let remain = endOffset == 0 ? a:previous : strcharpart(a:previous, 0, prevLen - endOffset)
if len(remain)
for i in range(min([strchars(remain), strchars(start)]))
if strcharpart(remain, i, 1) ==# strcharpart(start, i ,1)
let startOffset = startOffset + 1
else
break
endif
endfor
endif
return {
\ 'start': startOffset,
\ 'end': prevLen - endOffset,
\ 'text': strcharpart(a:curr, startOffset, currLen - startOffset - endOffset)
\ }
endfunction
function! coc#helper#str_apply(content, diff) abort
let totalLen = strchars(a:content)
let endLen = totalLen - a:diff['end']
return strcharpart(a:content, 0, a:diff['start']).a:diff['text'].strcharpart(a:content, a:diff['end'], endLen)
endfunction
" insert inserted to line at position, use ... when result is too long
" line should only contains character has strwidth equals 1
function! coc#helper#str_compose(line, position, inserted) abort
let width = strwidth(a:line)
let text = a:inserted
let res = a:line
let need_truncate = a:position + strwidth(text) + 1 > width
if need_truncate
let remain = width - a:position - 3
if remain < 2
" use text for full line, use first & end of a:line, ignore position
let res = strcharpart(a:line, 0, 1)
let w = strwidth(res)
for i in range(strchars(text))
let c = strcharpart(text, i, 1)
let a = strwidth(c)
if w + a <= width - 1
let w = w + a
let res = res.c
endif
endfor
let res = res.strcharpart(a:line, w)
else
let res = strcharpart(a:line, 0, a:position)
let w = strwidth(res)
for i in range(strchars(text))
let c = strcharpart(text, i, 1)
let a = strwidth(c)
if w + a <= width - 3
let w = w + a
let res = res.c
endif
endfor
let res = res.'..'
let w = w + 2
let res = res.strcharpart(a:line, w)
endif
else
let first = strcharpart(a:line, 0, a:position)
let res = first.text.strcharpart(a:line, a:position + strwidth(text))
endif
return res
endfunction
" Return new dict with keys removed
function! coc#helper#dict_omit(dict, keys) abort
let res = {}
for key in keys(a:dict)
if index(a:keys, key) == -1
let res[key] = a:dict[key]
endif
endfor
return res
endfunction
" Return new dict with keys only
function! coc#helper#dict_pick(dict, keys) abort
let res = {}
for key in keys(a:dict)
if index(a:keys, key) != -1
let res[key] = a:dict[key]
endif
endfor
return res
endfunction
" support for float values
function! coc#helper#min(first, ...) abort
let val = a:first
for i in range(0, len(a:000) - 1)
if a:000[i] < val
let val = a:000[i]
endif
endfor
return val
endfunction
" support for float values
function! coc#helper#max(first, ...) abort
let val = a:first
for i in range(0, len(a:000) - 1)
if a:000[i] > val
let val = a:000[i]
endif
endfor
return val
endfunction

View File

@ -0,0 +1,729 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:clear_match_by_window = has('nvim-0.6.0') || has('patch-8.1.1084')
let s:set_extmark = has('nvim') && exists('*nvim_buf_set_extmark')
let s:del_extmark = has('nvim') && exists('*nvim_buf_del_extmark')
let s:prop_offset = get(g:, 'coc_text_prop_offset', 1000)
let s:namespace_map = {}
let s:ns_id = 1
let s:diagnostic_hlgroups = ['CocErrorHighlight', 'CocWarningHighlight', 'CocInfoHighlight', 'CocHintHighlight', 'CocDeprecatedHighlight', 'CocUnusedHighlight']
" Maximum count to highlight each time.
let g:coc_highlight_maximum_count = get(g:, 'coc_highlight_maximum_count', 100)
if has('nvim-0.5.0') && s:clear_match_by_window == 0
try
call getmatches(0)
let s:clear_match_by_window = 1
catch /^Vim\%((\a\+)\)\=:E118/
" ignored
endtry
endif
" Update buffer region by region.
function! coc#highlight#buffer_update(bufnr, key, highlights, ...) abort
if !bufloaded(a:bufnr)
return
endif
if empty(a:highlights)
call coc#highlight#clear_highlight(a:bufnr, a:key, 0, -1)
return
endif
let priority = get(a:, 1, v:null)
let changedtick = getbufvar(a:bufnr, 'changedtick', 0)
if type(get(a:, 2, v:null)) == 0 && changedtick > a:2
return
endif
let hls = map(copy(a:highlights), "{'hlGroup':v:val[0],'lnum':v:val[1],'colStart':v:val[2],'colEnd':v:val[3],'combine':get(v:val,4,1),'start_incl':get(v:val,5,0),'end_incl':get(v:val,6,0)}")
if len(hls) <= g:coc_highlight_maximum_count || get(g:, 'coc_node_env', '') ==# 'test'
call coc#highlight#update_highlights(a:bufnr, a:key, hls, 0, -1, priority)
return
endif
let linecount = coc#compat#buf_line_count(a:bufnr)
let groups = s:group_hls(hls, linecount)
call s:update_highlights_timer(a:bufnr, changedtick, a:key, priority, groups, 0)
endfunction
" Update highlights by check exists highlights.
" 0 based, end exclusive start and end
function! coc#highlight#update_highlights(bufnr, key, highlights, ...) abort
let bufnr = a:bufnr == 0 ? bufnr('%') : a:bufnr
if !bufloaded(bufnr)
return
endif
let start = get(a:, 1, 0)
let end = get(a:, 2, -1)
if end == 0
return
endif
let linecount = coc#compat#buf_line_count(a:bufnr)
if end >= linecount
let end = -1
endif
if empty(a:highlights)
call coc#highlight#clear_highlight(bufnr, a:key, start, end)
return
endif
let priority = get(a:, 3, v:null)
let total = len(a:highlights)
" index list that exists with current highlights
let exists = []
let ns = coc#highlight#create_namespace(a:key)
if has('nvim-0.5.0') || exists('*prop_list')
let endLnum = end < 0 ? linecount - 1 : end - 1
let firstLnum = a:highlights[0]['lnum']
if firstLnum > start
call coc#highlight#clear_highlight(bufnr, a:key, start, firstLnum)
let start = firstLnum
endif
let lastLnum = a:highlights[total - 1]['lnum']
if lastLnum < endLnum
call coc#highlight#clear_highlight(bufnr, a:key, lastLnum + 1, endLnum + 1)
let endLnum = lastLnum
endif
let current = coc#highlight#get_highlights(bufnr, a:key, start, endLnum)
let currIndex = 0
let clearLnums = []
if !empty(current)
for [lnum, items] in s:to_group(current)
let indexes = []
let currIndexes = range(0, len(items) - 1)
"call coc#rpc#notify('Log', ['items:', lnum, items])
while currIndex != total
let hi = a:highlights[currIndex]
if hi['lnum'] == lnum
for idx in currIndexes
let item = items[idx]
if hi['hlGroup'] ==# item[0] && hi['colStart'] == item[2] && hi['colEnd'] == item[3]
call add(indexes, currIndex)
call filter(currIndexes, 'v:val != '.idx)
break
elseif item[2] > hi['colStart']
break
endif
endfor
elseif hi['lnum'] > lnum
break
endif
let currIndex = currIndex + 1
endwhile
if !empty(currIndexes)
if s:del_extmark
for idx in currIndexes
call nvim_buf_del_extmark(bufnr, ns, items[idx][4])
endfor
call extend(exists, indexes)
else
call add(clearLnums, lnum)
endif
else
" all highlights of current line exists, not clear.
call extend(exists, indexes)
endif
endfor
endif
call coc#highlight#clear(bufnr, a:key, clearLnums)
else
call coc#highlight#clear_highlight(bufnr, a:key, start, end)
endif
let indexes = range(0, total - 1)
if !empty(exists)
let indexes = filter(indexes, 'index(exists, v:val) == -1')
endif
for idx in indexes
let hi = a:highlights[idx]
let opts = {}
if type(priority) == 0
let opts['priority'] = s:get_priority(a:key, hi['hlGroup'], priority)
endif
for key in ['combine', 'start_incl', 'end_incl']
if has_key(hi, key)
let opts[key] = hi[key]
endif
endfor
call coc#highlight#add_highlight(bufnr, ns, hi['hlGroup'], hi['lnum'], hi['colStart'], hi['colEnd'], opts)
endfor
endfunction
" 0 based line, start_col and end_col
" 0 based start & end line, end inclusive.
function! coc#highlight#get_highlights(bufnr, key, ...) abort
if !bufloaded(a:bufnr)
return v:null
endif
if !has_key(s:namespace_map, a:key)
return []
endif
let start = get(a:, 1, 0)
let end = get(a:, 2, -1)
let res = []
let ns = s:namespace_map[a:key]
if exists('*prop_list')
" Could filter by end_lnum and ids
if has('patch-8.2.3652')
let endLnum = end == -1 ? -1 : end + 1
for prop in prop_list(start + 1, {'bufnr': a:bufnr, 'ids': [s:prop_offset + ns], 'end_lnum': endLnum})
if prop['start'] == 0 || prop['end'] == 0
" multi line textprop are not supported, simply ignore it
continue
endif
let startCol = prop['col'] - 1
let endCol = startCol + prop['length']
call add(res, [s:prop_type_hlgroup(prop['type']), prop['lnum'] - 1, startCol, endCol])
endfor
else
if end == -1
let end = coc#compat#buf_line_count(a:bufnr)
else
let end = end + 1
endif
let id = s:prop_offset + ns
for line in range(start + 1, end)
for prop in prop_list(line, {'bufnr': a:bufnr})
if prop['id'] != id || prop['start'] == 0 || prop['end'] == 0
" multi line textprop are not supported, simply ignore it
continue
endif
let startCol = prop['col'] - 1
let endCol = startCol + prop['length']
call add(res, [s:prop_type_hlgroup(prop['type']), line - 1, startCol, endCol])
endfor
endfor
endif
elseif has('nvim-0.5.0')
let start = [start, 0]
let maximum = end == -1 ? nvim_buf_line_count(a:bufnr) : end + 1
let end = end == -1 ? -1 : [end + 1, 0]
let markers = nvim_buf_get_extmarks(a:bufnr, ns, start, -1, {'details': v:true})
for [marker_id, line, start_col, details] in markers
if line >= maximum
" Could be markers exceed end of line
continue
endif
let delta = details['end_row'] - line
if delta > 1 || (delta == 1 && details['end_col'] != 0)
" can't handle, single line only
continue
endif
let endCol = details['end_col']
if endCol == start_col
call nvim_buf_del_extmark(a:bufnr, ns, marker_id)
continue
endif
if delta == 1
let text = get(nvim_buf_get_lines(a:bufnr, line, line + 1, 0), 0, '')
let endCol = strlen(text)
endif
call add(res, [details['hl_group'], line, start_col, endCol, marker_id])
endfor
else
throw 'Get highlights requires neovim 0.5.0 or vim support prop_list'
endif
return res
endfunction
" Add multiple highlights to buffer.
" type HighlightItem = [hlGroup, lnum, colStart, colEnd, combine?, start_incl?, end_incl?]
function! coc#highlight#set(bufnr, key, highlights, priority) abort
if !bufloaded(a:bufnr)
return
endif
let ns = coc#highlight#create_namespace(a:key)
if len(a:highlights) > g:coc_highlight_maximum_count
call s:add_highlights_timer(a:bufnr, ns, a:highlights, a:priority)
else
call s:add_highlights(a:bufnr, ns, a:highlights, a:priority)
endif
endfunction
" Clear highlights by 0 based line numbers.
function! coc#highlight#clear(bufnr, key, lnums) abort
if !bufloaded(a:bufnr)
return
endif
let ns = coc#highlight#create_namespace(a:key)
for lnum in a:lnums
if has('nvim')
call nvim_buf_clear_namespace(a:bufnr, ns, lnum, lnum + 1)
else
call coc#api#call('buf_clear_namespace', [a:bufnr, ns, lnum, lnum + 1])
endif
endfor
" clear highlights in invalid line.
if has('nvim')
let linecount = nvim_buf_line_count(a:bufnr)
call nvim_buf_clear_namespace(a:bufnr, ns, linecount, -1)
endif
endfunction
function! coc#highlight#del_markers(bufnr, key, ids) abort
if !bufloaded(a:bufnr)
return
endif
let ns = coc#highlight#create_namespace(a:key)
for id in a:ids
call nvim_buf_del_extmark(a:bufnr, ns, id)
endfor
endfunction
" highlight LSP range,
function! coc#highlight#ranges(bufnr, key, hlGroup, ranges, ...) abort
let bufnr = a:bufnr == 0 ? bufnr('%') : a:bufnr
if !bufloaded(bufnr) || !exists('*getbufline')
return
endif
let opts = get(a:, 1, {})
let synmaxcol = getbufvar(a:bufnr, '&synmaxcol', 1000)
if synmaxcol == 0
let synmaxcol = 1000
endif
let synmaxcol = min([synmaxcol, 1000])
let srcId = coc#highlight#create_namespace(a:key)
for range in a:ranges
let start = range['start']
let end = range['end']
for lnum in range(start['line'] + 1, end['line'] + 1)
let arr = getbufline(bufnr, lnum)
let line = empty(arr) ? '' : arr[0]
if empty(line)
continue
endif
if start['character'] > synmaxcol || end['character'] > synmaxcol
continue
endif
" TODO don't know how to count UTF16 code point, should work most cases.
let colStart = lnum == start['line'] + 1 ? strlen(strcharpart(line, 0, start['character'])) : 0
let colEnd = lnum == end['line'] + 1 ? strlen(strcharpart(line, 0, end['character'])) : strlen(line)
if colStart == colEnd
continue
endif
call coc#highlight#add_highlight(bufnr, srcId, a:hlGroup, lnum - 1, colStart, colEnd, opts)
endfor
endfor
endfunction
function! coc#highlight#add_highlight(bufnr, src_id, hl_group, line, col_start, col_end, ...) abort
let opts = get(a:, 1, {})
let priority = get(opts, 'priority', v:null)
if has('nvim')
if s:set_extmark && a:src_id != -1
" get(opts, 'start_incl', 0) ? v:true : v:false,
try
call nvim_buf_set_extmark(a:bufnr, a:src_id, a:line, a:col_start, {
\ 'end_col': a:col_end,
\ 'hl_group': a:hl_group,
\ 'hl_mode': get(opts, 'combine', 1) ? 'combine' : 'replace',
\ 'right_gravity': v:true,
\ 'end_right_gravity': v:false,
\ 'priority': type(priority) == 0 ? min([priority, 4096]) : 4096,
\ })
catch /^Vim\%((\a\+)\)\=:E5555/
" the end_col could be invalid, ignore this error
endtry
else
call nvim_buf_add_highlight(a:bufnr, a:src_id, a:hl_group, a:line, a:col_start, a:col_end)
endif
else
call coc#api#call('buf_add_highlight', [a:bufnr, a:src_id, a:hl_group, a:line, a:col_start, a:col_end, opts])
endif
endfunction
function! coc#highlight#clear_highlight(bufnr, key, start_line, end_line) abort
let bufnr = a:bufnr == 0 ? bufnr('%') : a:bufnr
if !bufloaded(bufnr)
return
endif
let src_id = coc#highlight#create_namespace(a:key)
if has('nvim')
call nvim_buf_clear_namespace(a:bufnr, src_id, a:start_line, a:end_line)
else
call coc#api#call('buf_clear_namespace', [a:bufnr, src_id, a:start_line, a:end_line])
endif
endfunction
" highlight buffer in winid with CodeBlock &HighlightItems
" export interface HighlightItem {
" lnum: number // 0 based
" hlGroup: string
" colStart: number // 0 based
" colEnd: number
" }
" export interface CodeBlock {
" filetype?: string
" hlGroup?: string
" startLine: number // 0 based
" endLine: number
" }
function! coc#highlight#add_highlights(winid, codes, highlights) abort
" clear highlights
call coc#compat#execute(a:winid, 'syntax clear')
let bufnr = winbufnr(a:winid)
call coc#highlight#clear_highlight(bufnr, -1, 0, -1)
if !empty(a:codes)
call coc#highlight#highlight_lines(a:winid, a:codes)
endif
if !empty(a:highlights)
for item in a:highlights
call coc#highlight#add_highlight(bufnr, -1, item['hlGroup'], item['lnum'], item['colStart'], item['colEnd'])
endfor
endif
endfunction
" Add highlights to line groups of winid, support hlGroup and filetype
" config should have startLine, endLine (0 based, end excluded) and filetype or hlGroup
" endLine should > startLine and endLine is excluded
"
" export interface CodeBlock {
" filetype?: string
" hlGroup?: string
" startLine: number // 0 based
" endLine: number
" }
function! coc#highlight#highlight_lines(winid, blocks) abort
let region_id = 1
let defined = []
let cmds = []
for config in a:blocks
let start = config['startLine'] + 1
let end = config['endLine'] == -1 ? len(getbufline(winbufnr(a:winid), 1, '$')) + 1 : config['endLine'] + 1
let filetype = get(config, 'filetype', '')
let hlGroup = get(config, 'hlGroup', '')
if !empty(hlGroup)
call add(cmds, 'syntax region '.hlGroup.' start=/\%'.start.'l/ end=/\%'.end.'l/')
else
let filetype = matchstr(filetype, '\v^\w+')
if empty(filetype) || filetype == 'txt' || index(get(g:, 'coc_markdown_disabled_languages', []), filetype) != -1
continue
endif
if index(defined, filetype) == -1
call add(cmds, 'syntax include @'.toupper(filetype).' syntax/'.filetype.'.vim')
call add(cmds, 'unlet! b:current_syntax')
call add(defined, filetype)
endif
call add(cmds, 'syntax region CodeBlock'.region_id.' start=/\%'.start.'l/ end=/\%'.end.'l/ contains=@'.toupper(filetype).' keepend')
let region_id = region_id + 1
endif
endfor
if !empty(cmds)
call coc#compat#execute(a:winid, cmds, 'silent!')
endif
endfunction
" Compose hlGroups with foreground and background colors.
function! coc#highlight#compose_hlgroup(fgGroup, bgGroup) abort
let hlGroup = 'Fg'.a:fgGroup.'Bg'.a:bgGroup
if a:fgGroup ==# a:bgGroup
return a:fgGroup
endif
if hlexists(hlGroup)
return hlGroup
endif
let fgId = synIDtrans(hlID(a:fgGroup))
let bgId = synIDtrans(hlID(a:bgGroup))
let isGuiReversed = synIDattr(fgId, 'reverse', 'gui') !=# '1' || synIDattr(bgId, 'reverse', 'gui') !=# '1'
let guifg = isGuiReversed ? synIDattr(fgId, 'fg', 'gui') : synIDattr(fgId, 'bg', 'gui')
let guibg = isGuiReversed ? synIDattr(bgId, 'bg', 'gui') : synIDattr(bgId, 'fg', 'gui')
let isCtermReversed = synIDattr(fgId, 'reverse', 'cterm') !=# '1' || synIDattr(bgId, 'reverse', 'cterm') !=# '1'
let ctermfg = isCtermReversed ? synIDattr(fgId, 'fg', 'cterm') : synIDattr(fgId, 'bg', 'cterm')
let ctermbg = isCtermReversed ? synIDattr(bgId, 'bg', 'cterm') : synIDattr(bgId, 'fg', 'cterm')
let bold = synIDattr(fgId, 'bold') ==# '1'
let italic = synIDattr(fgId, 'italic') ==# '1'
let underline = synIDattr(fgId, 'underline') ==# '1'
let cmd = 'silent hi ' . hlGroup
if !empty(guifg)
let cmd .= ' guifg=' . guifg
endif
if !empty(ctermfg)
let cmd .= ' ctermfg=' . ctermfg
elseif guifg =~# '^#'
let cmd .= ' ctermfg=' . coc#color#rgb2term(strpart(guifg, 1))
endif
if !empty(guibg)
let cmd .= ' guibg=' . guibg
endif
if !empty(ctermbg)
let cmd .= ' ctermbg=' . ctermbg
elseif guibg =~# '^#'
let cmd .= ' ctermbg=' . coc#color#rgb2term(strpart(guibg, 1))
endif
if bold
let cmd .= ' cterm=bold gui=bold'
elseif italic
let cmd .= ' cterm=italic gui=italic'
elseif underline
let cmd .= ' cterm=underline gui=underline'
endif
if cmd ==# 'silent hi ' . hlGroup
return 'Normal'
endif
execute cmd
return hlGroup
endfunction
" add matches for winid, use 0 for current window.
function! coc#highlight#match_ranges(winid, bufnr, ranges, hlGroup, priority) abort
let winid = a:winid == 0 ? win_getid() : a:winid
let bufnr = a:bufnr == 0 ? winbufnr(winid) : a:bufnr
if empty(getwininfo(winid)) || (a:bufnr != 0 && winbufnr(a:winid) != a:bufnr)
" not valid
return []
endif
if !s:clear_match_by_window
let curr = win_getid()
if has('nvim')
noa call nvim_set_current_win(winid)
else
noa call win_gotoid(winid)
endif
endif
let ids = []
for range in a:ranges
let pos = []
let start = range['start']
let end = range['end']
for lnum in range(start['line'] + 1, end['line'] + 1)
let arr = getbufline(bufnr, lnum)
let line = empty(arr) ? '' : arr[0]
if empty(line)
continue
endif
let colStart = lnum == start['line'] + 1 ? strlen(strcharpart(line, 0, start['character'])) + 1 : 1
let colEnd = lnum == end['line'] + 1 ? strlen(strcharpart(line, 0, end['character'])) + 1 : strlen(line) + 1
if colStart == colEnd
continue
endif
call add(pos, [lnum, colStart, colEnd - colStart])
endfor
if !empty(pos)
let opts = s:clear_match_by_window ? {'window': a:winid} : {}
let i = 1
let l = []
for p in pos
call add(l, p)
if i % 8 == 0
let id = matchaddpos(a:hlGroup, l, a:priority, -1, opts)
call add(ids, id)
let l = []
endif
let i += 1
endfor
if !empty(l)
let id = matchaddpos(a:hlGroup, l, a:priority, -1, opts)
call add(ids, id)
endif
endif
endfor
if !s:clear_match_by_window
if has('nvim')
noa call nvim_set_current_win(curr)
else
noa call win_gotoid(curr)
endif
endif
return ids
endfunction
" Clear matches by hlGroup regexp.
function! coc#highlight#clear_match_group(winid, match) abort
let winid = a:winid == 0 ? win_getid() : a:winid
if empty(getwininfo(winid))
" not valid
return
endif
if s:clear_match_by_window
let arr = filter(getmatches(winid), 'v:val["group"] =~# "'.a:match.'"')
for item in arr
call matchdelete(item['id'], winid)
endfor
else
let curr = win_getid()
let switch = exists('*nvim_set_current_win') && curr != winid
if switch
noa call nvim_set_current_win(a:winid)
endif
if win_getid() == winid
let arr = filter(getmatches(), 'v:val["group"] =~# "'.a:match.'"')
for item in arr
call matchdelete(item['id'])
endfor
endif
if switch
noa call nvim_set_current_win(curr)
endif
endif
endfunction
" Clear matches by match ids, use 0 for current win.
function! coc#highlight#clear_matches(winid, ids)
let winid = a:winid == 0 ? win_getid() : a:winid
if empty(getwininfo(winid))
" not valid
return
endif
if s:clear_match_by_window
for id in a:ids
try
call matchdelete(id, winid)
catch /^Vim\%((\a\+)\)\=:E803/
" ignore
endtry
endfor
else
let curr = win_getid()
let switch = exists('*nvim_set_current_win') && curr != winid
if switch
noa call nvim_set_current_win(a:winid)
endif
if win_getid() == winid
for id in a:ids
try
call matchdelete(id)
catch /^Vim\%((\a\+)\)\=:E803/
" ignore
endtry
endfor
endif
if switch
noa call nvim_set_current_win(curr)
endif
endif
endfunction
function! coc#highlight#clear_all() abort
for src_id in values(s:namespace_map)
for bufnr in map(getbufinfo({'bufloaded': 1}), 'v:val["bufnr"]')
if has('nvim')
call nvim_buf_clear_namespace(bufnr, src_id, 0, -1)
else
call coc#api#call('buf_clear_namespace', [bufnr, src_id, 0, -1])
endif
endfor
endfor
endfunction
function! coc#highlight#create_namespace(key) abort
if type(a:key) == 0
return a:key
endif
if has_key(s:namespace_map, a:key)
return s:namespace_map[a:key]
endif
if has('nvim')
let s:namespace_map[a:key] = nvim_create_namespace('coc-'.a:key)
else
let s:namespace_map[a:key] = s:ns_id
let s:ns_id = s:ns_id + 1
endif
return s:namespace_map[a:key]
endfunction
function! coc#highlight#get_syntax_name(lnum, col)
return synIDattr(synIDtrans(synID(a:lnum,a:col,1)),"name")
endfunction
function! s:prop_type_hlgroup(type) abort
if strpart(a:type, 0, 12) ==# 'CocHighlight'
return strpart(a:type, 12)
endif
return get(prop_type_get(a:type), 'highlight', '')
endfunction
function! s:update_highlights_timer(bufnr, changedtick, key, priority, groups, idx) abort
if getbufvar(a:bufnr, 'changedtick', 0) != a:changedtick
return
endif
let group = get(a:groups, a:idx, v:null)
if empty(group)
return
endif
if empty(group['highlights'])
call coc#highlight#clear_highlight(a:bufnr, a:key, group['start'], group['end'])
else
call coc#highlight#update_highlights(a:bufnr, a:key, group['highlights'], group['start'], group['end'], a:priority)
endif
if a:idx < len(a:groups) - 1
call timer_start(50, { -> s:update_highlights_timer(a:bufnr, a:changedtick, a:key, a:priority, a:groups, a:idx + 1)})
endif
endfunction
function! s:add_highlights_timer(bufnr, ns, highlights, priority) abort
let hls = []
let next = []
for i in range(0, len(a:highlights) - 1)
if i < g:coc_highlight_maximum_count
call add(hls, a:highlights[i])
else
call add(next, a:highlights[i])
endif
endfor
call s:add_highlights(a:bufnr, a:ns, hls, a:priority)
if len(next)
call timer_start(30, {->s:add_highlights_timer(a:bufnr, a:ns, next, a:priority)})
endif
endfunction
function! s:add_highlights(bufnr, ns, highlights, priority) abort
for item in a:highlights
let opts = {
\ 'priority': a:priority,
\ 'combine': get(item, 4, 1) ? 1 : 0,
\ 'start_incl': get(item, 5, 0) ? 1 : 0,
\ 'end_incl': get(item, 6, 0) ? 1 : 0,
\ }
call coc#highlight#add_highlight(a:bufnr, a:ns, item[0], item[1], item[2], item[3], opts)
endfor
endfunction
function! s:to_group(items) abort
let res = []
let before = v:null
for item in a:items
if empty(before) || before[0] != item[1]
let before = [item[1], [item]]
call add(res, before)
else
call add(before[1], item)
endif
endfor
return res
endfunction
function! s:get_priority(key, hlGroup, priority) abort
if a:hlGroup ==# 'Search'
return 999
endif
if strpart(a:key, 0, 10) !=# 'diagnostic'
return a:priority
endif
return a:priority - index(s:diagnostic_hlgroups, a:hlGroup)
endfunction
function! s:group_hls(hls, linecount) abort
" start, end, highlights
let groups = []
if empty(a:hls)
call add(groups, {'start': 0, 'end': a:linecount, 'highlights': []})
return groups
endif
let start = 0
let highlights = []
let lastLnum = -1
for item in a:hls
let lnum = item['lnum']
if lnum >= a:linecount
break
endif
if len(highlights) < g:coc_highlight_maximum_count || lnum == lastLnum
call add(highlights, item)
let lastLnum = lnum
else
call add(groups, {'start': start, 'end': lastLnum + 1, 'highlights': highlights})
let highlights = []
let start = lastLnum + 1
call add(highlights, item)
let lastLnum = lnum
endif
endfor
call add(groups, {'start': start, 'end': a:linecount, 'highlights': highlights})
return groups
endfunction

View File

@ -0,0 +1,303 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:prefix = '[List Preview]'
" filetype detect could be slow.
let s:filetype_map = {
\ 'c': 'c',
\ 'py': 'python',
\ 'vim': 'vim',
\ 'ts': 'typescript',
\ 'js': 'javascript',
\ 'html': 'html',
\ 'css': 'css'
\ }
function! coc#list#getchar() abort
return coc#prompt#getchar()
endfunction
function! coc#list#setlines(bufnr, lines, append)
if a:append
silent call appendbufline(a:bufnr, '$', a:lines)
else
if exists('*deletebufline')
silent call deletebufline(a:bufnr, len(a:lines) + 1, '$')
else
let n = len(a:lines) + 1
let saved_reg = @"
silent execute n.',$d'
let @" = saved_reg
endif
silent call setbufline(a:bufnr, 1, a:lines)
endif
endfunction
function! coc#list#options(...)
let list = ['--top', '--tab', '--normal', '--no-sort', '--input', '--strict',
\ '--regex', '--interactive', '--number-select', '--auto-preview',
\ '--ignore-case', '--no-quit', '--first', '--reverse']
if get(g:, 'coc_enabled', 0)
let names = coc#rpc#request('listNames', [])
call extend(list, names)
endif
return join(list, "\n")
endfunction
function! coc#list#names(...) abort
let names = coc#rpc#request('listNames', [])
return join(names, "\n")
endfunction
function! coc#list#status(name)
if !exists('b:list_status') | return '' | endif
return get(b:list_status, a:name, '')
endfunction
function! coc#list#create(position, height, name, numberSelect)
if a:position ==# 'tab'
execute 'silent tabe list:///'.a:name
else
execute 'silent keepalt '.(a:position ==# 'top' ? '' : 'botright').a:height.'sp list:///'.a:name
execute 'resize '.a:height
endif
if a:numberSelect
setl norelativenumber
setl number
else
setl nonumber
setl norelativenumber
setl signcolumn=yes
endif
return [bufnr('%'), win_getid(), tabpagenr()]
endfunction
" close list windows
function! coc#list#clean_up() abort
for i in range(1, winnr('$'))
let bufname = bufname(winbufnr(i))
if bufname =~# 'list://'
execute i.'close!'
endif
endfor
endfunction
function! coc#list#setup(source)
let b:list_status = {}
setl buftype=nofile nobuflisted nofen nowrap
setl norelativenumber bufhidden=wipe cursorline winfixheight
setl tabstop=1 nolist nocursorcolumn undolevels=-1
setl signcolumn=auto
if has('nvim-0.5.0') || has('patch-8.1.0864')
setl scrolloff=0
endif
if exists('&cursorlineopt')
setl cursorlineopt=both
endif
setl filetype=list
syntax case ignore
let source = a:source[8:]
let name = toupper(source[0]).source[1:]
execute 'syntax match Coc'.name.'Line /\v^.*$/'
if !s:is_vim
" Repeat press <C-f> and <C-b> would invoke <esc> on vim
nnoremap <silent><nowait><buffer> <esc> <C-w>c
endif
endfunction
" Check if previewwindow exists on current tab.
function! coc#list#has_preview()
for i in range(1, winnr('$'))
let preview = getwinvar(i, 'previewwindow', getwinvar(i, '&previewwindow', 0))
if preview
return i
endif
endfor
return 0
endfunction
" Get previewwindow from tabnr, use 0 for current tab
function! coc#list#get_preview(...) abort
let tabnr = get(a:, 1, 0) == 0 ? tabpagenr() : a:1
let info = gettabinfo(tabnr)
if !empty(info)
for win in info[0]['windows']
if gettabwinvar(tabnr, win, 'previewwindow', 0)
return win
endif
endfor
endif
return -1
endfunction
function! coc#list#scroll_preview(dir) abort
let winnr = coc#list#has_preview()
if !winnr
return
endif
let winid = win_getid(winnr)
if exists('*win_execute')
call win_execute(winid, "normal! ".(a:dir ==# 'up' ? "\<C-u>" : "\<C-d>"))
else
let id = win_getid()
noa call win_gotoid(winid)
execute "normal! ".(a:dir ==# 'up' ? "\<C-u>" : "\<C-d>")
noa call win_gotoid(id)
endif
endfunction
function! coc#list#close_preview(tabnr) abort
let winid = coc#list#get_preview(a:tabnr)
if winid != -1
call coc#window#close(winid)
endif
endfunction
" Improve preview performance by reused window & buffer.
" lines - list of lines
" config.position - could be 'below' 'top' 'tab'.
" config.winid - id of original window.
" config.name - (optional )name of preview buffer.
" config.splitRight - (optional) split to right when 1.
" config.lnum - (optional) current line number
" config.filetype - (optional) filetype of lines.
" config.hlGroup - (optional) highlight group.
" config.maxHeight - (optional) max height of window, valid for 'below' & 'top' position.
function! coc#list#preview(lines, config) abort
if s:is_vim && !exists('*win_execute')
throw 'win_execute function required for preview, please upgrade your vim.'
return
endif
let name = fnamemodify(get(a:config, 'name', ''), ':.')
let lines = a:lines
if empty(lines)
if get(a:config, 'scheme', 'file') != 'file'
let bufnr = s:load_buffer(name)
if bufnr != 0
let lines = getbufline(bufnr, 1, '$')
else
let lines = ['']
endif
else
" Show empty lines so not close window.
let lines = ['']
endif
endif
let winid = coc#list#get_preview(0)
let bufnr = winid == -1 ? 0 : winbufnr(winid)
" Try reuse buffer & window
let bufnr = coc#float#create_buf(bufnr, lines)
if bufnr == 0
return
endif
call setbufvar(bufnr, '&synmaxcol', 500)
let filetype = get(a:config, 'filetype', '')
let extname = matchstr(name, '\.\zs[^.]\+$')
if empty(filetype) && !empty(extname)
let filetype = get(s:filetype_map, extname, '')
endif
let range = get(a:config, 'range', v:null)
let hlGroup = get(a:config, 'hlGroup', 'Search')
let lnum = get(a:config, 'lnum', 1)
let position = get(a:config, 'position', 'below')
let original = get(a:config, 'winid', -1)
if winid == -1
let change = position != 'tab' && get(a:config, 'splitRight', 0)
let curr = win_getid()
if change
if original && win_id2win(original)
noa call win_gotoid(original)
else
noa wincmd t
endif
execute 'noa belowright vert sb '.bufnr
let winid = win_getid()
elseif position == 'tab' || get(a:config, 'splitRight', 0)
execute 'noa belowright vert sb '.bufnr
let winid = win_getid()
else
let mod = position == 'top' ? 'below' : 'above'
let height = s:get_height(lines, a:config)
execute 'noa '.mod.' sb +resize\ '.height.' '.bufnr
let winid = win_getid()
endif
noa call winrestview({"lnum": lnum ,"topline":s:get_topline(a:config, lnum, winid)})
call setwinvar(winid, '&signcolumn', 'no')
call setwinvar(winid, '&number', 1)
call setwinvar(winid, '&cursorline', 0)
call setwinvar(winid, '&relativenumber', 0)
call setwinvar(winid, 'previewwindow', 1)
noa call win_gotoid(curr)
else
let height = s:get_height(lines, a:config)
if height > 0
if s:is_vim
let curr = win_getid()
noa call win_gotoid(winid)
execute 'silent! noa resize '.height
noa call win_gotoid(curr)
else
call nvim_win_set_height(winid, height)
endif
endif
call coc#compat#execute(winid, ['syntax clear', 'noa call winrestview({"lnum":'.lnum.',"topline":'.s:get_topline(a:config, lnum, winid).'})'])
endif
call setwinvar(winid, '&foldenable', 0)
if s:prefix.' '.name != bufname(bufnr)
if s:is_vim
call win_execute(winid, 'noa file '.fnameescape(s:prefix.' '.name), 'silent!')
else
silent! noa call nvim_buf_set_name(bufnr, s:prefix.' '.name)
endif
endif
" highlights
if !empty(filetype)
let start = max([0, lnum - 300])
let end = min([len(lines), lnum + 300])
call coc#highlight#highlight_lines(winid, [{'filetype': filetype, 'startLine': start, 'endLine': end}])
call coc#compat#execute(winid, 'syn sync fromstart')
else
call coc#compat#execute(winid, 'filetype detect')
let ft = getbufvar(bufnr, '&filetype', '')
if !empty(extname) && !empty(ft)
let s:filetype_map[extname] = ft
endif
endif
call sign_unplace('coc', {'buffer': bufnr})
call coc#compat#execute(winid, 'call clearmatches()')
if !s:is_vim
" vim send <esc> to buffer on FocusLost, <C-w> and other cases
call coc#compat#execute(winid, 'nnoremap <silent><nowait><buffer> <esc> :call CocActionAsync("listCancel")<CR>')
endif
if !empty(range)
call sign_place(1, 'coc', 'CocCurrentLine', bufnr, {'lnum': lnum})
call coc#highlight#match_ranges(winid, bufnr, [range], hlGroup, 10)
endif
redraw
endfunction
function! s:get_height(lines, config) abort
if get(a:config, 'splitRight', 0) || get(a:config, 'position', 'below') == 'tab'
return 0
endif
let height = min([get(a:config, 'maxHeight', 10), len(a:lines), &lines - &cmdheight - 2])
return height
endfunction
function! s:load_buffer(name) abort
if exists('*bufadd') && exists('*bufload')
let bufnr = bufadd(a:name)
call bufload(bufnr)
return bufnr
endif
return 0
endfunction
function! s:get_topline(config, lnum, winid) abort
let toplineStyle = get(a:config, 'toplineStyle', 'offset')
if toplineStyle == 'middle'
return max([1, a:lnum - winheight(a:winid)/2])
endif
let toplineOffset = get(a:config, 'toplineOffset', 3)
return max([1, a:lnum - toplineOffset])
endfunction

View File

@ -0,0 +1,11 @@
" support for float values
function! coc#math#min(first, ...) abort
let val = a:first
for i in range(0, len(a:000) - 1)
if a:000[i] < val
let val = a:000[i]
endif
endfor
return val
endfunction

View File

@ -0,0 +1,531 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:utf = &encoding =~# '^utf'
let s:error_icon = get(g:, 'coc_notify_error_icon', s:utf ? '' : 'E')
let s:warning_icon = get(g:, 'coc_notify_warning_icon', s:utf ? '⚠' : 'W')
let s:info_icon = get(g:, 'coc_notify_info_icon', s:utf ? '' : 'I')
let s:interval = get(g:, 'coc_notify_interval', s:is_vim ? 50 : 20)
let s:phl = 'CocNotificationProgress'
let s:progress_char = '─'
let s:duration = 300.0
let s:winids = []
" Valid notify winids on current tab
function! coc#notify#win_list() abort
call filter(s:winids, 'coc#float#valid(v:val)')
return filter(copy(s:winids), '!empty(getwinvar(v:val,"float"))')
endfunction
function! coc#notify#close_all() abort
for winid in coc#notify#win_list()
call coc#notify#close(winid)
endfor
endfunction
" Do action for winid or first notify window with actions.
function! coc#notify#do_action(...) abort
let winids = a:0 > 0 ? a:000 : coc#notify#win_list()
for winid in winids
if coc#float#valid(winid) && getwinvar(winid, 'closing', 0) != 1
let actions = getwinvar(winid, 'actions', [])
if !empty(actions)
let items = map(copy(actions), '(v:key + 1).". ".v:val')
let msg = join(getbufline(winbufnr(winid), 1, '$'), ' ')
call coc#ui#quickpick(msg, items, {err, res -> s:on_action(err, res, winid) })
break
endif
endif
endfor
endfunction
" Copy notification contents
function! coc#notify#copy() abort
let lines = []
for winid in coc#notify#win_list()
let key = getwinvar(winid, 'key', v:null)
if type(key) == v:t_string
call extend(lines, json_decode(key)['lines'])
endif
endfor
if empty(lines)
echohl WarningMsg | echon 'No content to copy' | echohl None
return
endif
call setreg('*', join(lines, "\n"))
endfunction
" Show source name in window
function! coc#notify#show_sources() abort
if !exists('*getbufline') || !exists('*appendbufline')
throw "getbufline and appendbufline functions required, please upgrade your vim."
endif
let winids = filter(coc#notify#win_list(), 'coc#window#get_var(v:val,"closing") != 1')
for winid in winids
let key = getwinvar(winid, 'key', v:null)
if type(key) == v:t_string
let bufnr = winbufnr(winid)
let obj = json_decode(key)
let sourcename = get(obj, 'source', '')
let lnum = get(obj, 'kind', '') ==# 'progress' ? 1 : 0
let content = get(getbufline(bufnr, lnum + 1), 0, '')
if empty(sourcename) || content ==# sourcename
continue
endif
call appendbufline(bufnr, lnum, sourcename)
call coc#highlight#add_highlight(bufnr, -1, 'Title', lnum, 0, -1)
call coc#float#scroll_win(winid, 0, 1)
endif
endfor
redra
endfunction
function! coc#notify#close_by_source(source) abort
let winids = filter(coc#notify#win_list(), 'coc#window#get_var(v:val,"closing") != 1')
for winid in winids
let key = getwinvar(winid, 'key', v:null)
if type(key) == v:t_string
let obj = json_decode(key)
if get(obj, 'source', '') ==# a:source
call coc#notify#close(winid)
endif
endif
endfor
endfunction
" Cancel auto hide
function! coc#notify#keep() abort
for winid in coc#notify#win_list()
call s:cancel(winid, 'close_timer')
endfor
endfunction
" borderhighlight - border highlight [string]
" maxWidth - max content width, default 60 [number]
" minWidth - minimal width [number]
" maxHeight - max content height, default 10 [number]
" highlight - default highlight [string]
" winblend - winblend [number]
" timeout - auto close timeout, default 5000 [number]
" title - title text
" marginRight - margin right, default 10 [number]
" focusable - focusable [number]
" source - source name [string]
" kind - kind for create icon [string]
" actions - action names [string[]]
function! coc#notify#create(lines, config) abort
let actions = get(a:config, 'actions', [])
let key = json_encode(extend({'lines': a:lines}, a:config))
let winid = s:find_win(key)
let kind = get(a:config, 'kind', '')
let row = 0
if winid != -1
let row = getwinvar(winid, 'top', 0)
call filter(s:winids, 'v:val != '.winid)
call coc#float#close(winid)
let winid = v:null
endif
let opts = coc#dict#pick(a:config, ['highlight', 'borderhighlight', 'focusable', 'shadow'])
let border = has_key(opts, 'borderhighlight') ? [1, 1, 1, 1] : []
let icon = s:get_icon(kind, get(a:config, 'highlight', 'CocFloating'))
let margin = get(a:config, 'marginRight', 10)
let maxWidth = min([&columns - margin - 2, get(a:config, 'maxWidth', 80)])
if maxWidth <= 0
throw 'No enough spaces for notification'
endif
let lines = map(copy(a:lines), 'tr(v:val, "\t", " ")')
if has_key(a:config, 'title')
if !empty(border)
let opts['title'] = a:config['title']
else
let lines = [a:config['title']] + lines
endif
endif
let width = max(map(copy(lines), 'strwidth(v:val)')) + (empty(icon) ? 1 : 3)
if width > maxWidth
let lines = coc#string#reflow(lines, maxWidth)
let width = max(map(copy(lines), 'strwidth(v:val)')) + (empty(icon) ? 1 : 3)
endif
let highlights = []
if !empty(icon)
let ic = icon['text']
if empty(lines)
call add(lines, ic)
else
let lines[0] = ic.' '.lines[0]
endif
call add(highlights, {'lnum': 0, 'hlGroup': icon['hl'], 'colStart': 0, 'colEnd': strlen(ic)})
endif
let actionText = join(actions, ' ')
call map(lines, 'v:key == 0 ? v:val : repeat(" ", '.(empty(icon) ? 0 : 2).').v:val')
let minWidth = get(a:config, 'minWidth', kind ==# 'progress' ? 30 : 10)
let width = max(extend(map(lines + [get(opts, 'title', '').' '], 'strwidth(v:val)'), [minWidth, strwidth(actionText) + 1]))
let width = min([maxWidth, width])
let height = min([get(a:config, 'maxHeight', 3), len(lines)])
if kind ==# 'progress'
let lines = [repeat(s:progress_char, width)] + lines
let height = height + 1
endif
if !empty(actions)
let before = width - strwidth(actionText)
let lines = lines + [repeat(' ', before).actionText]
let height = height + 1
call s:add_action_highlights(before, height - 1, highlights, actions)
endif
if row == 0
let wintop = coc#notify#get_top()
let row = wintop - height - (empty(border) ? 0 : 2) - 1
if !s:is_vim && !empty(border)
let row = row + 1
endif
endif
let col = &columns - margin - width
if s:is_vim && !empty(border)
let col = col - 2
endif
let winblend = 60
" Avoid animate for transparent background.
if get(a:config, 'winblend', 30) == 0 && empty(synIDattr(synIDtrans(hlID(get(opts, 'highlight', 'CocFloating'))), 'bg', 'gui'))
let winblend = 0
endif
call extend(opts, {
\ 'relative': 'editor',
\ 'width': width,
\ 'height': height,
\ 'col': col,
\ 'row': row + 1,
\ 'lines': lines,
\ 'rounded': 1,
\ 'highlights': highlights,
\ 'winblend': winblend,
\ 'border': border,
\ })
let result = coc#float#create_float_win(0, 0, opts)
if empty(result)
throw 'Unable to create notify window'
endif
let winid = result[0]
let bufnr = result[1]
call setwinvar(winid, 'right', 1)
call setwinvar(winid, 'kind', 'notification')
call setwinvar(winid, 'top', row)
call setwinvar(winid, 'key', key)
call setwinvar(winid, 'actions', actions)
call setwinvar(winid, 'source', get(a:config, 'source', ''))
call setwinvar(winid, 'border', !empty(border))
call coc#float#nvim_scrollbar(winid)
call add(s:winids, winid)
let from = {'row': opts['row'], 'winblend': opts['winblend']}
let to = {'row': row, 'winblend': get(a:config, 'winblend', 30)}
call timer_start(s:interval, { -> s:animate(winid, from, to, 0)})
if kind ==# 'progress'
call timer_start(s:interval, { -> s:progress(winid, width, 0, -1)})
endif
if !s:is_vim
call coc#compat#buf_add_keymap(bufnr, 'n', '<LeftRelease>', ':call coc#notify#nvim_click('.winid.')<CR>', {
\ 'silent': v:true,
\ 'nowait': v:true
\ })
endif
" Enable auto close
if empty(actions) && kind !=# 'progress'
let timer = timer_start(get(a:config, 'timeout', 10000), { -> coc#notify#close(winid)})
call setwinvar(winid, 'close_timer', timer)
endif
return [winid, bufnr]
endfunction
function! coc#notify#nvim_click(winid) abort
if getwinvar(a:winid, 'closing', 0)
return
endif
call s:cancel(a:winid, 'close_timer')
let actions = getwinvar(a:winid, 'actions', [])
if !empty(actions)
let character = strpart(getline('.'), col('.') - 1, 1)
if character =~# '^\k'
let word = expand('<cword>')
let idx = index(actions, word)
if idx != -1
call coc#rpc#notify('FloatBtnClick', [winbufnr(a:winid), idx])
call coc#notify#close(a:winid)
endif
endif
endif
endfunction
function! coc#notify#on_close(winid) abort
if index(s:winids, a:winid) >= 0
call filter(s:winids, 'v:val != '.a:winid)
call coc#notify#reflow()
endif
endfunction
function! coc#notify#get_top() abort
let mintop = min(map(coc#notify#win_list(), 'coc#notify#get_win_top(v:val)'))
if mintop != 0
return mintop
endif
return &lines - &cmdheight - (&laststatus == 0 ? 0 : 1 )
endfunction
function! coc#notify#get_win_top(winid) abort
let row = getwinvar(a:winid, 'top', 0)
if row == 0
return row
endif
return row - (s:is_vim ? 0 : getwinvar(a:winid, 'border', 0))
endfunction
" Close with timer
function! coc#notify#close(winid) abort
if !coc#float#valid(a:winid) || coc#window#get_var(a:winid, 'closing', 0) == 1
return
endif
if !coc#window#visible(a:winid)
call coc#float#close(a:winid)
return
endif
let row = coc#window#get_var(a:winid, 'top')
if type(row) != v:t_number
call coc#float#close(a:winid)
return
endif
call coc#window#set_var(a:winid, 'closing', 1)
call s:cancel(a:winid)
let curr = s:is_vim ? {'row': row} : {'winblend': coc#window#get_var(a:winid, 'winblend', 30)}
let dest = s:is_vim ? {'row': row + 1} : {'winblend': 60}
call s:animate(a:winid, curr, dest, 0, 1)
endfunction
function! s:add_action_highlights(before, lnum, highlights, actions) abort
let colStart = a:before
for text in a:actions
let w = strwidth(text)
call add(a:highlights, {
\ 'lnum': a:lnum,
\ 'hlGroup': 'CocNotificationButton',
\ 'colStart': colStart,
\ 'colEnd': colStart + w
\ })
let colStart = colStart + w + 1
endfor
endfunction
function! s:on_action(err, idx, winid) abort
if !empty(a:err)
throw a:err
endif
if a:idx > 0
call coc#rpc#notify('FloatBtnClick', [winbufnr(a:winid), a:idx - 1])
call coc#notify#close(a:winid)
endif
endfunction
function! s:cancel(winid, ...) abort
let name = get(a:, 1, 'timer')
let timer = coc#window#get_var(a:winid, name)
if !empty(timer)
call timer_stop(timer)
call coc#window#set_var(a:winid, name, v:null)
endif
endfunction
function! s:progress(winid, total, curr, index) abort
if !coc#float#valid(a:winid)
return
endif
if coc#window#visible(a:winid)
let total = a:total
let idx = float2nr(a:curr/5.0)%total
if idx != a:index
" update percent
let bufnr = winbufnr(a:winid)
let percent = coc#window#get_var(a:winid, 'percent')
if !empty(percent)
let width = strchars(getbufline(bufnr, 1)[0])
let line = repeat(s:progress_char, width - 4).printf('%4s', percent)
let total = width - 4
call setbufline(bufnr, 1, line)
endif
let message = coc#window#get_var(a:winid, 'message')
if !empty(message)
let linecount = coc#compat#buf_line_count(bufnr)
let hasAction = !empty(coc#window#get_var(a:winid, 'actions', []))
if getbufvar(bufnr, 'message', 0) == 0
call appendbufline(bufnr, linecount - hasAction, message)
call setbufvar(bufnr, 'message', 1)
call coc#float#change_height(a:winid, 1)
let tabnr = coc#window#tabnr(a:winid)
call coc#notify#reflow(tabnr)
else
call setbufline(bufnr, linecount - hasAction, message)
endif
endif
let bytes = strlen(s:progress_char)
call coc#highlight#clear_highlight(bufnr, -1, 0, 1)
let colStart = bytes * idx
if idx + 4 <= total
let colEnd = bytes * (idx + 4)
call coc#highlight#add_highlight(bufnr, -1, s:phl, 0, colStart, colEnd)
else
let colEnd = bytes * total
call coc#highlight#add_highlight(bufnr, -1, s:phl, 0, colStart, colEnd)
call coc#highlight#add_highlight(bufnr, -1, s:phl, 0, 0, bytes * (idx + 4 - total))
endif
endif
call timer_start(s:interval, { -> s:progress(a:winid, total, a:curr + 1, idx)})
else
" Not block CursorHold event
call timer_start(&updatetime + 100, { -> s:progress(a:winid, a:total, a:curr, a:index)})
endif
endfunction
" Optional row & winblend
function! s:config_win(winid, props) abort
let change_row = has_key(a:props, 'row')
if s:is_vim
if change_row
call popup_move(a:winid, {'line': a:props['row'] + 1})
endif
else
if change_row
let [row, column] = nvim_win_get_position(a:winid)
call nvim_win_set_config(a:winid, {
\ 'row': a:props['row'],
\ 'col': column,
\ 'relative': 'editor',
\ })
call s:nvim_move_related(a:winid, a:props['row'])
endif
call coc#float#nvim_set_winblend(a:winid, get(a:props, 'winblend', v:null))
call coc#float#nvim_refresh_scrollbar(a:winid)
endif
endfunction
function! s:nvim_move_related(winid, row) abort
let winids = coc#window#get_var(a:winid, 'related')
if empty(winids)
return
endif
for winid in winids
if nvim_win_is_valid(winid)
let [row, column] = nvim_win_get_position(winid)
let delta = coc#window#get_var(winid, 'delta', 0)
call nvim_win_set_config(winid, {
\ 'row': a:row + delta,
\ 'col': column,
\ 'relative': 'editor',
\ })
endif
endfor
endfunction
function! s:animate(winid, from, to, prev, ...) abort
if !coc#float#valid(a:winid)
return
endif
let curr = a:prev + s:interval
let percent = coc#math#min(curr / s:duration, 1)
let props = s:get_props(a:from, a:to, percent)
call s:config_win(a:winid, props)
let close = get(a:, 1, 0)
if percent < 1
call timer_start(s:interval, { -> s:animate(a:winid, a:from, a:to, curr, close)})
elseif close
call filter(s:winids, 'v:val != '.a:winid)
let tabnr = coc#window#tabnr(a:winid)
if tabnr != -1
call coc#float#close(a:winid)
call coc#notify#reflow(tabnr)
endif
endif
endfunction
function! coc#notify#reflow(...) abort
let tabnr = get(a:, 1, tabpagenr())
let winids = filter(copy(s:winids), 'coc#window#tabnr(v:val) == '.tabnr.' && coc#window#get_var(v:val,"closing") != 1')
if empty(winids)
return
endif
let animate = tabnr == tabpagenr()
let wins = map(copy(winids), {_, val -> {
\ 'winid': val,
\ 'row': coc#window#get_var(val,'top',0),
\ 'top': coc#window#get_var(val,'top',0) - (s:is_vim ? 0 : coc#window#get_var(val, 'border', 0)),
\ 'height': coc#float#get_height(val),
\ }})
call sort(wins, {a, b -> b['top'] - a['top']})
let bottom = &lines - &cmdheight - (&laststatus == 0 ? 0 : 1 )
let moved = 0
for item in wins
let winid = item['winid']
let delta = bottom - (item['top'] + item['height'] + 1)
if delta != 0
call s:cancel(winid)
let dest = item['row'] + delta
call coc#window#set_var(winid, 'top', dest)
if animate
call s:move_win_timer(winid, {'row': item['row']}, {'row': dest}, 0)
else
call s:config_win(winid, {'row': dest})
endif
let moved = moved + delta
endif
let bottom = item['top'] + delta
endfor
endfunction
function! s:move_win_timer(winid, from, to, curr) abort
if !coc#float#valid(a:winid)
return
endif
if coc#window#get_var(a:winid, 'closing', 0) == 1
return
endif
let percent = coc#math#min(a:curr / s:duration, 1)
let next = a:curr + s:interval
if a:curr > 0
call s:config_win(a:winid, s:get_props(a:from, a:to, percent))
endif
if percent < 1
let timer = timer_start(s:interval, { -> s:move_win_timer(a:winid, a:from, a:to, next)})
call coc#window#set_var(a:winid, 'timer', timer)
endif
endfunction
function! s:find_win(key) abort
for winid in coc#notify#win_list()
if getwinvar(winid, 'key', '') ==# a:key
return winid
endif
endfor
return -1
endfunction
function! s:get_icon(kind, bg) abort
if a:kind ==# 'info'
return {'text': s:info_icon, 'hl': coc#highlight#compose_hlgroup('CocInfoSign', a:bg)}
endif
if a:kind ==# 'warning'
return {'text': s:warning_icon, 'hl': coc#highlight#compose_hlgroup('CocWarningSign', a:bg)}
endif
if a:kind ==# 'error'
return {'text': s:error_icon, 'hl': coc#highlight#compose_hlgroup('CocErrorSign', a:bg)}
endif
return v:null
endfunction
" percent should be float
function! s:get_props(from, to, percent) abort
let obj = {}
for key in keys(a:from)
let changed = a:to[key] - a:from[key]
if !s:is_vim && key ==# 'row'
" Could be float
let obj[key] = a:from[key] + changed * a:percent
else
let obj[key] = a:from[key] + float2nr(round(changed * a:percent))
endif
endfor
return obj
endfunction

View File

@ -0,0 +1,214 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:activated = 0
let s:session_names = []
let s:saved_ve = &t_ve
let s:saved_cursor = &guicursor
let s:gui = has('gui_running') || has('nvim')
let s:char_map = {
\ "\<Plug>": '<plug>',
\ "\<Esc>": '<esc>',
\ "\<Tab>": '<tab>',
\ "\<S-Tab>": '<s-tab>',
\ "\<bs>": '<bs>',
\ "\<right>": '<right>',
\ "\<left>": '<left>',
\ "\<up>": '<up>',
\ "\<down>": '<down>',
\ "\<home>": '<home>',
\ "\<end>": '<end>',
\ "\<cr>": '<cr>',
\ "\<PageUp>":'<PageUp>' ,
\ "\<PageDown>":'<PageDown>' ,
\ "\<FocusGained>":'<FocusGained>',
\ "\<FocusLost>":'<FocusLost>',
\ "\<ScrollWheelUp>": '<ScrollWheelUp>',
\ "\<ScrollWheelDown>": '<ScrollWheelDown>',
\ "\<LeftMouse>": '<LeftMouse>',
\ "\<LeftDrag>": '<LeftDrag>',
\ "\<LeftRelease>": '<LeftRelease>',
\ "\<2-LeftMouse>": '<2-LeftMouse>',
\ "\<C-a>": '<C-a>',
\ "\<C-b>": '<C-b>',
\ "\<C-c>": '<C-c>',
\ "\<C-d>": '<C-d>',
\ "\<C-e>": '<C-e>',
\ "\<C-f>": '<C-f>',
\ "\<C-g>": '<C-g>',
\ "\<C-h>": '<C-h>',
\ "\<C-j>": '<C-j>',
\ "\<C-k>": '<C-k>',
\ "\<C-l>": '<C-l>',
\ "\<C-n>": '<C-n>',
\ "\<C-o>": '<C-o>',
\ "\<C-p>": '<C-p>',
\ "\<C-q>": '<C-q>',
\ "\<C-r>": '<C-r>',
\ "\<C-s>": '<C-s>',
\ "\<C-t>": '<C-t>',
\ "\<C-u>": '<C-u>',
\ "\<C-v>": '<C-v>',
\ "\<C-w>": '<C-w>',
\ "\<C-x>": '<C-x>',
\ "\<C-y>": '<C-y>',
\ "\<C-z>": '<C-z>',
\ "\<A-a>": '<A-a>',
\ "\<A-b>": '<A-b>',
\ "\<A-c>": '<A-c>',
\ "\<A-d>": '<A-d>',
\ "\<A-e>": '<A-e>',
\ "\<A-f>": '<A-f>',
\ "\<A-g>": '<A-g>',
\ "\<A-h>": '<A-h>',
\ "\<A-i>": '<A-i>',
\ "\<A-j>": '<A-j>',
\ "\<A-k>": '<A-k>',
\ "\<A-l>": '<A-l>',
\ "\<A-m>": '<A-m>',
\ "\<A-n>": '<A-n>',
\ "\<A-o>": '<A-o>',
\ "\<A-p>": '<A-p>',
\ "\<A-q>": '<A-q>',
\ "\<A-r>": '<A-r>',
\ "\<A-s>": '<A-s>',
\ "\<A-t>": '<A-t>',
\ "\<A-u>": '<A-u>',
\ "\<A-v>": '<A-v>',
\ "\<A-w>": '<A-w>',
\ "\<A-x>": '<A-x>',
\ "\<A-y>": '<A-y>',
\ "\<A-z>": '<A-z>',
\ }
function! coc#prompt#getc() abort
let c = getchar()
return type(c) is 0 ? nr2char(c) : c
endfunction
function! coc#prompt#getchar() abort
let input = coc#prompt#getc()
if 1 != &iminsert
return input
endif
"a language keymap is activated, so input must be resolved to the mapped values.
let partial_keymap = mapcheck(input, 'l')
while partial_keymap !=# ''
let dict = maparg(input, 'l', 0, 1)
if empty(dict) || get(dict, 'expr', 0)
return input
endif
let full_keymap = get(dict, 'rhs', '')
if full_keymap ==# "" && len(input) >= 3 "HACK: assume there are no keymaps longer than 3.
return input
elseif full_keymap ==# partial_keymap
return full_keymap
endif
let c = coc#prompt#getc()
if c ==# "\<Esc>" || c ==# "\<CR>"
"if the short sequence has a valid mapping, return that.
if !empty(full_keymap)
return full_keymap
endif
return input
endif
let input .= c
let partial_keymap = mapcheck(input, 'l')
endwhile
return input
endfunction
function! coc#prompt#start_prompt(session) abort
let s:session_names = s:filter(s:session_names, a:session)
call add(s:session_names, a:session)
if s:activated | return | endif
if s:is_vim
call s:start_prompt_vim()
else
call s:start_prompt()
endif
endfunction
function! s:start_prompt_vim() abort
call timer_start(10, {-> s:start_prompt()})
endfunction
function! s:start_prompt()
if s:activated | return | endif
if !get(g:, 'coc_disable_transparent_cursor', 0)
if s:gui
if has('nvim-0.5.0') && !empty(s:saved_cursor)
set guicursor+=a:ver1-CocCursorTransparent/lCursor
endif
elseif s:is_vim
set t_ve=
endif
endif
let s:activated = 1
try
while s:activated
let ch = coc#prompt#getchar()
if ch ==# "\<FocusLost>" || ch ==# "\<FocusGained>" || ch ==# "\<CursorHold>"
continue
else
let curr = s:current_session()
let mapped = get(s:char_map, ch, ch)
if !empty(curr)
call coc#rpc#notify('InputChar', [curr, mapped, getcharmod()])
endif
if mapped == '<esc>'
let s:session_names = []
call s:reset()
break
endif
endif
endwhile
catch /^Vim:Interrupt$/
let s:activated = 0
call coc#rpc#notify('InputChar', [s:current_session(), '<esc>'])
return
endtry
let s:activated = 0
endfunction
function! coc#prompt#stop_prompt(session)
let s:session_names = s:filter(s:session_names, a:session)
if len(s:session_names)
return
endif
if s:activated
let s:activated = 0
call s:reset()
call feedkeys("\<esc>", 'int')
endif
endfunction
function! coc#prompt#activated() abort
return s:activated
endfunction
function! s:reset() abort
if !get(g:, 'coc_disable_transparent_cursor',0)
" neovim has bug with revert empty &guicursor
if s:gui && !empty(s:saved_cursor)
if has('nvim-0.5.0')
set guicursor+=a:ver1-Cursor/lCursor
let &guicursor = s:saved_cursor
endif
elseif s:is_vim
let &t_ve = s:saved_ve
endif
endif
echo ""
endfunction
function! s:current_session() abort
if empty(s:session_names)
return v:null
endif
return s:session_names[len(s:session_names) - 1]
endfunction
function! s:filter(list, id) abort
return filter(copy(a:list), 'v:val !=# a:id')
endfunction

View File

@ -0,0 +1,129 @@
scriptencoding utf-8
let s:is_win = has("win32") || has("win64")
let s:client = v:null
let s:name = 'coc'
let s:is_vim = !has('nvim')
function! coc#rpc#start_server()
if get(g:, 'coc_node_env', '') ==# 'test'
" server already started
let s:client = coc#client#create(s:name, [])
let chan_id = get(g:, 'coc_node_channel_id', 0)
let s:client['running'] = chan_id != 0
let s:client['chan_id'] = chan_id
return
endif
if empty(s:client)
let cmd = coc#util#job_command()
if empty(cmd) | return | endif
let $COC_VIMCONFIG = coc#util#get_config_home()
let $COC_DATA_HOME = coc#util#get_data_home()
let s:client = coc#client#create(s:name, cmd)
endif
if !coc#client#is_running('coc')
call s:client['start']()
endif
endfunction
function! coc#rpc#started() abort
return !empty(s:client)
endfunction
function! coc#rpc#ready()
if empty(s:client) || s:client['running'] == 0
return 0
endif
return 1
endfunction
function! coc#rpc#set_channel(chan_id) abort
let g:coc_node_channel_id = a:chan_id
if a:chan_id != 0
let s:client['running'] = 1
let s:client['chan_id'] = a:chan_id
endif
endfunction
function! coc#rpc#kill()
let pid = get(g:, 'coc_process_pid', 0)
if !pid | return | endif
if s:is_win
call system('taskkill /PID '.pid)
else
call system('kill -9 '.pid)
endif
endfunction
function! coc#rpc#get_errors()
return split(execute('messages'), "\n")
endfunction
function! coc#rpc#stop()
if empty(s:client)
return
endif
try
if s:is_vim
call job_stop(ch_getjob(s:client['channel']), 'term')
else
call jobstop(s:client['chan_id'])
endif
catch /.*/
" ignore
endtry
endfunction
function! coc#rpc#restart()
if empty(s:client)
call coc#rpc#start_server()
else
call coc#highlight#clear_all()
call coc#float#close_all()
call coc#rpc#request('detach', [])
sleep 100m
let s:client['command'] = coc#util#job_command()
call coc#client#restart(s:name)
echohl MoreMsg | echom 'starting coc.nvim service' | echohl None
endif
endfunction
function! coc#rpc#request(method, args) abort
if !coc#rpc#ready()
return ''
endif
return s:client['request'](a:method, a:args)
endfunction
function! coc#rpc#notify(method, args) abort
if !coc#rpc#ready()
return ''
endif
call s:client['notify'](a:method, a:args)
return ''
endfunction
function! coc#rpc#request_async(method, args, cb) abort
if !coc#rpc#ready()
return cb('coc.nvim service not started.')
endif
call s:client['request_async'](a:method, a:args, a:cb)
endfunction
" receive async response
function! coc#rpc#async_response(id, resp, isErr) abort
if empty(s:client)
return
endif
call coc#client#on_response(s:name, a:id, a:resp, a:isErr)
endfunction
" send async response to server
function! coc#rpc#async_request(id, method, args)
let l:Cb = {err, ... -> coc#rpc#notify('nvim_async_response_event', [a:id, err, get(a:000, 0, v:null)])}
let args = a:args + [l:Cb]
try
call call(a:method, args)
catch /.*/
call coc#rpc#notify('nvim_async_response_event', [a:id, v:exception, v:null])
endtry
endfunction

View File

@ -0,0 +1,146 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:map_next = 1
let s:map_prev = 1
let s:cmd_mapping = has('nvim') || has('patch-8.2.1978')
function! coc#snippet#_select_mappings()
if !get(g:, 'coc_selectmode_mapping', 1)
return
endif
redir => mappings
silent! smap
redir END
for map in map(filter(split(mappings, '\n'),
\ "v:val !~# '^s' && v:val !~# '^\\a*\\s*<\\S\\+>'"),
\ "matchstr(v:val, '^\\a*\\s*\\zs\\S\\+')")
silent! execute 'sunmap' map
silent! execute 'sunmap <buffer>' map
endfor
" same behaviour of ultisnips
snoremap <silent> <BS> <c-g>c
snoremap <silent> <DEL> <c-g>c
snoremap <silent> <c-h> <c-g>c
snoremap <c-r> <c-g>"_c<c-r>
endfunction
function! coc#snippet#show_choices(lnum, col, len, values) abort
let m = mode()
call cursor(a:lnum, a:col + a:len)
if m !=# 'i'
call feedkeys("\<Esc>i", 'in')
endif
let changedtick = b:changedtick
call timer_start(20, { -> coc#_do_complete(a:col - 1, a:values, 0, changedtick)})
redraw
endfunction
function! coc#snippet#enable(...)
if get(b:, 'coc_snippet_active', 0) == 1
return
endif
let complete = get(a:, 1, 0)
let b:coc_snippet_active = 1
call coc#snippet#_select_mappings()
let nextkey = get(g:, 'coc_snippet_next', '<C-j>')
let prevkey = get(g:, 'coc_snippet_prev', '<C-k>')
if maparg(nextkey, 'i') =~# 'snippet'
let s:map_next = 0
endif
if maparg(prevkey, 'i') =~# 'snippet'
let s:map_prev = 0
endif
if !empty(nextkey)
if s:map_next
execute 'inoremap <buffer><nowait><silent>'.nextkey." <C-R>=coc#snippet#jump(1, ".complete.")<cr>"
endif
execute 'snoremap <buffer><nowait><silent>'.nextkey." <Esc>:call coc#snippet#jump(1, ".complete.")<cr>"
endif
if !empty(prevkey)
if s:map_prev
execute 'inoremap <buffer><nowait><silent>'.prevkey." <C-R>=coc#snippet#jump(0, ".complete.")<cr>"
endif
execute 'snoremap <buffer><nowait><silent>'.prevkey." <Esc>:call coc#snippet#jump(0, ".complete.")<cr>"
endif
endfunction
function! coc#snippet#prev() abort
call coc#rpc#request('snippetPrev', [])
return ''
endfunction
function! coc#snippet#next() abort
call coc#rpc#request('snippetNext', [])
return ''
endfunction
function! coc#snippet#jump(direction, complete) abort
if a:direction == 1 && a:complete && pumvisible()
let pre = exists('*complete_info') && complete_info()['selected'] == -1 ? "\<C-n>" : ''
call feedkeys(pre."\<C-y>", 'in')
return ''
endif
call coc#rpc#request(a:direction == 1 ? 'snippetNext' : 'snippetPrev', [])
return ''
endfunction
function! coc#snippet#disable()
if get(b:, 'coc_snippet_active', 0) == 0
return
endif
let b:coc_snippet_active = 0
let nextkey = get(g:, 'coc_snippet_next', '<C-j>')
let prevkey = get(g:, 'coc_snippet_prev', '<C-k>')
if s:map_next
silent! execute 'iunmap <buffer> <silent> '.nextkey
endif
if s:map_prev
silent! execute 'iunmap <buffer> <silent> '.prevkey
endif
silent! execute 'sunmap <buffer> <silent> '.prevkey
silent! execute 'sunmap <buffer> <silent> '.nextkey
endfunction
function! coc#snippet#select(position, text) abort
if pumvisible()
call coc#_cancel()
endif
if mode() == 's'
call feedkeys("\<Esc>", 'in')
endif
let cursor = coc#snippet#to_cursor(a:position)
call cursor([cursor[0], cursor[1] - (&selection !~# 'exclusive')])
let len = strchars(a:text) - (&selection !~# 'exclusive')
let cmd = ''
let cmd .= mode()[0] ==# 'i' ? "\<Esc>l" : ''
let cmd .= printf('v%s', len > 0 ? len . 'h' : '')
let cmd .= "o\<C-g>"
call feedkeys(cmd, 'n')
endfunction
function! coc#snippet#move(position) abort
let m = mode()
if m == 's'
call feedkeys("\<Esc>", 'in')
elseif pumvisible()
call coc#_cancel()
endif
let pos = coc#snippet#to_cursor(a:position)
call cursor(pos)
if pos[1] > strlen(getline(pos[0]))
startinsert!
else
startinsert
endif
endfunction
function! coc#snippet#to_cursor(position) abort
let line = getline(a:position.line + 1)
if line is v:null
return [a:position.line + 1, a:position.character + 1]
endif
return [a:position.line + 1, byteidx(line, a:position.character) + 1]
endfunction

View File

@ -0,0 +1,125 @@
scriptencoding utf-8
function! coc#string#get_character(line, col) abort
return strchars(strpart(a:line, 0, a:col - 1))
endfunction
function! coc#string#last_character(line) abort
return strcharpart(a:line, strchars(a:line) - 1, 1)
endfunction
function! coc#string#reflow(lines, width) abort
let lines = []
let currlen = 0
let parts = []
for line in a:lines
for part in split(line, '\s\+')
let w = strwidth(part)
if currlen + w + 1 >= a:width
if len(parts) > 0
call add(lines, join(parts, ' '))
endif
if w >= a:width
call add(lines, part)
let currlen = 0
let parts = []
else
let currlen = w
let parts = [part]
endif
continue
endif
call add(parts, part)
let currlen = currlen + w + 1
endfor
endfor
if len(parts) > 0
call add(lines, join(parts, ' '))
endif
return empty(lines) ? [''] : lines
endfunction
" get change between two lines
function! coc#string#diff(curr, previous, col) abort
let end = strpart(a:curr, a:col - 1)
let start = strpart(a:curr, 0, a:col -1)
let endOffset = 0
let startOffset = 0
let currLen = strchars(a:curr)
let prevLen = strchars(a:previous)
if len(end)
let endLen = strchars(end)
for i in range(min([prevLen, endLen]))
if strcharpart(end, endLen - 1 - i, 1) ==# strcharpart(a:previous, prevLen -1 -i, 1)
let endOffset = endOffset + 1
else
break
endif
endfor
endif
let remain = endOffset == 0 ? a:previous : strcharpart(a:previous, 0, prevLen - endOffset)
if len(remain)
for i in range(min([strchars(remain), strchars(start)]))
if strcharpart(remain, i, 1) ==# strcharpart(start, i ,1)
let startOffset = startOffset + 1
else
break
endif
endfor
endif
return {
\ 'start': startOffset,
\ 'end': prevLen - endOffset,
\ 'text': strcharpart(a:curr, startOffset, currLen - startOffset - endOffset)
\ }
endfunction
function! coc#string#apply(content, diff) abort
let totalLen = strchars(a:content)
let endLen = totalLen - a:diff['end']
return strcharpart(a:content, 0, a:diff['start']).a:diff['text'].strcharpart(a:content, a:diff['end'], endLen)
endfunction
" insert inserted to line at position, use ... when result is too long
" line should only contains character has strwidth equals 1
function! coc#string#compose(line, position, inserted) abort
let width = strwidth(a:line)
let text = a:inserted
let res = a:line
let need_truncate = a:position + strwidth(text) + 1 > width
if need_truncate
let remain = width - a:position - 3
if remain < 2
" use text for full line, use first & end of a:line, ignore position
let res = strcharpart(a:line, 0, 1)
let w = strwidth(res)
for i in range(strchars(text))
let c = strcharpart(text, i, 1)
let a = strwidth(c)
if w + a <= width - 1
let w = w + a
let res = res . c
endif
endfor
let res = res.strcharpart(a:line, w)
else
let res = strcharpart(a:line, 0, a:position)
let w = strwidth(res)
for i in range(strchars(text))
let c = strcharpart(text, i, 1)
let a = strwidth(c)
if w + a <= width - 3
let w = w + a
let res = res . c
endif
endfor
let res = res.'..'
let w = w + 2
let res = res . strcharpart(a:line, w)
endif
else
let first = strcharpart(a:line, 0, a:position)
let res = first . text . strcharpart(a:line, a:position + strwidth(text))
endif
return res
endfunction

View File

@ -0,0 +1,179 @@
" ============================================================================
" Description: Manage long running tasks.
" Author: Qiming Zhao <chemzqm@gmail.com>
" Licence: Anti 966 licence
" Version: 0.1
" Last Modified: Dec 12, 2020
" ============================================================================
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:running_task = {}
" neovim emit strings that part of lines.
let s:out_remain_text = {}
let s:err_remain_text = {}
function! coc#task#start(id, opts)
if coc#task#running(a:id)
call coc#task#stop(a:id)
endif
let cmd = [a:opts['cmd']] + get(a:opts, 'args', [])
let cwd = get(a:opts, 'cwd', getcwd())
let env = get(a:opts, 'env', {})
" cmd args cwd pty
if s:is_vim
let options = {
\ 'cwd': cwd,
\ 'err_mode': 'nl',
\ 'out_mode': 'nl',
\ 'err_cb': {channel, message -> s:on_stderr(a:id, [message])},
\ 'out_cb': {channel, message -> s:on_stdout(a:id, [message])},
\ 'exit_cb': {channel, code -> s:on_exit(a:id, code)},
\ 'env': env,
\}
if has("patch-8.1.350")
let options['noblock'] = 1
endif
if get(a:opts, 'pty', 0)
let options['pty'] = 1
endif
let job = job_start(cmd, options)
let status = job_status(job)
if status !=# 'run'
echohl Error | echom 'Failed to start '.a:id.' task' | echohl None
return v:false
endif
let s:running_task[a:id] = job
else
let options = {
\ 'cwd': cwd,
\ 'on_stderr': {channel, msgs -> s:on_stderr(a:id, msgs)},
\ 'on_stdout': {channel, msgs -> s:on_stdout(a:id, msgs)},
\ 'on_exit': {channel, code -> s:on_exit(a:id, code)},
\ 'detach': get(a:opts, 'detach', 0),
\}
let original = {}
if !empty(env)
if has('nvim-0.5.0')
let options['env'] = env
elseif exists('*setenv') && exists('*getenv')
for key in keys(env)
let original[key] = getenv(key)
call setenv(key, env[key])
endfor
endif
endif
if get(a:opts, 'pty', 0)
let options['pty'] = 1
endif
let chan_id = jobstart(cmd, options)
if !empty(original)
for key in keys(original)
call setenv(key, original[key])
endfor
endif
if chan_id <= 0
echohl Error | echom 'Failed to start '.a:id.' task' | echohl None
return v:false
endif
let s:running_task[a:id] = chan_id
endif
return v:true
endfunction
function! coc#task#stop(id)
let job = get(s:running_task, a:id, v:null)
if !job | return | endif
if s:is_vim
call job_stop(job, 'term')
else
call jobstop(job)
endif
sleep 50m
let running = coc#task#running(a:id)
if running
echohl Error | echom 'job '.a:id. ' stop failed.' | echohl None
endif
endfunction
function! s:on_exit(id, code) abort
if get(g:, 'coc_vim_leaving', 0) | return | endif
if has('nvim')
let s:out_remain_text[a:id] = ''
let s:err_remain_text[a:id] = ''
endif
if has_key(s:running_task, a:id)
call remove(s:running_task, a:id)
endif
call coc#rpc#notify('TaskExit', [a:id, a:code])
endfunction
function! s:on_stderr(id, msgs)
if get(g:, 'coc_vim_leaving', 0) | return | endif
if empty(a:msgs)
return
endif
if s:is_vim
call coc#rpc#notify('TaskStderr', [a:id, a:msgs])
else
let remain = get(s:err_remain_text, a:id, '')
let eof = (a:msgs == [''])
let msgs = copy(a:msgs)
if len(remain) > 0
if msgs[0] == ''
let msgs[0] = remain
else
let msgs[0] = remain . msgs[0]
endif
endif
let last = msgs[len(msgs) - 1]
let s:err_remain_text[a:id] = len(last) > 0 ? last : ''
" all lines from 0 to n - 2
if len(msgs) > 1
call coc#rpc#notify('TaskStderr', [a:id, msgs[:len(msgs)-2]])
elseif eof && len(msgs[0]) > 0
call coc#rpc#notify('TaskStderr', [a:id, msgs])
endif
endif
endfunction
function! s:on_stdout(id, msgs)
if empty(a:msgs)
return
endif
if s:is_vim
call coc#rpc#notify('TaskStdout', [a:id, a:msgs])
else
let remain = get(s:out_remain_text, a:id, '')
let eof = (a:msgs == [''])
let msgs = copy(a:msgs)
if len(remain) > 0
if msgs[0] == ''
let msgs[0] = remain
else
let msgs[0] = remain . msgs[0]
endif
endif
let last = msgs[len(msgs) - 1]
let s:out_remain_text[a:id] = len(last) > 0 ? last : ''
" all lines from 0 to n - 2
if len(msgs) > 1
call coc#rpc#notify('TaskStdout', [a:id, msgs[:len(msgs)-2]])
elseif eof && len(msgs[0]) > 0
call coc#rpc#notify('TaskStdout', [a:id, msgs])
endif
endif
endfunction
function! coc#task#running(id)
if !has_key(s:running_task, a:id) == 1
return v:false
endif
let job = s:running_task[a:id]
if s:is_vim
let status = job_status(job)
return status ==# 'run'
endif
let [code] = jobwait([job], 10)
return code == -1
endfunction

View File

@ -0,0 +1,115 @@
scriptencoding utf-8
let s:is_vim = !has('nvim')
let s:channel_map = {}
let s:is_win = has('win32') || has('win64')
" start terminal, return [bufnr, pid]
function! coc#terminal#start(cmd, cwd, env, strict) abort
if s:is_vim && !has('terminal')
throw 'terminal feature not supported by current vim.'
endif
let cwd = empty(a:cwd) ? getcwd() : a:cwd
execute 'belowright '.get(g:, 'coc_terminal_height', 8).'new +setl\ buftype=nofile'
setl winfixheight
setl norelativenumber
setl nonumber
setl bufhidden=hide
if exists('#User#CocTerminalOpen')
exe 'doautocmd <nomodeline> User CocTerminalOpen'
endif
let bufnr = bufnr('%')
let env = {}
let original = {}
if !empty(a:env)
" use env option when possible
if s:is_vim
let env = copy(a:env)
elseif exists('*setenv')
for key in keys(a:env)
let original[key] = getenv(key)
call setenv(key, a:env[key])
endfor
endif
endif
function! s:OnExit(status) closure
call coc#rpc#notify('CocAutocmd', ['TermExit', bufnr, a:status])
if a:status == 0
execute 'silent! bd! '.bufnr
endif
endfunction
if has('nvim')
let job_id = termopen(a:cmd, {
\ 'cwd': cwd,
\ 'pty': v:true,
\ 'on_exit': {job, status -> s:OnExit(status)},
\ 'env': env,
\ 'clear_env': a:strict ? v:true : v:false
\ })
if !empty(original) && exists('*setenv')
for key in keys(original)
call setenv(key, original[key])
endfor
endif
if job_id == 0
throw 'create terminal job failed'
endif
wincmd p
let s:channel_map[bufnr] = job_id
return [bufnr, jobpid(job_id)]
else
let cmd = s:is_win ? join(a:cmd, ' ') : a:cmd
let res = term_start(cmd, {
\ 'cwd': cwd,
\ 'term_kill': s:is_win ? 'kill' : 'term',
\ 'term_finish': 'close',
\ 'exit_cb': {job, status -> s:OnExit(status)},
\ 'curwin': 1,
\ 'env': env,
\})
if res == 0
throw 'create terminal job failed'
endif
let job = term_getjob(bufnr)
let s:channel_map[bufnr] = job_getchannel(job)
wincmd p
return [bufnr, job_info(job).process]
endif
endfunction
function! coc#terminal#send(bufnr, text, add_new_line) abort
let chan = get(s:channel_map, a:bufnr, v:null)
if empty(chan) | return| endif
if has('nvim')
let lines = split(a:text, '\v\r?\n')
if a:add_new_line && !empty(lines[len(lines) - 1])
if s:is_win
call add(lines, "\r\n")
else
call add(lines, '')
endif
endif
call chansend(chan, lines)
let winid = bufwinid(a:bufnr)
if winid != -1
call coc#compat#execute(winid, 'noa normal! G')
endif
else
if !a:add_new_line
call ch_sendraw(chan, a:text)
else
call ch_sendraw(chan, a:text.(s:is_win ? "\r\n" : "\n"))
endif
endif
endfunction
function! coc#terminal#close(bufnr) abort
if has('nvim')
let job_id = get(s:channel_map, a:bufnr, 0)
if !empty(job_id)
silent! call chanclose(job_id)
endif
endif
exe 'silent! bd! '.a:bufnr
endfunction

View File

@ -0,0 +1,316 @@
let s:is_vim = !has('nvim')
let s:is_win = has('win32') || has('win64')
function! coc#ui#quickpick(title, items, cb) abort
if exists('*popup_menu')
function! s:QuickpickHandler(id, result) closure
call a:cb(v:null, a:result)
endfunction
function! s:QuickpickFilter(id, key) closure
for i in range(1, len(a:items))
if a:key == string(i)
call popup_close(a:id, i)
return 1
endif
endfor
" No shortcut, pass to generic filter
return popup_filter_menu(a:id, a:key)
endfunction
try
call popup_menu(a:items, {
\ 'title': a:title,
\ 'filter': function('s:QuickpickFilter'),
\ 'callback': function('s:QuickpickHandler'),
\ })
redraw
catch /.*/
call a:cb(v:exception)
endtry
else
let res = inputlist([a:title] + a:items)
call a:cb(v:null, res)
endif
endfunction
" cmd, cwd
function! coc#ui#open_terminal(opts) abort
if s:is_vim && !exists('*term_start')
echohl WarningMsg | echon "Your vim doesn't have terminal support!" | echohl None
return
endif
if get(a:opts, 'position', 'bottom') ==# 'bottom'
let p = '5new'
else
let p = 'vnew'
endif
execute 'belowright '.p.' +setl\ buftype=nofile '
setl buftype=nofile
setl winfixheight
setl norelativenumber
setl nonumber
setl bufhidden=wipe
if exists('#User#CocTerminalOpen')
exe 'doautocmd <nomodeline> User CocTerminalOpen'
endif
let cmd = get(a:opts, 'cmd', '')
let autoclose = get(a:opts, 'autoclose', 1)
if empty(cmd)
throw 'command required!'
endif
let cwd = get(a:opts, 'cwd', getcwd())
let keepfocus = get(a:opts, 'keepfocus', 0)
let bufnr = bufnr('%')
let Callback = get(a:opts, 'Callback', v:null)
function! s:OnExit(status) closure
let content = join(getbufline(bufnr, 1, '$'), "\n")
if a:status == 0 && autoclose == 1
execute 'silent! bd! '.bufnr
endif
if !empty(Callback)
call call(Callback, [a:status, bufnr, content])
endif
endfunction
if has('nvim')
call termopen(cmd, {
\ 'cwd': cwd,
\ 'on_exit': {job, status -> s:OnExit(status)},
\})
else
if s:is_win
let cmd = 'cmd.exe /C "'.cmd.'"'
endif
call term_start(cmd, {
\ 'cwd': cwd,
\ 'exit_cb': {job, status -> s:OnExit(status)},
\ 'curwin': 1,
\})
endif
if keepfocus
wincmd p
endif
return bufnr
endfunction
" run command in terminal
function! coc#ui#run_terminal(opts, cb)
let cmd = get(a:opts, 'cmd', '')
if empty(cmd)
return a:cb('command required for terminal')
endif
let opts = {
\ 'cmd': cmd,
\ 'cwd': get(a:opts, 'cwd', getcwd()),
\ 'keepfocus': get(a:opts, 'keepfocus', 0),
\ 'Callback': {status, bufnr, content -> a:cb(v:null, {'success': status == 0 ? v:true : v:false, 'bufnr': bufnr, 'content': content})}
\}
call coc#ui#open_terminal(opts)
endfunction
function! coc#ui#echo_hover(msg)
echohl MoreMsg
echo a:msg
echohl None
let g:coc_last_hover_message = a:msg
endfunction
function! coc#ui#echo_messages(hl, msgs)
if a:hl !~# 'Error' && (mode() !~# '\v^(i|n)$')
return
endif
let msgs = filter(copy(a:msgs), '!empty(v:val)')
if empty(msgs)
return
endif
execute 'echohl '.a:hl
echom a:msgs[0]
redraw
echo join(msgs, "\n")
echohl None
endfunction
function! coc#ui#preview_info(lines, filetype, ...) abort
pclose
keepalt new +setlocal\ previewwindow|setlocal\ buftype=nofile|setlocal\ noswapfile|setlocal\ wrap [Document]
setl bufhidden=wipe
setl nobuflisted
setl nospell
exe 'setl filetype='.a:filetype
setl conceallevel=0
setl nofoldenable
for command in a:000
execute command
endfor
call append(0, a:lines)
exe "normal! z" . len(a:lines) . "\<cr>"
exe "normal! gg"
wincmd p
endfunction
function! coc#ui#open_files(files)
let bufnrs = []
" added on latest vim8
if exists('*bufadd') && exists('*bufload')
for file in a:files
let file = fnamemodify(file, ':.')
if bufloaded(file)
call add(bufnrs, bufnr(file))
else
let bufnr = bufadd(file)
call bufload(file)
call add(bufnrs, bufnr)
call setbufvar(bufnr, '&buflisted', 1)
endif
endfor
else
noa keepalt 1new +setl\ bufhidden=wipe
for file in a:files
let file = fnamemodify(file, ':.')
execute 'noa edit +setl\ bufhidden=hide '.fnameescape(file)
if &filetype ==# ''
filetype detect
endif
call add(bufnrs, bufnr('%'))
endfor
noa close
endif
doautocmd BufEnter
return bufnrs
endfunction
function! coc#ui#echo_lines(lines)
echo join(a:lines, "\n")
endfunction
function! coc#ui#echo_signatures(signatures) abort
if pumvisible() | return | endif
echo ""
for i in range(len(a:signatures))
call s:echo_signature(a:signatures[i])
if i != len(a:signatures) - 1
echon "\n"
endif
endfor
endfunction
function! s:echo_signature(parts)
for part in a:parts
let hl = get(part, 'type', 'Normal')
let text = get(part, 'text', '')
if !empty(text)
execute 'echohl '.hl
execute "echon '".substitute(text, "'", "''", 'g')."'"
echohl None
endif
endfor
endfunction
function! coc#ui#iterm_open(dir)
return s:osascript(
\ 'if application "iTerm2" is not running',
\ 'error',
\ 'end if') && s:osascript(
\ 'tell application "iTerm2"',
\ 'tell current window',
\ 'create tab with default profile',
\ 'tell current session',
\ 'write text "cd ' . a:dir . '"',
\ 'write text "clear"',
\ 'activate',
\ 'end tell',
\ 'end tell',
\ 'end tell')
endfunction
function! s:osascript(...) abort
let args = join(map(copy(a:000), '" -e ".shellescape(v:val)'), '')
call s:system('osascript'. args)
return !v:shell_error
endfunction
function! s:system(cmd)
let output = system(a:cmd)
if v:shell_error && output !=# ""
echohl Error | echom output | echohl None
return
endif
return output
endfunction
function! coc#ui#set_lines(bufnr, changedtick, original, replacement, start, end, changes, cursor, col) abort
if !bufloaded(a:bufnr)
return
endif
let delta = 0
if !empty(a:col)
let delta = col('.') - a:col
endif
if getbufvar(a:bufnr, 'changedtick') != a:changedtick && bufnr('%') == a:bufnr
" try apply current line change
let lnum = line('.')
let idx = a:start - lnum + 1
let previous = get(a:original, idx, 0)
if type(previous) == 1
let content = getline('.')
if previous !=# content
let diff = coc#string#diff(content, previous, col('.'))
let changed = get(a:replacement, idx, 0)
if type(changed) == 1 && strcharpart(previous, 0, diff['end']) ==# strcharpart(changed, 0, diff['end'])
let applied = coc#string#apply(changed, diff)
let replacement = copy(a:replacement)
let replacement[idx] = applied
call coc#compat#buf_set_lines(a:bufnr, a:start, a:end, replacement)
return
endif
endif
endif
endif
if exists('*nvim_buf_set_text') && !empty(a:changes)
for item in reverse(copy(a:changes))
call nvim_buf_set_text(a:bufnr, item[1], item[2], item[3], item[4], item[0])
endfor
else
call coc#compat#buf_set_lines(a:bufnr, a:start, a:end, a:replacement)
endif
if !empty(a:cursor)
call cursor(a:cursor[0], a:cursor[1] + delta)
endif
endfunction
function! coc#ui#change_lines(bufnr, list) abort
if !bufloaded(a:bufnr) | return v:null | endif
undojoin
if exists('*setbufline')
for [lnum, line] in a:list
call setbufline(a:bufnr, lnum + 1, line)
endfor
elseif a:bufnr == bufnr('%')
for [lnum, line] in a:list
call setline(lnum + 1, line)
endfor
else
let bufnr = bufnr('%')
exe 'noa buffer '.a:bufnr
for [lnum, line] in a:list
call setline(lnum + 1, line)
endfor
exe 'noa buffer '.bufnr
endif
endfunction
function! coc#ui#open_url(url)
if has('mac') && executable('open')
call system('open '.a:url)
return
endif
if executable('xdg-open')
call system('xdg-open '.a:url)
return
endif
call system('cmd /c start "" /b '. substitute(a:url, '&', '^&', 'g'))
if v:shell_error
echohl Error | echom 'Failed to open '.a:url | echohl None
return
endif
endfunction

View File

@ -0,0 +1,601 @@
scriptencoding utf-8
let s:root = expand('<sfile>:h:h:h')
let s:is_win = has('win32') || has('win64')
let s:is_vim = !has('nvim')
let s:vim_api_version = 29
function! coc#util#remote_fns(name)
let fns = ['init', 'complete', 'should_complete', 'refresh', 'get_startcol', 'on_complete', 'on_enter']
let res = []
for fn in fns
if exists('*coc#source#'.a:name.'#'.fn)
call add(res, fn)
endif
endfor
return res
endfunction
function! coc#util#do_complete(name, opt, cb) abort
let handler = 'coc#source#'.a:name.'#complete'
let l:Cb = {res -> a:cb(v:null, res)}
let args = [a:opt, l:Cb]
call call(handler, args)
endfunction
function! coc#util#suggest_variables(bufnr) abort
return {
\ 'coc_suggest_disable': getbufvar(a:bufnr, 'coc_suggest_disable', 0),
\ 'coc_disabled_sources': getbufvar(a:bufnr, 'coc_disabled_sources', []),
\ 'coc_suggest_blacklist': getbufvar(a:bufnr, 'coc_suggest_blacklist', []),
\ }
endfunction
function! coc#util#api_version() abort
return s:vim_api_version
endfunction
function! coc#util#semantic_hlgroups() abort
let res = split(execute('hi'), "\n")
let filtered = filter(res, "v:val =~# '^CocSem'")
return map(filtered, "matchstr(v:val,'\\v^CocSem\\w+')")
endfunction
" get cursor position
function! coc#util#cursor()
return [line('.') - 1, strchars(strpart(getline('.'), 0, col('.') - 1))]
endfunction
function! coc#util#change_info() abort
return {'lnum': line('.'), 'col': col('.'), 'line': getline('.'), 'changedtick': b:changedtick}
endfunction
function! coc#util#jumpTo(line, character) abort
echohl WarningMsg | echon 'coc#util#jumpTo is deprecated, use coc#cursor#move_to instead.' | echohl None
call coc#cursor#move_to(a:line, a:character)
endfunction
function! coc#util#root_patterns() abort
return coc#rpc#request('rootPatterns', [bufnr('%')])
endfunction
function! coc#util#get_config(key) abort
return coc#rpc#request('getConfig', [a:key])
endfunction
function! coc#util#open_terminal(opts) abort
return coc#ui#open_terminal(a:opts)
endfunction
function! coc#util#synname() abort
return synIDattr(synID(line('.'), col('.') - 1, 1), 'name')
endfunction
function! coc#util#setline(lnum, line)
keepjumps call setline(a:lnum, a:line)
endfunction
function! coc#util#path_replace_patterns() abort
if has('win32unix') && exists('g:coc_cygqwin_path_prefixes')
echohl WarningMsg
echon 'g:coc_cygqwin_path_prefixes is deprecated, use g:coc_uri_prefix_replace_patterns instead'
echohl None
return g:coc_cygqwin_path_prefixes
endif
if exists('g:coc_uri_prefix_replace_patterns')
return g:coc_uri_prefix_replace_patterns
endif
return v:null
endfunction
function! coc#util#version()
if s:is_vim
return string(v:versionlong)
endif
let c = execute('silent version')
let lines = split(matchstr(c, 'NVIM v\zs[^\n-]*'))
return lines[0]
endfunction
function! coc#util#check_refresh(bufnr)
if !bufloaded(a:bufnr)
return 0
endif
if getbufvar(a:bufnr, 'coc_diagnostic_disable', 0)
return 0
endif
if get(g: , 'EasyMotion_loaded', 0)
return EasyMotion#is_active() != 1
endif
return 1
endfunction
function! coc#util#diagnostic_info(bufnr, checkInsert) abort
let checked = coc#util#check_refresh(a:bufnr)
if !checked
return v:null
endif
if a:checkInsert && mode() =~# '^i'
return v:null
endif
let locationlist = ''
let winid = -1
for info in getwininfo()
if info['bufnr'] == a:bufnr
let winid = info['winid']
let locationlist = get(getloclist(winid, {'title': 1}), 'title', '')
break
endif
endfor
return {
\ 'bufnr': bufnr('%'),
\ 'winid': winid,
\ 'lnum': line('.'),
\ 'locationlist': locationlist
\ }
endfunction
function! coc#util#open_file(cmd, file)
let file = fnameescape(a:file)
execute a:cmd .' '.file
endfunction
function! coc#util#job_command()
if (has_key(g:, 'coc_node_path'))
let node = expand(g:coc_node_path)
else
let node = $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH
endif
if !executable(node)
echohl Error | echom '[coc.nvim] "'.node.'" is not executable, checkout https://nodejs.org/en/download/' | echohl None
return
endif
if !filereadable(s:root.'/build/index.js')
if isdirectory(s:root.'/src')
echohl Error | echom '[coc.nvim] build/index.js not found, please install dependencies and compile coc.nvim by: yarn install' | echohl None
else
echohl Error | echon '[coc.nvim] your coc.nvim is broken.' | echohl None
endif
return
endif
return [node] + get(g:, 'coc_node_args', ['--no-warnings']) + [s:root.'/build/index.js']
endfunction
function! coc#util#jump(cmd, filepath, ...) abort
if a:cmd != 'pedit'
silent! normal! m'
endif
let path = a:filepath
if (has('win32unix'))
let path = substitute(a:filepath, '\v\\', '/', 'g')
endif
let file = fnamemodify(path, ":~:.")
if a:cmd == 'pedit'
let extra = empty(get(a:, 1, [])) ? '' : '+'.(a:1[0] + 1)
exe 'pedit '.extra.' '.fnameescape(file)
return
elseif a:cmd == 'drop' && exists('*bufadd')
let dstbuf = bufadd(path)
let binfo = getbufinfo(dstbuf)
if len(binfo) == 1 && empty(binfo[0].windows)
exec 'buffer '.dstbuf
let &buflisted = 1
else
exec 'drop '.fnameescape(file)
endif
elseif a:cmd == 'edit'
if bufloaded(file)
exe 'b '.bufnr(file)
else
exe a:cmd.' '.fnameescape(file)
endif
else
exe a:cmd.' '.fnameescape(file)
endif
if !empty(get(a:, 1, []))
let line = getline(a:1[0] + 1)
" TODO need to use utf16 here
let col = byteidx(line, a:1[1]) + 1
if col == 0
let col = 999
endif
call cursor(a:1[0] + 1, col)
endif
if &filetype ==# ''
filetype detect
endif
if s:is_vim
redraw
endif
endfunction
function! coc#util#variables(bufnr) abort
let info = getbufinfo(a:bufnr)
let variables = empty(info) ? {} : copy(info[0]['variables'])
for key in keys(variables)
if key !~# '\v^coc'
unlet variables[key]
endif
endfor
return variables
endfunction
function! coc#util#with_callback(method, args, cb)
function! s:Cb() closure
try
let res = call(a:method, a:args)
call a:cb(v:null, res)
catch /.*/
call a:cb(v:exception)
endtry
endfunction
let timeout = s:is_vim ? 10 : 0
call timer_start(timeout, {-> s:Cb() })
endfunction
function! coc#util#timer(method, args)
call timer_start(0, { -> s:Call(a:method, a:args)})
endfunction
function! s:Call(method, args)
try
call call(a:method, a:args)
redraw
catch /.*/
return 0
endtry
endfunction
function! coc#util#vim_info()
return {
\ 'apiversion': s:vim_api_version,
\ 'mode': mode(),
\ 'config': get(g:, 'coc_user_config', {}),
\ 'floating': has('nvim') && exists('*nvim_open_win') ? v:true : v:false,
\ 'extensionRoot': coc#util#extension_root(),
\ 'globalExtensions': get(g:, 'coc_global_extensions', []),
\ 'lines': &lines,
\ 'columns': &columns,
\ 'cmdheight': &cmdheight,
\ 'pid': coc#util#getpid(),
\ 'filetypeMap': get(g:, 'coc_filetype_map', {}),
\ 'version': coc#util#version(),
\ 'completeOpt': &completeopt,
\ 'pumevent': exists('##MenuPopupChanged') || exists('##CompleteChanged'),
\ 'isVim': has('nvim') ? v:false : v:true,
\ 'isCygwin': has('win32unix') ? v:true : v:false,
\ 'isMacvim': has('gui_macvim') ? v:true : v:false,
\ 'isiTerm': $TERM_PROGRAM ==# "iTerm.app",
\ 'colorscheme': get(g:, 'colors_name', ''),
\ 'workspaceFolders': get(g:, 'WorkspaceFolders', v:null),
\ 'background': &background,
\ 'runtimepath': join(globpath(&runtimepath, '', 0, 1), ','),
\ 'locationlist': get(g:,'coc_enable_locationlist', 1),
\ 'progpath': v:progpath,
\ 'guicursor': &guicursor,
\ 'tabCount': tabpagenr('$'),
\ 'updateHighlight': has('nvim-0.5.0') || has('patch-8.1.1719') ? v:true : v:false,
\ 'vimCommands': get(g:, 'coc_vim_commands', []),
\ 'sign': exists('*sign_place') && exists('*sign_unplace'),
\ 'textprop': has('textprop') && has('patch-8.1.1719') && !has('nvim') ? v:true : v:false,
\ 'dialog': has('nvim-0.4.0') || has('patch-8.2.0750') ? v:true : v:false,
\ 'semanticHighlights': coc#util#semantic_hlgroups()
\}
endfunction
function! coc#util#all_state()
return {
\ 'bufnr': bufnr('%'),
\ 'winid': win_getid(),
\ 'bufnrs': map(getbufinfo({'bufloaded': 1}),'v:val["bufnr"]'),
\ 'winids': map(getwininfo(),'v:val["winid"]'),
\ }
endfunction
function! coc#util#install() abort
let yarncmd = get(g:, 'coc_install_yarn_cmd', executable('yarnpkg') ? 'yarnpkg' : 'yarn')
call coc#ui#open_terminal({
\ 'cwd': s:root,
\ 'cmd': yarncmd.' install --frozen-lockfile --ignore-engines',
\ 'autoclose': 0,
\ })
endfunction
function! coc#util#extension_root() abort
if get(g:, 'coc_node_env', '') ==# 'test'
return s:root.'/src/__tests__/extensions'
endif
if !empty(get(g:, 'coc_extension_root', ''))
echohl Error | echon 'g:coc_extension_root not used any more, use g:coc_data_home instead' | echohl None
endif
return coc#util#get_data_home().'/extensions'
endfunction
function! coc#util#update_extensions(...) abort
let async = get(a:, 1, 0)
if async
call coc#rpc#notify('updateExtensions', [])
else
call coc#rpc#request('updateExtensions', [v:true])
endif
endfunction
function! coc#util#install_extension(args) abort
let names = filter(copy(a:args), 'v:val !~# "^-"')
let isRequest = index(a:args, '-sync') != -1
if isRequest
call coc#rpc#request('installExtensions', names)
else
call coc#rpc#notify('installExtensions', names)
endif
endfunction
function! coc#util#do_autocmd(name) abort
if exists('#User#'.a:name)
exe 'doautocmd <nomodeline> User '.a:name
endif
endfunction
function! coc#util#rebuild()
let dir = coc#util#extension_root()
if !isdirectory(dir) | return | endif
call coc#ui#open_terminal({
\ 'cwd': dir,
\ 'cmd': 'npm rebuild',
\ 'keepfocus': 1,
\})
endfunction
function! coc#util#unmap(bufnr, keys) abort
if bufnr('%') == a:bufnr
for key in a:keys
exe 'silent! nunmap <buffer> '.key
endfor
endif
endfunction
function! coc#util#refactor_foldlevel(lnum) abort
if a:lnum <= 2 | return 0 | endif
let line = getline(a:lnum)
if line =~# '^\%u3000\s*$' | return 0 | endif
return 1
endfunction
function! coc#util#refactor_fold_text(lnum) abort
let range = ''
let info = get(b:line_infos, a:lnum, [])
if !empty(info)
let range = info[0].':'.info[1]
endif
return trim(getline(a:lnum)[3:]).' '.range
endfunction
" get tabsize & expandtab option
function! coc#util#get_format_opts(bufnr) abort
let bufnr = a:bufnr && bufloaded(a:bufnr) ? a:bufnr : bufnr('%')
let tabsize = getbufvar(bufnr, '&shiftwidth')
if tabsize == 0
let tabsize = getbufvar(bufnr, '&tabstop')
endif
return {
\ 'tabsize': tabsize,
\ 'expandtab': getbufvar(bufnr, '&expandtab'),
\ 'insertFinalNewline': getbufvar(bufnr, '&eol'),
\ 'trimTrailingWhitespace': getbufvar(bufnr, 'coc_trim_trailing_whitespace', 0),
\ 'trimFinalNewlines': getbufvar(bufnr, 'coc_trim_final_newlines', 0)
\ }
endfunction
function! coc#util#get_editoroption(winid) abort
if !coc#compat#win_is_valid(a:winid)
return v:null
endif
if has('nvim') && exists('*nvim_win_get_config')
" avoid float window
let config = nvim_win_get_config(a:winid)
if !empty(get(config, 'relative', ''))
return v:null
endif
endif
let info = getwininfo(a:winid)[0]
let bufnr = info['bufnr']
let buftype = getbufvar(bufnr, '&buftype')
" avoid window for other purpose.
if buftype !=# '' && buftype !=# 'acwrite'
return v:null
endif
let tabSize = getbufvar(bufnr, '&shiftwidth')
if tabSize == 0
let tabSize = getbufvar(bufnr, '&tabstop')
endif
return {
\ 'bufnr': bufnr,
\ 'winid': a:winid,
\ 'winids': map(getwininfo(), 'v:val["winid"]'),
\ 'tabpagenr': info['tabnr'],
\ 'winnr': winnr(),
\ 'visibleRanges': s:visible_ranges(a:winid),
\ 'tabSize': tabSize,
\ 'insertSpaces': getbufvar(bufnr, '&expandtab') ? v:true : v:false
\ }
endfunction
function! coc#util#getpid()
if !has('win32unix')
return getpid()
endif
let cmd = 'cat /proc/' . getpid() . '/winpid'
return substitute(system(cmd), '\v\n', '', 'gi')
endfunction
" Get indentkeys for indent on TextChangedP, consider = for word indent only.
function! coc#util#get_indentkeys() abort
if empty(&indentexpr)
return ''
endif
if &indentkeys !~# '='
return ''
endif
return &indentkeys
endfunction
function! coc#util#get_bufoptions(bufnr) abort
if !bufloaded(a:bufnr) | return v:null | endif
let bufname = bufname(a:bufnr)
let buftype = getbufvar(a:bufnr, '&buftype')
let winid = bufwinid(a:bufnr)
let size = -1
if bufnr('%') == a:bufnr
let size = line2byte(line("$") + 1)
elseif !empty(bufname)
let size = getfsize(bufname)
endif
let lines = v:null
if getbufvar(a:bufnr, 'coc_enabled', 1) && (buftype == '' || buftype == 'acwrite') && size < get(g:, 'coc_max_filesize', 2097152)
let lines = getbufline(a:bufnr, 1, '$')
endif
return {
\ 'bufnr': a:bufnr,
\ 'size': size,
\ 'lines': lines,
\ 'winid': winid,
\ 'bufname': bufname,
\ 'buftype': buftype,
\ 'previewwindow': v:false,
\ 'eol': getbufvar(a:bufnr, '&eol'),
\ 'indentkeys': coc#util#get_indentkeys(),
\ 'variables': coc#util#variables(a:bufnr),
\ 'filetype': getbufvar(a:bufnr, '&filetype'),
\ 'iskeyword': getbufvar(a:bufnr, '&iskeyword'),
\ 'changedtick': getbufvar(a:bufnr, 'changedtick'),
\ 'fullpath': empty(bufname) ? '' : fnamemodify(bufname, ':p'),
\}
endfunction
function! coc#util#get_config_home()
if !empty(get(g:, 'coc_config_home', ''))
return resolve(expand(g:coc_config_home))
endif
if exists('$VIMCONFIG')
return resolve($VIMCONFIG)
endif
if has('nvim')
if exists('$XDG_CONFIG_HOME')
return resolve($XDG_CONFIG_HOME."/nvim")
endif
if s:is_win
return resolve($HOME.'/AppData/Local/nvim')
endif
return resolve($HOME.'/.config/nvim')
else
if s:is_win
return resolve($HOME."/vimfiles")
endif
return resolve($HOME.'/.vim')
endif
endfunction
function! coc#util#get_data_home()
if !empty(get(g:, 'coc_data_home', ''))
let dir = resolve(expand(g:coc_data_home))
else
if exists('$XDG_CONFIG_HOME')
let dir = resolve($XDG_CONFIG_HOME."/coc")
else
if s:is_win
let dir = resolve(expand('~/AppData/Local/coc'))
else
let dir = resolve(expand('~/.config/coc'))
endif
endif
endif
if !isdirectory(dir)
call coc#notify#create(['creating data directory: '.dir], {
\ 'borderhighlight': 'CocInfoSign',
\ 'timeout': 5000,
\ 'kind': 'info',
\ })
call mkdir(dir, "p", 0755)
endif
return dir
endfunction
function! coc#util#get_complete_option()
if get(b:,"coc_suggest_disable",0)
return v:null
endif
let pos = getcurpos()
let line = getline(pos[1])
let input = matchstr(strpart(line, 0, pos[2] - 1), '\k*$')
let col = pos[2] - strlen(input)
return {
\ 'word': matchstr(strpart(line, col - 1), '^\k\+'),
\ 'input': empty(input) ? '' : input,
\ 'line': line,
\ 'filetype': &filetype,
\ 'filepath': expand('%:p'),
\ 'bufnr': bufnr('%'),
\ 'linenr': pos[1],
\ 'colnr' : pos[2],
\ 'col': col - 1,
\ 'changedtick': b:changedtick,
\ 'blacklist': get(b:, 'coc_suggest_blacklist', []),
\ 'disabled': get(b:, 'coc_disabled_sources', []),
\ 'indentkeys': coc#util#get_indentkeys()
\}
endfunction
" used by vim
function! coc#util#get_buf_lines(bufnr, changedtick)
if !bufloaded(a:bufnr)
return v:null
endif
let changedtick = getbufvar(a:bufnr, 'changedtick')
if changedtick == a:changedtick
return v:null
endif
return {
\ 'lines': getbufline(a:bufnr, 1, '$'),
\ 'changedtick': getbufvar(a:bufnr, 'changedtick')
\ }
endfunction
" used for TextChangedI with InsertCharPre
function! coc#util#get_changeinfo()
return {
\ 'bufnr': bufnr('%'),
\ 'lnum': line('.'),
\ 'line': getline('.'),
\ 'changedtick': b:changedtick,
\}
endfunction
function! s:visible_ranges(winid) abort
let info = getwininfo(a:winid)[0]
let res = []
if !has_key(info, 'topline') || !has_key(info, 'botline')
return res
endif
let begin = 0
let curr = info['topline']
let max = info['botline']
if win_getid() != a:winid
return [[curr, max]]
endif
while curr <= max
let closedend = foldclosedend(curr)
if closedend == -1
let begin = begin == 0 ? curr : begin
if curr == max
call add(res, [begin, curr])
endif
let curr = curr + 1
else
if begin != 0
call add(res, [begin, curr - 1])
let begin = closedend + 1
endif
let curr = closedend + 1
endif
endwhile
return res
endfunction

View File

@ -0,0 +1,198 @@
let g:coc_max_treeview_width = get(g:, 'coc_max_treeview_width', 40)
let s:is_vim = !has('nvim')
" Get tabpagenr of winid, return -1 if window doesn't exist
function! coc#window#tabnr(winid) abort
if exists('*nvim_win_get_tabpage')
try
return nvim_win_get_tabpage(a:winid)
catch /Invalid window id/
return -1
endtry
endif
if exists('*win_execute')
let ref = {}
call win_execute(a:winid, 'let ref["out"] = tabpagenr()')
return get(ref, 'out', -1)
elseif !s:is_vim
let info = getwininfo(a:winid)
return empty(info) ? -1 : info[0]['tabnr']
else
throw 'win_execute() does not exist, please upgrade your vim.'
endif
endfunction
" Check if winid visible on current tabpage
function! coc#window#visible(winid) abort
if s:is_vim
if coc#window#tabnr(a:winid) != tabpagenr()
return 0
endif
" Check possible hidden popup
try
return get(popup_getpos(a:winid), 'visible', 0) == 1
catch /^Vim\%((\a\+)\)\=:E993/
return 1
endtry
endif
if !nvim_win_is_valid(a:winid)
return 0
endif
return coc#window#tabnr(a:winid) == tabpagenr()
endfunction
" Return v:null when name or window doesn't exist,
" 'getwinvar' only works on window of current tab
function! coc#window#get_var(winid, name, ...) abort
if !s:is_vim
try
if a:name =~# '^&'
return nvim_win_get_option(a:winid, a:name[1:])
else
return nvim_win_get_var(a:winid, a:name)
endif
catch /E5555/
return get(a:, 1, v:null)
endtry
else
try
return coc#api#exec('win_get_var', [a:winid, a:name, get(a:, 1, v:null)])
catch /.*/
return get(a:, 1, v:null)
endtry
endif
endfunction
" Not throw like setwinvar
function! coc#window#set_var(winid, name, value) abort
try
if !s:is_vim
if a:name =~# '^&'
call nvim_win_set_option(a:winid, a:name[1:], a:value)
else
call nvim_win_set_var(a:winid, a:name, a:value)
endif
else
call coc#api#exec('win_set_var', [a:winid, a:name, a:value])
endif
catch /Invalid window id/
" ignore
endtry
endfunction
function! coc#window#is_float(winid) abort
if s:is_vim
if exists('*popup_list')
return index(popup_list(), a:winid) != -1
else
try
return !empty(popup_getpos(a:winid))
catch /^Vim\%((\a\+)\)\=:E993/
return 0
endtry
endif
return 0
elseif exists('*nvim_win_get_config')
let config = nvim_win_get_config(a:winid)
return !empty(config) && !empty(get(config, 'relative', ''))
endif
endfunction
function! coc#window#set_height(winid, height) abort
if empty(getwininfo(a:winid))
return
endif
if exists('*nvim_win_set_height')
call nvim_win_set_height(a:winid, a:height)
else
call coc#compat#execute(a:winid, 'noa resize '.a:height, 'silent')
endif
endfunction
function! coc#window#adjust_width(winid) abort
let bufnr = winbufnr(a:winid)
if bufloaded(bufnr)
let maxwidth = 0
let lines = getbufline(bufnr, 1, '$')
if len(lines) > 2
call coc#compat#execute(a:winid, 'setl nowrap')
for line in lines
let w = strwidth(line)
if w > maxwidth
let maxwidth = w
endif
endfor
endif
if maxwidth > winwidth(a:winid)
call coc#compat#execute(a:winid, 'vertical resize '.min([maxwidth, g:coc_max_treeview_width]))
endif
endif
endfunction
" Get single window by window variable, current tab only
function! coc#window#find(key, val) abort
for i in range(1, winnr('$'))
let res = getwinvar(i, a:key)
if res == a:val
return win_getid(i)
endif
endfor
return -1
endfunction
" Visible buffer numbers
function! coc#window#bufnrs() abort
let winids = []
if exists('*nvim_list_wins')
let winids = nvim_list_wins()
else
let winids = map(getwininfo(), 'v:val["winid"]')
endif
return uniq(map(winids, 'winbufnr(v:val)'))
endfunction
" Avoid errors
function! coc#window#close(winid) abort
if empty(a:winid) || a:winid == -1
return
endif
if exists('*nvim_win_is_valid') && exists('*nvim_win_close')
if nvim_win_is_valid(a:winid)
call nvim_win_close(a:winid, 1)
endif
elseif exists('*win_execute')
call coc#compat#execute(a:winid, 'noa close!', 'silent!')
else
let curr = win_getid()
if curr == a:winid
silent! close!
else
let res = win_gotoid(a:winid)
if res
silent! close!
call win_gotoid(curr)
endif
endif
endif
endfunction
function! coc#window#visible_range(bufnr) abort
let winid = bufwinid(a:bufnr)
if winid == -1
return v:null
endif
let info = getwininfo(winid)[0]
return [info['topline'], info['botline']]
endfunction
function! coc#window#visible_ranges(bufnr) abort
let wins = gettabinfo(tabpagenr())[0]['windows']
let res = []
for id in wins
let info = getwininfo(id)[0]
if info['bufnr'] == a:bufnr
call add(res, [info['topline'], info['botline']])
endif
endfor
return res
endfunction

View File

@ -0,0 +1,100 @@
scriptencoding utf-8
let s:root = expand('<sfile>:h:h:h')
function! s:checkVim(test, name, patchlevel) abort
if a:test
if !has(a:patchlevel)
call health#report_error(a:name . ' version not satisfied, ' . a:patchlevel . ' and above required')
return 0
else
call health#report_ok(a:name . ' version satisfied')
return 1
endif
endif
return 0
endfunction
function! s:checkEnvironment() abort
let valid
\ = s:checkVim(has('nvim'), 'nvim', 'nvim-0.3.2')
\ + s:checkVim(!has('nvim'), 'vim', 'patch-0.8.1453')
let node = get(g:, 'coc_node_path', $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH)
if !executable(node)
let valid = 0
call health#report_error('Executable node.js not found, install node.js from http://nodejs.org/')
endif
let output = system(node . ' --version')
if v:shell_error && output !=# ""
let valid = 0
call health#report_error(output)
endif
let ms = matchlist(output, 'v\(\d\+\).\(\d\+\).\(\d\+\)')
if empty(ms)
let valid = 0
call health#report_error('Unable to detect version of node, make sure your node executable is http://nodejs.org/')
elseif str2nr(ms[1]) < 12 || (str2nr(ms[1]) == 12 && str2nr(ms[2]) < 12)
let valid = 0
call health#report_warn('Node.js version '.trim(output).' < 12.12.0, please upgrade node.js')
endif
if valid
call health#report_ok('Environment check passed')
endif
if has('pythonx')
try
silent pyx print("")
catch /.*/
call health#report_warn('pyx command not work, some extensions may fail to work, checkout ":h pythonx"')
if has('nvim')
call health#report_warn('Install pynvim by command: pip install pynvim --upgrade')
endif
endtry
endif
return valid
endfunction
function! s:checkCommand()
let file = s:root.'/build/index.js'
if filereadable(file)
call health#report_ok('Javascript bundle build/index.js found')
else
call health#report_error('Javascript entry not found, please compile coc.nvim by esbuild.')
endif
endfunction
function! s:checkAutocmd()
let cmds = ['CursorHold', 'CursorHoldI', 'CursorMovedI', 'InsertCharPre', 'TextChangedI']
for cmd in cmds
let lines = split(execute('verbose autocmd '.cmd), '\n')
let n = 0
for line in lines
if line =~# 'CocAction(' && n < len(lines) - 1
let next = lines[n + 1]
let ms = matchlist(next, 'Last set from \(.*\)')
if !empty(ms)
call health#report_warn('Use CocActionAsync to replace CocAction for better performance on '.cmd)
call health#report_warn('Checkout the file '.ms[1])
endif
endif
let n = n + 1
endfor
endfor
endfunction
function! s:checkInitialize() abort
if coc#client#is_running('coc')
call health#report_ok('Service started')
return 1
endif
call health#report_error('service could not be initialized', [
\ 'Use command ":messages" to get error messages.',
\ 'Open a issue at https://github.com/neoclide/coc.nvim/issues for feedback.'
\])
return 0
endfunction
function! health#coc#check() abort
call s:checkEnvironment()
call s:checkCommand()
call s:checkInitialize()
call s:checkAutocmd()
endfunction