client.vim 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. scriptencoding utf-8
  2. let s:root = expand('<sfile>:h:h:h')
  3. let s:is_vim = !has('nvim')
  4. let s:is_win = has("win32") || has("win64")
  5. let s:clients = {}
  6. if get(g:, 'node_client_debug', 0)
  7. echohl WarningMsg | echo '[coc.nvim] Enable g:node_client_debug could impact your vim experience' | echohl None
  8. let $NODE_CLIENT_LOG_LEVEL = 'debug'
  9. if exists('$NODE_CLIENT_LOG_FILE')
  10. let s:logfile = resolve($NODE_CLIENT_LOG_FILE)
  11. else
  12. let s:logfile = tempname()
  13. let $NODE_CLIENT_LOG_FILE = s:logfile
  14. endif
  15. endif
  16. " create a client
  17. function! coc#client#create(name, command)
  18. let client = {}
  19. let client['command'] = a:command
  20. let client['name'] = a:name
  21. let client['running'] = 0
  22. let client['async_req_id'] = 1
  23. let client['async_callbacks'] = {}
  24. " vim only
  25. let client['channel'] = v:null
  26. " neovim only
  27. let client['chan_id'] = 0
  28. let client['start'] = function('s:start', [], client)
  29. let client['request'] = function('s:request', [], client)
  30. let client['notify'] = function('s:notify', [], client)
  31. let client['request_async'] = function('s:request_async', [], client)
  32. let client['on_async_response'] = function('s:on_async_response', [], client)
  33. let s:clients[a:name] = client
  34. return client
  35. endfunction
  36. function! s:start() dict
  37. if self.running | return | endif
  38. if !isdirectory(getcwd())
  39. echohl Error | echon '[coc.nvim] Current cwd is not a valid directory.' | echohl None
  40. return
  41. endif
  42. let timeout = string(get(g:, 'coc_channel_timeout', 30))
  43. let tmpdir = fnamemodify(tempname(), ':p:h')
  44. if s:is_vim
  45. if get(g:, 'node_client_debug', 0)
  46. let file = tmpdir . '/coc.log'
  47. call ch_logfile(file, 'w')
  48. echohl MoreMsg | echo '[coc.nvim] channel log to '.file | echohl None
  49. endif
  50. let options = {
  51. \ 'in_mode': 'json',
  52. \ 'out_mode': 'json',
  53. \ 'err_mode': 'nl',
  54. \ 'err_cb': {channel, message -> s:on_stderr(self.name, split(message, "\n"))},
  55. \ 'exit_cb': {channel, code -> s:on_exit(self.name, code)},
  56. \ 'env': {
  57. \ 'NODE_NO_WARNINGS': '1',
  58. \ 'VIM_NODE_RPC': '1',
  59. \ 'COC_NVIM': '1',
  60. \ 'COC_CHANNEL_TIMEOUT': timeout,
  61. \ 'TMPDIR': tmpdir,
  62. \ }
  63. \}
  64. if has("patch-8.1.350")
  65. let options['noblock'] = 1
  66. endif
  67. let job = job_start(self.command, options)
  68. let status = job_status(job)
  69. if status !=# 'run'
  70. let self.running = 0
  71. echohl Error | echom 'Failed to start '.self.name.' service' | echohl None
  72. return
  73. endif
  74. let self['running'] = 1
  75. let self['channel'] = job_getchannel(job)
  76. else
  77. let original = {}
  78. let opts = {
  79. \ 'rpc': 1,
  80. \ 'on_stderr': {channel, msgs -> s:on_stderr(self.name, msgs)},
  81. \ 'on_exit': {channel, code -> s:on_exit(self.name, code)},
  82. \ }
  83. if has('nvim-0.5.0')
  84. " could use env option
  85. let opts['env'] = {
  86. \ 'COC_NVIM': '1',
  87. \ 'NODE_NO_WARNINGS': '1',
  88. \ 'COC_CHANNEL_TIMEOUT': timeout,
  89. \ 'TMPDIR': tmpdir
  90. \ }
  91. else
  92. if exists('*getenv')
  93. let original = {
  94. \ 'NODE_NO_WARNINGS': getenv('NODE_NO_WARNINGS'),
  95. \ 'TMPDIR': getenv('TMPDIR'),
  96. \ }
  97. endif
  98. if exists('*setenv')
  99. call setenv('COC_NVIM', '1')
  100. call setenv('NODE_NO_WARNINGS', '1')
  101. call setenv('COC_CHANNEL_TIMEOUT', timeout)
  102. call setenv('TMPDIR', tmpdir)
  103. else
  104. let $NODE_NO_WARNINGS = 1
  105. let $TMPDIR = tmpdir
  106. endif
  107. endif
  108. let chan_id = jobstart(self.command, opts)
  109. if !empty(original)
  110. if exists('*setenv')
  111. for key in keys(original)
  112. call setenv(key, original[key])
  113. endfor
  114. else
  115. let $TMPDIR = original['TMPDIR']
  116. endif
  117. endif
  118. if chan_id <= 0
  119. echohl Error | echom 'Failed to start '.self.name.' service' | echohl None
  120. return
  121. endif
  122. let self['chan_id'] = chan_id
  123. let self['running'] = 1
  124. endif
  125. endfunction
  126. function! s:on_stderr(name, msgs)
  127. if get(g:, 'coc_vim_leaving', 0) | return | endif
  128. if get(g:, 'coc_disable_uncaught_error', 0) | return | endif
  129. let data = filter(copy(a:msgs), '!empty(v:val)')
  130. if empty(data) | return | endif
  131. let client = a:name ==# 'coc' ? '[coc.nvim]' : '['.a:name.']'
  132. let data[0] = client.': '.data[0]
  133. call coc#ui#echo_messages('Error', data)
  134. endfunction
  135. function! s:on_exit(name, code) abort
  136. if get(g:, 'coc_vim_leaving', 0) | return | endif
  137. let client = get(s:clients, a:name, v:null)
  138. if empty(client) | return | endif
  139. if client['running'] != 1 | return | endif
  140. let client['running'] = 0
  141. let client['chan_id'] = 0
  142. let client['channel'] = v:null
  143. let client['async_req_id'] = 1
  144. if a:code != 0 && a:code != 143
  145. echohl Error | echom 'client '.a:name. ' abnormal exit with: '.a:code | echohl None
  146. endif
  147. endfunction
  148. function! coc#client#get_client(name) abort
  149. return get(s:clients, a:name, v:null)
  150. endfunction
  151. function! coc#client#get_channel(client)
  152. if s:is_vim
  153. return a:client['channel']
  154. endif
  155. return a:client['chan_id']
  156. endfunction
  157. function! s:request(method, args) dict
  158. let channel = coc#client#get_channel(self)
  159. if empty(channel) | return '' | endif
  160. try
  161. if s:is_vim
  162. let res = ch_evalexpr(channel, [a:method, a:args], {'timeout': 60 * 1000})
  163. if type(res) == 1 && res ==# ''
  164. throw 'request '.a:method. ' '.string(a:args).' timeout after 60s'
  165. endif
  166. let [l:errmsg, res] = res
  167. if !empty(l:errmsg)
  168. throw l:errmsg
  169. else
  170. return res
  171. endif
  172. else
  173. return call('rpcrequest', [channel, a:method] + a:args)
  174. endif
  175. catch /.*/
  176. if v:exception =~# 'E475'
  177. if get(g:, 'coc_vim_leaving', 0) | return | endif
  178. echohl Error | echom '['.self.name.'] server connection lost' | echohl None
  179. let name = self.name
  180. call s:on_exit(name, 0)
  181. execute 'silent do User ConnectionLost'.toupper(name[0]).name[1:]
  182. elseif v:exception =~# 'E12'
  183. " neovim's bug, ignore it
  184. else
  185. echohl Error | echo 'Error on request ('.a:method.'): '.v:exception | echohl None
  186. endif
  187. endtry
  188. endfunction
  189. function! s:notify(method, args) dict
  190. let channel = coc#client#get_channel(self)
  191. if empty(channel)
  192. return ''
  193. endif
  194. try
  195. if s:is_vim
  196. call ch_sendraw(channel, json_encode([0, [a:method, a:args]])."\n")
  197. else
  198. call call('rpcnotify', [channel, a:method] + a:args)
  199. endif
  200. catch /.*/
  201. if v:exception =~# 'E475'
  202. if get(g:, 'coc_vim_leaving', 0)
  203. return
  204. endif
  205. echohl Error | echom '['.self.name.'] server connection lost' | echohl None
  206. let name = self.name
  207. call s:on_exit(name, 0)
  208. execute 'silent do User ConnectionLost'.toupper(name[0]).name[1:]
  209. elseif v:exception =~# 'E12'
  210. " neovim's bug, ignore it
  211. else
  212. echohl Error | echo 'Error on notify ('.a:method.'): '.v:exception | echohl None
  213. endif
  214. endtry
  215. endfunction
  216. function! s:request_async(method, args, cb) dict
  217. let channel = coc#client#get_channel(self)
  218. if empty(channel) | return '' | endif
  219. if type(a:cb) != 2
  220. echohl Error | echom '['.self['name'].'] Callback should be function' | echohl None
  221. return
  222. endif
  223. let id = self.async_req_id
  224. let self.async_req_id = id + 1
  225. let self.async_callbacks[id] = a:cb
  226. call self['notify']('nvim_async_request_event', [id, a:method, a:args])
  227. endfunction
  228. function! s:on_async_response(id, resp, isErr) dict
  229. let Callback = get(self.async_callbacks, a:id, v:null)
  230. if empty(Callback)
  231. " should not happen
  232. echohl Error | echom 'callback not found' | echohl None
  233. return
  234. endif
  235. call remove(self.async_callbacks, a:id)
  236. if a:isErr
  237. call call(Callback, [a:resp, v:null])
  238. else
  239. call call(Callback, [v:null, a:resp])
  240. endif
  241. endfunction
  242. function! coc#client#is_running(name) abort
  243. let client = get(s:clients, a:name, v:null)
  244. if empty(client) | return 0 | endif
  245. if !client['running'] | return 0 | endif
  246. if s:is_vim
  247. let status = job_status(ch_getjob(client['channel']))
  248. return status ==# 'run'
  249. else
  250. let chan_id = client['chan_id']
  251. let [code] = jobwait([chan_id], 10)
  252. return code == -1
  253. endif
  254. endfunction
  255. function! coc#client#stop(name) abort
  256. let client = get(s:clients, a:name, v:null)
  257. if empty(client) | return 1 | endif
  258. let running = coc#client#is_running(a:name)
  259. if !running
  260. echohl WarningMsg | echom 'client '.a:name. ' not running.' | echohl None
  261. return 1
  262. endif
  263. if s:is_vim
  264. call job_stop(ch_getjob(client['channel']), 'term')
  265. else
  266. call jobstop(client['chan_id'])
  267. endif
  268. sleep 200m
  269. if coc#client#is_running(a:name)
  270. echohl Error | echom 'client '.a:name. ' stop failed.' | echohl None
  271. return 0
  272. endif
  273. call s:on_exit(a:name, 0)
  274. echohl MoreMsg | echom 'client '.a:name.' stopped!' | echohl None
  275. return 1
  276. endfunction
  277. function! coc#client#request(name, method, args)
  278. let client = get(s:clients, a:name, v:null)
  279. if !empty(client)
  280. return client['request'](a:method, a:args)
  281. endif
  282. endfunction
  283. function! coc#client#notify(name, method, args)
  284. let client = get(s:clients, a:name, v:null)
  285. if !empty(client)
  286. call client['notify'](a:method, a:args)
  287. endif
  288. endfunction
  289. function! coc#client#request_async(name, method, args, cb)
  290. let client = get(s:clients, a:name, v:null)
  291. if !empty(client)
  292. call client['request_async'](a:method, a:args, a:cb)
  293. endif
  294. endfunction
  295. function! coc#client#on_response(name, id, resp, isErr)
  296. let client = get(s:clients, a:name, v:null)
  297. if !empty(client)
  298. call client['on_async_response'](a:id, a:resp, a:isErr)
  299. endif
  300. endfunction
  301. function! coc#client#restart(name) abort
  302. let stopped = coc#client#stop(a:name)
  303. if !stopped | return | endif
  304. let client = get(s:clients, a:name, v:null)
  305. if !empty(client)
  306. call client['start']()
  307. endif
  308. endfunction
  309. function! coc#client#restart_all()
  310. for key in keys(s:clients)
  311. call coc#client#restart(key)
  312. endfor
  313. endfunction
  314. function! coc#client#open_log()
  315. if !get(g:, 'node_client_debug', 0)
  316. echohl Error | echon '[coc.nvim] use let g:node_client_debug = 1 in your vimrc to enabled debug mode.' | echohl None
  317. return
  318. endif
  319. execute 'vs '.s:logfile
  320. endfunction