ui.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. let s:is_vim = !has('nvim')
  2. let s:is_win = has('win32') || has('win64')
  3. let s:is_mac = has('mac')
  4. let s:sign_api = exists('*sign_getplaced') && exists('*sign_place')
  5. let s:sign_groups = []
  6. " Check <Tab> and <CR>
  7. function! coc#ui#check_pum_keymappings() abort
  8. let keys = []
  9. for key in ['<cr>', '<tab>']
  10. if maparg(key, 'i') =~# 'pumvisible()'
  11. call add(keys, key)
  12. endif
  13. endfor
  14. if len(keys)
  15. let lines = [
  16. \ 'coc.nvim switched to custom popup menu from 0.0.82',
  17. \ 'you have to change key-mappings for '.join(keys, ', ').' to make them work.',
  18. \ 'see :h coc-completion-example']
  19. call coc#notify#create(lines, {
  20. \ 'borderhighlight': 'CocInfoSign',
  21. \ 'timeout': 30000,
  22. \ 'kind': 'warning',
  23. \ })
  24. endif
  25. endfunction
  26. function! coc#ui#quickpick(title, items, cb) abort
  27. if exists('*popup_menu')
  28. function! s:QuickpickHandler(id, result) closure
  29. call a:cb(v:null, a:result)
  30. endfunction
  31. function! s:QuickpickFilter(id, key) closure
  32. for i in range(1, len(a:items))
  33. if a:key == string(i)
  34. call popup_close(a:id, i)
  35. return 1
  36. endif
  37. endfor
  38. " No shortcut, pass to generic filter
  39. return popup_filter_menu(a:id, a:key)
  40. endfunction
  41. try
  42. call popup_menu(a:items, {
  43. \ 'title': a:title,
  44. \ 'filter': function('s:QuickpickFilter'),
  45. \ 'callback': function('s:QuickpickHandler'),
  46. \ })
  47. redraw
  48. catch /.*/
  49. call a:cb(v:exception)
  50. endtry
  51. else
  52. let res = inputlist([a:title] + a:items)
  53. call a:cb(v:null, res)
  54. endif
  55. endfunction
  56. " cmd, cwd
  57. function! coc#ui#open_terminal(opts) abort
  58. if s:is_vim && !exists('*term_start')
  59. echohl WarningMsg | echon "Your vim doesn't have terminal support!" | echohl None
  60. return
  61. endif
  62. if get(a:opts, 'position', 'bottom') ==# 'bottom'
  63. let p = '5new'
  64. else
  65. let p = 'vnew'
  66. endif
  67. execute 'belowright '.p.' +setl\ buftype=nofile '
  68. setl buftype=nofile
  69. setl winfixheight
  70. setl norelativenumber
  71. setl nonumber
  72. setl bufhidden=wipe
  73. if exists('#User#CocTerminalOpen')
  74. exe 'doautocmd <nomodeline> User CocTerminalOpen'
  75. endif
  76. let cmd = get(a:opts, 'cmd', '')
  77. let autoclose = get(a:opts, 'autoclose', 1)
  78. if empty(cmd)
  79. throw 'command required!'
  80. endif
  81. let cwd = get(a:opts, 'cwd', getcwd())
  82. let keepfocus = get(a:opts, 'keepfocus', 0)
  83. let bufnr = bufnr('%')
  84. let Callback = get(a:opts, 'Callback', v:null)
  85. function! s:OnExit(status) closure
  86. let content = join(getbufline(bufnr, 1, '$'), "\n")
  87. if a:status == 0 && autoclose == 1
  88. execute 'silent! bd! '.bufnr
  89. endif
  90. if !empty(Callback)
  91. call call(Callback, [a:status, bufnr, content])
  92. endif
  93. endfunction
  94. if has('nvim')
  95. call termopen(cmd, {
  96. \ 'cwd': cwd,
  97. \ 'on_exit': {job, status -> s:OnExit(status)},
  98. \})
  99. else
  100. if s:is_win
  101. let cmd = 'cmd.exe /C "'.cmd.'"'
  102. endif
  103. call term_start(cmd, {
  104. \ 'cwd': cwd,
  105. \ 'exit_cb': {job, status -> s:OnExit(status)},
  106. \ 'curwin': 1,
  107. \})
  108. endif
  109. if keepfocus
  110. wincmd p
  111. endif
  112. return bufnr
  113. endfunction
  114. " run command in terminal
  115. function! coc#ui#run_terminal(opts, cb)
  116. let cmd = get(a:opts, 'cmd', '')
  117. if empty(cmd)
  118. return a:cb('command required for terminal')
  119. endif
  120. let opts = {
  121. \ 'cmd': cmd,
  122. \ 'cwd': get(a:opts, 'cwd', getcwd()),
  123. \ 'keepfocus': get(a:opts, 'keepfocus', 0),
  124. \ 'Callback': {status, bufnr, content -> a:cb(v:null, {'success': status == 0 ? v:true : v:false, 'bufnr': bufnr, 'content': content})}
  125. \}
  126. call coc#ui#open_terminal(opts)
  127. endfunction
  128. function! coc#ui#echo_hover(msg)
  129. echohl MoreMsg
  130. echo a:msg
  131. echohl None
  132. let g:coc_last_hover_message = a:msg
  133. endfunction
  134. function! coc#ui#echo_messages(hl, msgs)
  135. if a:hl !~# 'Error' && (mode() !~# '\v^(i|n)$')
  136. return
  137. endif
  138. let msgs = filter(copy(a:msgs), '!empty(v:val)')
  139. if empty(msgs)
  140. return
  141. endif
  142. execute 'echohl '.a:hl
  143. echom a:msgs[0]
  144. redraw
  145. echo join(msgs, "\n")
  146. echohl None
  147. endfunction
  148. function! coc#ui#preview_info(lines, filetype, ...) abort
  149. pclose
  150. keepalt new +setlocal\ previewwindow|setlocal\ buftype=nofile|setlocal\ noswapfile|setlocal\ wrap [Document]
  151. setl bufhidden=wipe
  152. setl nobuflisted
  153. setl nospell
  154. exe 'setl filetype='.a:filetype
  155. setl conceallevel=0
  156. setl nofoldenable
  157. for command in a:000
  158. execute command
  159. endfor
  160. call append(0, a:lines)
  161. exe "normal! z" . len(a:lines) . "\<cr>"
  162. exe "normal! gg"
  163. wincmd p
  164. endfunction
  165. function! coc#ui#open_files(files)
  166. let bufnrs = []
  167. " added on latest vim8
  168. if exists('*bufadd') && exists('*bufload')
  169. for file in a:files
  170. let file = fnamemodify(file, ':.')
  171. if bufloaded(file)
  172. call add(bufnrs, bufnr(file))
  173. else
  174. let bufnr = bufadd(file)
  175. call bufload(file)
  176. call add(bufnrs, bufnr)
  177. call setbufvar(bufnr, '&buflisted', 1)
  178. endif
  179. endfor
  180. else
  181. noa keepalt 1new +setl\ bufhidden=wipe
  182. for file in a:files
  183. let file = fnamemodify(file, ':.')
  184. execute 'noa edit +setl\ bufhidden=hide '.fnameescape(file)
  185. if &filetype ==# ''
  186. filetype detect
  187. endif
  188. call add(bufnrs, bufnr('%'))
  189. endfor
  190. noa close
  191. endif
  192. doautocmd BufEnter
  193. return bufnrs
  194. endfunction
  195. function! coc#ui#echo_lines(lines)
  196. echo join(a:lines, "\n")
  197. endfunction
  198. function! coc#ui#echo_signatures(signatures) abort
  199. if pumvisible() | return | endif
  200. echo ""
  201. for i in range(len(a:signatures))
  202. call s:echo_signature(a:signatures[i])
  203. if i != len(a:signatures) - 1
  204. echon "\n"
  205. endif
  206. endfor
  207. endfunction
  208. function! s:echo_signature(parts)
  209. for part in a:parts
  210. let hl = get(part, 'type', 'Normal')
  211. let text = get(part, 'text', '')
  212. if !empty(text)
  213. execute 'echohl '.hl
  214. execute "echon '".substitute(text, "'", "''", 'g')."'"
  215. echohl None
  216. endif
  217. endfor
  218. endfunction
  219. function! coc#ui#iterm_open(dir)
  220. return s:osascript(
  221. \ 'if application "iTerm2" is not running',
  222. \ 'error',
  223. \ 'end if') && s:osascript(
  224. \ 'tell application "iTerm2"',
  225. \ 'tell current window',
  226. \ 'create tab with default profile',
  227. \ 'tell current session',
  228. \ 'write text "cd ' . a:dir . '"',
  229. \ 'write text "clear"',
  230. \ 'activate',
  231. \ 'end tell',
  232. \ 'end tell',
  233. \ 'end tell')
  234. endfunction
  235. function! s:osascript(...) abort
  236. let args = join(map(copy(a:000), '" -e ".shellescape(v:val)'), '')
  237. call s:system('osascript'. args)
  238. return !v:shell_error
  239. endfunction
  240. function! s:system(cmd)
  241. let output = system(a:cmd)
  242. if v:shell_error && output !=# ""
  243. echohl Error | echom output | echohl None
  244. return
  245. endif
  246. return output
  247. endfunction
  248. function! coc#ui#set_lines(bufnr, changedtick, original, replacement, start, end, changes, cursor, col) abort
  249. if !bufloaded(a:bufnr)
  250. return
  251. endif
  252. let delta = 0
  253. if !empty(a:col)
  254. let delta = col('.') - a:col
  255. endif
  256. if getbufvar(a:bufnr, 'changedtick') > a:changedtick && bufnr('%') == a:bufnr
  257. " try apply current line change
  258. let lnum = line('.')
  259. " change for current line
  260. if a:end - a:start == 1 && a:end == lnum && len(a:replacement) == 1
  261. let idx = a:start - lnum + 1
  262. let previous = get(a:original, idx, 0)
  263. if type(previous) == 1
  264. let content = getline('.')
  265. if previous !=# content
  266. let diff = coc#string#diff(content, previous, col('.'))
  267. let changed = get(a:replacement, idx, 0)
  268. if type(changed) == 1 && strcharpart(previous, 0, diff['end']) ==# strcharpart(changed, 0, diff['end'])
  269. let applied = coc#string#apply(changed, diff)
  270. let replacement = copy(a:replacement)
  271. let replacement[idx] = applied
  272. call coc#compat#buf_set_lines(a:bufnr, a:start, a:end, replacement)
  273. return
  274. endif
  275. endif
  276. endif
  277. endif
  278. endif
  279. if exists('*nvim_buf_set_text') && !empty(a:changes)
  280. for item in reverse(copy(a:changes))
  281. call nvim_buf_set_text(a:bufnr, item[1], item[2], item[3], item[4], item[0])
  282. endfor
  283. else
  284. call coc#compat#buf_set_lines(a:bufnr, a:start, a:end, a:replacement)
  285. endif
  286. if !empty(a:cursor)
  287. call cursor(a:cursor[0], a:cursor[1] + delta)
  288. endif
  289. endfunction
  290. function! coc#ui#change_lines(bufnr, list) abort
  291. if !bufloaded(a:bufnr) | return v:null | endif
  292. undojoin
  293. if exists('*setbufline')
  294. for [lnum, line] in a:list
  295. call setbufline(a:bufnr, lnum + 1, line)
  296. endfor
  297. elseif a:bufnr == bufnr('%')
  298. for [lnum, line] in a:list
  299. call setline(lnum + 1, line)
  300. endfor
  301. else
  302. let bufnr = bufnr('%')
  303. exe 'noa buffer '.a:bufnr
  304. for [lnum, line] in a:list
  305. call setline(lnum + 1, line)
  306. endfor
  307. exe 'noa buffer '.bufnr
  308. endif
  309. endfunction
  310. function! coc#ui#open_url(url)
  311. if has('mac') && executable('open')
  312. call system('open '.a:url)
  313. return
  314. endif
  315. if executable('xdg-open')
  316. call system('xdg-open '.a:url)
  317. return
  318. endif
  319. call system('cmd /c start "" /b '. substitute(a:url, '&', '^&', 'g'))
  320. if v:shell_error
  321. echohl Error | echom 'Failed to open '.a:url | echohl None
  322. return
  323. endif
  324. endfunction
  325. function! coc#ui#rename_file(oldPath, newPath, write) abort
  326. let bufnr = bufnr(a:oldPath)
  327. if bufnr == -1
  328. throw 'Unable to get bufnr of '.a:oldPath
  329. endif
  330. if a:oldPath =~? a:newPath && (s:is_mac || s:is_win)
  331. return coc#ui#safe_rename(bufnr, a:oldPath, a:newPath, a:write)
  332. endif
  333. if bufloaded(a:newPath)
  334. execute 'silent bdelete! '.bufnr(a:newPath)
  335. endif
  336. let current = bufnr == bufnr('%')
  337. let bufname = fnamemodify(a:newPath, ":~:.")
  338. let filepath = fnamemodify(bufname(bufnr), '%:p')
  339. let winid = coc#compat#buf_win_id(bufnr)
  340. let curr = -1
  341. if winid == -1
  342. let curr = win_getid()
  343. let file = fnamemodify(bufname(bufnr), ':.')
  344. execute 'keepalt tab drop '.fnameescape(bufname(bufnr))
  345. let winid = win_getid()
  346. endif
  347. call coc#compat#execute(winid, 'keepalt file '.fnameescape(bufname), 'silent')
  348. call coc#compat#execute(winid, 'doautocmd BufEnter')
  349. if a:write
  350. call coc#compat#execute(winid, 'noa write!', 'silent')
  351. call delete(filepath, '')
  352. endif
  353. if curr != -1
  354. call win_gotoid(curr)
  355. endif
  356. return bufnr
  357. endfunction
  358. " System is case in sensitive and newPath have different case.
  359. function! coc#ui#safe_rename(bufnr, oldPath, newPath, write) abort
  360. let winid = win_getid()
  361. let lines = getbufline(a:bufnr, 1, '$')
  362. execute 'keepalt tab drop '.fnameescape(fnamemodify(a:oldPath, ':.'))
  363. let view = winsaveview()
  364. execute 'keepalt bwipeout! '.a:bufnr
  365. if a:write
  366. call delete(a:oldPath, '')
  367. endif
  368. execute 'keepalt edit '.fnameescape(fnamemodify(a:newPath, ':~:.'))
  369. let bufnr = bufnr('%')
  370. call coc#compat#buf_set_lines(bufnr, 0, -1, lines)
  371. if a:write
  372. execute 'noa write'
  373. endif
  374. call winrestview(view)
  375. call win_gotoid(winid)
  376. return bufnr
  377. endfunction
  378. function! coc#ui#sign_unplace() abort
  379. if exists('*sign_unplace')
  380. for group in s:sign_groups
  381. call sign_unplace(group)
  382. endfor
  383. endif
  384. endfunction
  385. function! coc#ui#update_signs(bufnr, group, signs) abort
  386. if !s:sign_api || !bufloaded(a:bufnr)
  387. return
  388. endif
  389. if len(a:signs)
  390. call add(s:sign_groups, a:group)
  391. endif
  392. let current = get(get(sign_getplaced(a:bufnr, {'group': a:group}), 0, {}), 'signs', [])
  393. let exists = []
  394. let unplaceList = []
  395. for item in current
  396. let index = 0
  397. let placed = 0
  398. for def in a:signs
  399. if def['name'] ==# item['name'] && def['lnum'] == item['lnum']
  400. let placed = 1
  401. call add(exists, index)
  402. break
  403. endif
  404. let index = index + 1
  405. endfor
  406. if !placed
  407. call add(unplaceList, item['id'])
  408. endif
  409. endfor
  410. for idx in range(0, len(a:signs) - 1)
  411. if index(exists, idx) == -1
  412. let def = a:signs[idx]
  413. let opts = {'lnum': def['lnum']}
  414. if has_key(def, 'priority')
  415. let opts['priority'] = def['priority']
  416. endif
  417. call sign_place(0, a:group, def['name'], a:bufnr, opts)
  418. endif
  419. endfor
  420. for id in unplaceList
  421. call sign_unplace(a:group, {'buffer': a:bufnr, 'id': id})
  422. endfor
  423. endfunction