scriptencoding utf-8 let s:root = expand(':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 | echo '[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 tmpdir = fnamemodify(tempname(), ':p:h') if s:is_vim if get(g:, 'node_client_debug', 0) let file = tmpdir . '/coc.log' call ch_logfile(file, 'w') echohl MoreMsg | echo '[coc.nvim] channel log to '.file | echohl None endif 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 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] if a:name ==# 'coc' && len(filter(copy(data), 'v:val =~# "SyntaxError: Unexpected token"')) call coc#client#check_version() endif if get(g:, 'coc_disable_uncaught_error', 0) | return | endif call coc#ui#echo_messages('Error', data) endfunction function! coc#client#check_version() abort 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 let output = system(node . ' --version') let msgs = [] if v:shell_error let msgs = ['Unexpected result from node --version'] + split(output, '\n') else let ms = matchlist(output, 'v\(\d\+\).\(\d\+\).\(\d\+\)') if empty(ms) let msgs = ['Unable to detect version of node, make sure your node executable is http://nodejs.org/'] elseif str2nr(ms[1]) < 14 || (str2nr(ms[1]) == 14 && str2nr(ms[2]) < 14) let msgs = ['Current Node.js version '.trim(output).' < 14.14.0 ', 'Please upgrade your node.js'] endif endif if !empty(msgs) call coc#notify#create(msgs, { \ 'borderhighlight': 'CocErrorSign', \ 'highlight': 'Normal', \ 'timeout': 50000, \ 'kind': 'error', \ }) endif 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