pum.vim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. scriptencoding utf-8
  2. let s:is_vim = !has('nvim')
  3. let s:float = has('nvim-0.4.0') || has('patch-8.1.1719')
  4. let s:pum_bufnr = 0
  5. let s:pum_winid = 0
  6. let s:pum_index = -1
  7. let s:inserted = 0
  8. let s:virtual_text = 0
  9. let s:virtual_text_ns = 0
  10. let s:ignore = s:is_vim || has('nvim-0.5.0') ? "\<Ignore>" : "\<space>\<bs>"
  11. let s:hide_pum = has('nvim-0.6.1') || has('patch-8.2.3389')
  12. function! coc#pum#visible() abort
  13. if !s:float || !s:pum_winid
  14. return 0
  15. endif
  16. return getwinvar(s:pum_winid, 'float', 0) == 1
  17. endfunction
  18. function! coc#pum#winid() abort
  19. return s:pum_winid
  20. endfunction
  21. function! coc#pum#close_detail() abort
  22. let winid = coc#float#get_float_by_kind('pumdetail')
  23. if winid
  24. call coc#float#close(winid)
  25. if s:is_vim
  26. call timer_start(0, { -> execute('redraw')})
  27. endif
  28. endif
  29. endfunction
  30. function! coc#pum#close(...) abort
  31. if coc#float#valid(s:pum_winid)
  32. if get(a:, 1, '') ==# 'cancel'
  33. let input = getwinvar(s:pum_winid, 'input', '')
  34. let s:pum_index = -1
  35. call s:insert_word(input)
  36. call s:on_pum_change(0)
  37. doautocmd TextChangedI
  38. elseif get(a:, 1, '') ==# 'confirm'
  39. let words = getwinvar(s:pum_winid, 'words', [])
  40. if s:pum_index >= 0
  41. let word = get(words, s:pum_index, '')
  42. call s:insert_word(word)
  43. endif
  44. doautocmd TextChangedI
  45. endif
  46. call s:close_pum()
  47. if !get(a:, 2, 0)
  48. let pretext = strpart(getline('.'), 0, col('.') - 1)
  49. call coc#rpc#notify('CompleteStop', [get(a:, 1, ''), pretext])
  50. endif
  51. endif
  52. endfunction
  53. function! coc#pum#select_confirm() abort
  54. if s:pum_index < 0
  55. let s:pum_index = 0
  56. call s:on_pum_change(0)
  57. endif
  58. call coc#pum#close('confirm')
  59. endfunction
  60. function! coc#pum#insert() abort
  61. call timer_start(10, { -> s:insert_current()})
  62. return s:ignore
  63. endfunction
  64. function! coc#pum#_close() abort
  65. if coc#float#valid(s:pum_winid)
  66. call s:close_pum()
  67. if s:is_vim
  68. call timer_start(0, { -> execute('redraw')})
  69. endif
  70. endif
  71. endfunction
  72. function! s:insert_current() abort
  73. if coc#float#valid(s:pum_winid)
  74. if s:pum_index >= 0
  75. let words = getwinvar(s:pum_winid, 'words', [])
  76. let word = get(words, s:pum_index, '')
  77. call s:insert_word(word)
  78. endif
  79. doautocmd TextChangedI
  80. call s:close_pum()
  81. let pretext = strpart(getline('.'), 0, col('.') - 1)
  82. call coc#rpc#notify('CompleteStop', ['', pretext])
  83. endif
  84. endfunction
  85. function! s:close_pum() abort
  86. call s:clear_virtual_text()
  87. call coc#float#close(s:pum_winid)
  88. let s:pum_winid = 0
  89. let winid = coc#float#get_float_by_kind('pumdetail')
  90. if winid
  91. call coc#float#close(winid)
  92. endif
  93. endfunction
  94. function! coc#pum#next(insert) abort
  95. call timer_start(10, { -> s:navigate(1, a:insert)})
  96. return s:ignore
  97. endfunction
  98. function! coc#pum#prev(insert) abort
  99. call timer_start(10, { -> s:navigate(0, a:insert)})
  100. return s:ignore
  101. endfunction
  102. function! coc#pum#stop() abort
  103. call timer_start(10, { -> coc#pum#close()})
  104. return s:ignore
  105. endfunction
  106. function! coc#pum#cancel() abort
  107. call timer_start(10, { -> coc#pum#close('cancel')})
  108. return s:ignore
  109. endfunction
  110. function! coc#pum#confirm() abort
  111. call timer_start(10, { -> coc#pum#close('confirm')})
  112. return s:ignore
  113. endfunction
  114. function! coc#pum#select(index, insert, confirm) abort
  115. if !coc#float#valid(s:pum_winid)
  116. return ''
  117. endif
  118. if a:index == -1
  119. call coc#pum#close('cancel')
  120. return ''
  121. endif
  122. let total = coc#compat#buf_line_count(s:pum_bufnr)
  123. if a:index < 0 || a:index >= total
  124. throw 'index out of range ' . a:index
  125. endif
  126. call s:select_by_index(a:index, a:insert)
  127. if a:confirm
  128. call coc#pum#close('confirm')
  129. endif
  130. return ''
  131. endfunction
  132. function! coc#pum#info() abort
  133. let bufnr = winbufnr(s:pum_winid)
  134. let size = coc#compat#buf_line_count(bufnr)
  135. let words = getwinvar(s:pum_winid, 'words', [])
  136. let word = s:pum_index < 0 ? '' : get(words, s:pum_index, '')
  137. if s:is_vim
  138. let pos = popup_getpos(s:pum_winid)
  139. let add = pos['scrollbar'] && has_key(popup_getoptions(s:pum_winid), 'border') ? 1 : 0
  140. return {
  141. \ 'word': word,
  142. \ 'index': s:pum_index,
  143. \ 'scrollbar': pos['scrollbar'],
  144. \ 'row': pos['line'] - 1,
  145. \ 'col': pos['col'] - 1,
  146. \ 'width': pos['width'] + add,
  147. \ 'height': pos['height'],
  148. \ 'size': size,
  149. \ 'inserted': s:inserted ? v:true : v:false,
  150. \ }
  151. else
  152. let scrollbar = coc#float#get_related(s:pum_winid, 'scrollbar')
  153. let winid = coc#float#get_related(s:pum_winid, 'border', s:pum_winid)
  154. let pos = nvim_win_get_position(winid)
  155. return {
  156. \ 'word': word,
  157. \ 'index': s:pum_index,
  158. \ 'scrollbar': scrollbar && nvim_win_is_valid(scrollbar) ? 1 : 0,
  159. \ 'row': pos[0],
  160. \ 'col': pos[1],
  161. \ 'width': nvim_win_get_width(winid),
  162. \ 'height': nvim_win_get_height(winid),
  163. \ 'size': size,
  164. \ 'inserted': s:inserted ? v:true : v:false,
  165. \ }
  166. endif
  167. endfunction
  168. function! coc#pum#scroll(forward) abort
  169. if coc#pum#visible()
  170. let size = coc#compat#buf_line_count(s:pum_bufnr)
  171. let height = s:get_height(s:pum_winid)
  172. if size > height
  173. call timer_start(10, { -> s:scroll_pum(a:forward, height, size)})
  174. endif
  175. endif
  176. return s:ignore
  177. endfunction
  178. function! s:get_height(winid) abort
  179. if has('nvim')
  180. return nvim_win_get_height(a:winid)
  181. endif
  182. return get(popup_getpos(a:winid), 'core_height', 0)
  183. endfunction
  184. function! s:scroll_pum(forward, height, size) abort
  185. let topline = s:get_topline(s:pum_winid)
  186. if !a:forward && topline == 1
  187. if s:pum_index >= 0
  188. call s:select_line(s:pum_winid, 1)
  189. call s:on_pum_change(1)
  190. endif
  191. return
  192. endif
  193. if a:forward && topline + a:height - 1 >= a:size
  194. if s:pum_index >= 0
  195. call s:select_line(s:pum_winid, a:size)
  196. call s:on_pum_change(1)
  197. endif
  198. return
  199. endif
  200. call coc#float#scroll_win(s:pum_winid, a:forward, a:height)
  201. if s:pum_index >= 0
  202. let lnum = s:pum_index + 1
  203. let topline = s:get_topline(s:pum_winid)
  204. if lnum >= topline && lnum <= topline + a:height - 1
  205. return
  206. endif
  207. call s:select_line(s:pum_winid, topline)
  208. call s:on_pum_change(1)
  209. endif
  210. endfunction
  211. function! s:get_topline(winid) abort
  212. if has('nvim')
  213. let info = getwininfo(a:winid)[0]
  214. return info['topline']
  215. else
  216. let pos = popup_getpos(a:winid)
  217. return pos['firstline']
  218. endif
  219. endfunction
  220. function! s:navigate(next, insert) abort
  221. if !coc#float#valid(s:pum_winid)
  222. return
  223. endif
  224. let index = s:get_index(a:next)
  225. call s:select_by_index(index, a:insert)
  226. endfunction
  227. function! s:select_by_index(index, insert) abort
  228. call s:set_cursor(s:pum_winid, a:index + 1)
  229. if !s:is_vim
  230. call coc#float#nvim_scrollbar(s:pum_winid)
  231. endif
  232. if a:insert
  233. let s:inserted = 1
  234. if a:index < 0
  235. let input = getwinvar(s:pum_winid, 'input', '')
  236. call s:insert_word(input)
  237. call coc#pum#close_detail()
  238. else
  239. let words = getwinvar(s:pum_winid, 'words', [])
  240. let word = get(words, a:index, '')
  241. call s:insert_word(word)
  242. endif
  243. doautocmd TextChangedP
  244. endif
  245. call s:on_pum_change(1)
  246. endfunction
  247. function! s:get_index(next) abort
  248. let size = coc#compat#buf_line_count(s:pum_bufnr)
  249. if a:next
  250. let index = s:pum_index + 1 == size ? -1 : s:pum_index + 1
  251. else
  252. let index = s:pum_index == -1 ? size - 1 : s:pum_index - 1
  253. endif
  254. return index
  255. endfunction
  256. function! s:insert_word(word) abort
  257. let parts = getwinvar(s:pum_winid, 'parts', [])
  258. if !empty(parts) && mode() ==# 'i'
  259. let curr = getline('.')
  260. if curr ==# parts[0].a:word.parts[1]
  261. return
  262. endif
  263. let saved_completeopt = &completeopt
  264. if saved_completeopt =~ 'menuone'
  265. noa set completeopt=menu
  266. endif
  267. noa call complete(strlen(parts[0]) + 1, [a:word])
  268. if s:hide_pum
  269. " exit complete state
  270. call feedkeys("\<C-x>\<C-z>", 'in')
  271. else
  272. let g:coc_disable_space_report = 1
  273. call feedkeys("\<space>\<bs>", 'in')
  274. endif
  275. execute 'noa set completeopt='.saved_completeopt
  276. endif
  277. endfunction
  278. " create or update pum with lines, CompleteOption and config.
  279. " return winid & dimension
  280. function! coc#pum#create(lines, opt, config) abort
  281. if mode() !=# 'i' || a:opt['line'] != line('.')
  282. return
  283. endif
  284. let len = col('.') - a:opt['col'] - 1
  285. if len < 0
  286. return
  287. endif
  288. let input = len == 0 ? '' : strpart(getline('.'), a:opt['col'], len)
  289. if input !=# a:opt['input']
  290. return
  291. endif
  292. let config = s:get_pum_dimension(a:lines, a:opt['col'], a:config)
  293. if empty(config)
  294. return
  295. endif
  296. let s:virtual_text = has('nvim-0.5.0') && a:opt['virtualText']
  297. if s:virtual_text && !s:virtual_text_ns
  298. let s:virtual_text_ns = coc#highlight#create_namespace('pum-virtual')
  299. endif
  300. let selected = a:opt['index'] + 1
  301. call extend(config, {
  302. \ 'lines': a:lines,
  303. \ 'relative': 'cursor',
  304. \ 'nopad': 1,
  305. \ 'cursorline': 1,
  306. \ 'index': a:opt['index'],
  307. \ 'focusable': v:false
  308. \ })
  309. call extend(config, coc#dict#pick(a:config, ['highlight', 'rounded', 'highlights', 'winblend', 'shadow', 'border', 'borderhighlight']))
  310. let result = coc#float#create_float_win(s:pum_winid, s:pum_bufnr, config)
  311. if empty(result)
  312. return
  313. endif
  314. let s:inserted = 0
  315. let s:pum_winid = result[0]
  316. let s:pum_bufnr = result[1]
  317. call setwinvar(s:pum_winid, 'above', config['row'] < 0)
  318. let lnum = max([1, a:opt['index'] + 1])
  319. if s:is_vim
  320. call popup_setoptions(s:pum_winid, {
  321. \ 'firstline': s:get_firstline(lnum, len(a:lines), config['height'])
  322. \ })
  323. else
  324. let firstline = s:get_firstline(lnum, len(a:lines), config['height'])
  325. call coc#compat#execute(s:pum_winid, 'call winrestview({"lnum":'.lnum.',"topline":'.firstline.'})')
  326. endif
  327. let s:pum_index = get(config, 'index', -1)
  328. call coc#dialog#place_sign(s:pum_bufnr, s:pum_index + 1)
  329. call setwinvar(s:pum_winid, 'kind', 'pum')
  330. " content before col and content after cursor
  331. let linetext = getline('.')
  332. let parts = [strpart(linetext, 0, a:opt['col']), strpart(linetext, col('.') - 1)]
  333. call setwinvar(s:pum_winid, 'input', input)
  334. call setwinvar(s:pum_winid, 'parts', parts)
  335. call setwinvar(s:pum_winid, 'words', a:opt['words'])
  336. if !s:is_vim
  337. if len(a:lines) > config['height']
  338. redraw
  339. call coc#float#nvim_scrollbar(s:pum_winid)
  340. else
  341. call coc#float#close_related(s:pum_winid, 'scrollbar')
  342. endif
  343. endif
  344. call timer_start(10, { -> s:on_pum_change(0)})
  345. endfunction
  346. function! s:get_firstline(lnum, total, height) abort
  347. if a:lnum <= a:height
  348. return 1
  349. endif
  350. return min([a:total - a:height + 1, a:lnum - (a:height*2/3)])
  351. endfunction
  352. function! s:on_pum_change(move) abort
  353. if coc#float#valid(s:pum_winid)
  354. if s:virtual_text_ns
  355. call s:insert_virtual_text()
  356. endif
  357. let ev = extend(coc#pum#info(), {'move': a:move ? v:true : v:false})
  358. call coc#rpc#notify('CocAutocmd', ['MenuPopupChanged', ev, win_screenpos(winnr())[0] + winline() - 2])
  359. endif
  360. endfunction
  361. function! s:get_pum_dimension(lines, col, config) abort
  362. let linecount = len(a:lines)
  363. let [lineIdx, colIdx] = coc#cursor#screen_pos()
  364. let bh = empty(get(a:config, 'border', [])) ? 0 : 2
  365. let width = min([&columns, max([exists('&pumwidth') ? &pumwidth : 15, a:config['width']])])
  366. let vh = &lines - &cmdheight - 1 - !empty(&tabline)
  367. if vh <= 0
  368. return v:null
  369. endif
  370. let pumheight = empty(&pumheight) ? vh : &pumheight
  371. let showTop = getwinvar(s:pum_winid, 'above', v:null)
  372. if type(showTop) != v:t_number
  373. if vh - lineIdx - bh - 1 < min([pumheight, linecount]) && lineIdx > vh - lineIdx
  374. let showTop = 1
  375. else
  376. let showTop = 0
  377. endif
  378. endif
  379. let height = showTop ? min([lineIdx - bh - !empty(&tabline), linecount, pumheight]) : min([vh - lineIdx - bh - 1, linecount, pumheight])
  380. if height <= 0
  381. return v:null
  382. endif
  383. let col = - (col('.') - a:col - 1) - 1
  384. let row = showTop ? - height : 1
  385. let delta = colIdx + col
  386. if delta < 0
  387. let col = col - delta
  388. elseif delta + width > &columns
  389. let col = max([-colIdx, col - (delta + width - &columns)])
  390. endif
  391. return {
  392. \ 'row': row,
  393. \ 'col': col,
  394. \ 'width': width,
  395. \ 'height': height
  396. \ }
  397. endfunction
  398. " can't use coc#dialog#set_cursor on vim8, don't know why
  399. function! s:set_cursor(winid, line) abort
  400. if s:is_vim
  401. let pos = popup_getpos(a:winid)
  402. let lastline = pos['firstline'] + pos['core_height'] - 1
  403. if a:line > lastline
  404. call popup_setoptions(a:winid, {
  405. \ 'firstline': pos['firstline'] + a:line - lastline,
  406. \ })
  407. elseif a:line < pos['firstline']
  408. call popup_setoptions(a:winid, {
  409. \ 'firstline': max([1, a:line]),
  410. \ })
  411. endif
  412. endif
  413. call s:select_line(a:winid, a:line)
  414. endfunction
  415. function! s:select_line(winid, line) abort
  416. let s:pum_index = a:line - 1
  417. let lnum = max([1, a:line])
  418. if s:is_vim
  419. call coc#compat#execute(a:winid, 'exe '.lnum)
  420. else
  421. call nvim_win_set_cursor(a:winid, [lnum, 0])
  422. endif
  423. call coc#dialog#place_sign(winbufnr(a:winid), a:line)
  424. endfunction
  425. function! s:insert_virtual_text() abort
  426. if !s:virtual_text_ns
  427. return
  428. endif
  429. let bufnr = bufnr('%')
  430. if !s:virtual_text || !coc#pum#visible() || s:pum_index < 0
  431. call nvim_buf_clear_namespace(bufnr, s:virtual_text_ns, 0, -1)
  432. else
  433. " Check if could create
  434. let insert = ''
  435. let words = getwinvar(s:pum_winid, 'words', [])
  436. let word = get(words, s:pum_index, '')
  437. let parts = getwinvar(s:pum_winid, 'parts', [])
  438. let input = strpart(getline('.'), strlen(parts[0]), col('.') - 1)
  439. if strchars(word) > strchars(input) && strcharpart(word, 0, strchars(input)) ==# input
  440. let insert = strcharpart(word, strchars(input))
  441. endif
  442. call nvim_buf_clear_namespace(bufnr, s:virtual_text_ns, 0, -1)
  443. if !empty(insert)
  444. let opts = {
  445. \ 'hl_mode': 'combine',
  446. \ 'virt_text': [[insert, 'CocPumVirtualText']],
  447. \ 'virt_text_pos': 'overlay',
  448. \ 'virt_text_win_col': virtcol('.') - 1,
  449. \ }
  450. call nvim_buf_set_extmark(bufnr, s:virtual_text_ns, line('.') - 1, col('.') - 1, opts)
  451. endif
  452. endif
  453. endfunction
  454. function! s:clear_virtual_text() abort
  455. if s:virtual_text_ns
  456. call nvim_buf_clear_namespace(bufnr('%'), s:virtual_text_ns, 0, -1)
  457. endif
  458. endfunction