float.vim 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. scriptencoding utf-8
  2. let s:is_vim = !has('nvim')
  3. let s:borderchars = get(g:, 'coc_borderchars', ['─', '│', '─', '│', '┌', '┐', '┘', '└'])
  4. let s:rounded_borderchars = s:borderchars[0:3] + ['╭', '╮', '╯', '╰']
  5. let s:borderjoinchars = get(g:, 'coc_border_joinchars', ['┬', '┤', '┴', '├'])
  6. let s:popup_list_api = exists('*popup_list')
  7. " Popup ids, used when popup_list() doesn't exist
  8. let s:popup_list = []
  9. let s:pad_bufnr = -1
  10. " Check visible float/popup exists.
  11. function! coc#float#has_float(...) abort
  12. return len(coc#float#get_float_win_list(get(a:, 1, 0))) > 0
  13. endfunction
  14. function! coc#float#close_all(...) abort
  15. let winids = coc#float#get_float_win_list(get(a:, 1, 0))
  16. for id in winids
  17. try
  18. call coc#float#close(id)
  19. catch /E5555:/
  20. " ignore
  21. endtry
  22. endfor
  23. endfunction
  24. function! coc#float#jump() abort
  25. if has('nvim')
  26. let winids = coc#float#get_float_win_list()
  27. if !empty(winids)
  28. call win_gotoid(winids[0])
  29. endif
  30. endif
  31. endfunction
  32. function! coc#float#valid(winid) abort
  33. if a:winid <= 0
  34. return 0
  35. endif
  36. if !s:is_vim
  37. if !nvim_win_is_valid(a:winid)
  38. return 0
  39. endif
  40. return !empty(nvim_win_get_config(a:winid)['relative'])
  41. endif
  42. try
  43. return !empty(popup_getpos(a:winid))
  44. catch /^Vim\%((\a\+)\)\=:E993/
  45. " not a popup window
  46. return 0
  47. endtry
  48. endfunction
  49. function! coc#float#get_height(winid) abort
  50. if !s:is_vim
  51. let borderwin = coc#float#get_related(a:winid, 'border')
  52. if borderwin
  53. return nvim_win_get_height(borderwin)
  54. endif
  55. return nvim_win_get_height(a:winid)
  56. endif
  57. return get(popup_getpos(a:winid), 'height', 0)
  58. endfunction
  59. function! coc#float#change_height(winid, delta) abort
  60. if s:is_vim
  61. let curr = get(popup_getpos(a:winid), 'core_height', v:null)
  62. if curr isnot v:null
  63. call popup_move(a:winid, {
  64. \ 'maxheight': max([1, curr + a:delta]),
  65. \ 'minheight': max([1, curr + a:delta]),
  66. \ })
  67. endif
  68. else
  69. let winids = copy(coc#window#get_var(a:winid, 'related', []))
  70. call filter(winids, 'index(["border","pad","scrollbar"],coc#window#get_var(v:val,"kind","")) >= 0')
  71. call add(winids, a:winid)
  72. for winid in winids
  73. if coc#window#get_var(winid, 'kind', '') ==# 'border'
  74. let bufnr = winbufnr(winid)
  75. if a:delta > 0
  76. call appendbufline(bufnr, 1, repeat(getbufline(bufnr, 2), a:delta))
  77. else
  78. call deletebufline(bufnr, 2, 2 - a:delta - 1)
  79. endif
  80. endif
  81. let height = nvim_win_get_height(winid)
  82. call nvim_win_set_height(winid, max([1, height + a:delta]))
  83. endfor
  84. endif
  85. endfunction
  86. " create or config float window, returns [winid, bufnr], config including:
  87. " - relative: could be 'editor' 'cursor'
  88. " - row: line count relative to editor/cursor, nagetive number means abover cursor.
  89. " - col: column count relative to editor/cursor, nagetive number means left of cursor.
  90. " - width: content width without border and title.
  91. " - height: content height without border and title.
  92. " - lines: (optional) lines to insert, default to v:null.
  93. " - title: (optional) title.
  94. " - border: (optional) border as number list, like [1, 1, 1 ,1].
  95. " - cursorline: (optional) enable cursorline when is 1.
  96. " - autohide: (optional) window should be closed on CursorMoved when is 1.
  97. " - highlight: (optional) highlight of window, default to 'CocFloating'
  98. " - borderhighlight: (optional) should be array or string for border highlights,
  99. " highlight all borders with first value.
  100. " - close: (optional) show close button when is 1.
  101. " - highlights: (optional) highlight items.
  102. " - buttons: (optional) array of button text for create buttons at bottom.
  103. " - codes: (optional) list of CodeBlock.
  104. " - winblend: (optional) winblend option for float window, neovim only.
  105. " - shadow: (optional) use shadow as border style, neovim only.
  106. " - focusable: (optional) neovim only, default to true.
  107. " - scrollinside: (optional) neovim only, create scrollbar inside window.
  108. " - rounded: (optional) use rounded borderchars, ignored when borderchars exists.
  109. " - borderchars: (optional) borderchars, should be length of 8
  110. " - nopad: (optional) not add pad when 1
  111. " - index: (optional) line index
  112. function! coc#float#create_float_win(winid, bufnr, config) abort
  113. let lines = get(a:config, 'lines', v:null)
  114. let bufnr = a:bufnr
  115. try
  116. let bufnr = coc#float#create_buf(a:bufnr, lines, 'hide')
  117. catch /E523:/
  118. " happens when using getchar() #3921
  119. return []
  120. endtry
  121. let lnum = max([1, get(a:config, 'index', 0) + 1])
  122. " use exists
  123. if a:winid && coc#float#valid(a:winid)
  124. if s:is_vim
  125. let [line, col] = s:popup_position(a:config)
  126. let opts = {
  127. \ 'firstline': 1,
  128. \ 'line': line,
  129. \ 'col': col,
  130. \ 'minwidth': a:config['width'],
  131. \ 'minheight': a:config['height'],
  132. \ 'maxwidth': a:config['width'],
  133. \ 'maxheight': a:config['height'],
  134. \ 'title': get(a:config, 'title', ''),
  135. \ 'highlight': get(a:config, 'highlight', 'CocFloating'),
  136. \ 'borderhighlight': [s:get_borderhighlight(a:config)],
  137. \ }
  138. if !s:empty_border(get(a:config, 'border', []))
  139. let opts['border'] = a:config['border']
  140. endif
  141. call popup_setoptions(a:winid, opts)
  142. call win_execute(a:winid, 'exe '.lnum)
  143. call coc#float#vim_buttons(a:winid, a:config)
  144. call s:add_highlights(a:winid, a:config, 0)
  145. return [a:winid, winbufnr(a:winid)]
  146. else
  147. let config = s:convert_config_nvim(a:config, 0)
  148. let hlgroup = get(a:config, 'highlight', 'CocFloating')
  149. let winhl = 'Normal:'.hlgroup.',NormalNC:'.hlgroup.',FoldColumn:'.hlgroup
  150. if winhl !=# getwinvar(a:winid, '&winhl', '')
  151. call setwinvar(a:winid, '&winhl', winhl)
  152. endif
  153. call nvim_win_set_buf(a:winid, bufnr)
  154. call nvim_win_set_config(a:winid, config)
  155. call nvim_win_set_cursor(a:winid, [lnum, 0])
  156. call coc#float#nvim_create_related(a:winid, config, a:config)
  157. call s:add_highlights(a:winid, a:config, 0)
  158. return [a:winid, bufnr]
  159. endif
  160. endif
  161. let winid = 0
  162. if s:is_vim
  163. let [line, col] = s:popup_position(a:config)
  164. let title = get(a:config, 'title', '')
  165. let buttons = get(a:config, 'buttons', [])
  166. let hlgroup = get(a:config, 'highlight', 'CocFloating')
  167. let nopad = get(a:config, 'nopad', 0)
  168. let border = s:empty_border(get(a:config, 'border', [])) ? [0, 0, 0, 0] : a:config['border']
  169. let opts = {
  170. \ 'title': title,
  171. \ 'line': line,
  172. \ 'col': col,
  173. \ 'fixed': 1,
  174. \ 'padding': [0, !nopad && !border[1], 0, !nopad && !border[3]],
  175. \ 'borderchars': s:get_borderchars(a:config),
  176. \ 'highlight': hlgroup,
  177. \ 'minwidth': a:config['width'],
  178. \ 'minheight': a:config['height'],
  179. \ 'maxwidth': a:config['width'],
  180. \ 'maxheight': a:config['height'],
  181. \ 'close': get(a:config, 'close', 0) ? 'button' : 'none',
  182. \ 'border': border,
  183. \ 'callback': { -> coc#float#on_close(winid)},
  184. \ 'borderhighlight': [s:get_borderhighlight(a:config)],
  185. \ }
  186. let winid = popup_create(bufnr, opts)
  187. if !s:popup_list_api
  188. call add(s:popup_list, winid)
  189. endif
  190. call s:set_float_defaults(winid, a:config)
  191. call win_execute(winid, 'exe '.lnum)
  192. call coc#float#vim_buttons(winid, a:config)
  193. else
  194. let config = s:convert_config_nvim(a:config, 1)
  195. let border = get(a:config, 'border', [])
  196. if has('nvim-0.5.0') && get(a:config, 'shadow', 0) && empty(get(a:config, 'buttons', v:null)) && empty(get(border, 2, 0))
  197. let config['border'] = 'shadow'
  198. endif
  199. noa let winid = nvim_open_win(bufnr, 0, config)
  200. if winid is 0
  201. return []
  202. endif
  203. " cursorline highlight not work on old neovim
  204. call s:set_float_defaults(winid, a:config)
  205. call nvim_win_set_cursor(winid, [lnum, 0])
  206. call coc#float#nvim_create_related(winid, config, a:config)
  207. call coc#float#nvim_set_winblend(winid, get(a:config, 'winblend', v:null))
  208. endif
  209. call s:add_highlights(winid, a:config, 1)
  210. let g:coc_last_float_win = winid
  211. call coc#util#do_autocmd('CocOpenFloat')
  212. return [winid, bufnr]
  213. endfunction
  214. function! coc#float#nvim_create_related(winid, config, opts) abort
  215. let related = getwinvar(a:winid, 'related', [])
  216. let exists = !empty(related)
  217. let border = get(a:opts, 'border', [])
  218. let borderhighlight = s:get_borderhighlight(a:opts)
  219. let buttons = get(a:opts, 'buttons', [])
  220. let pad = !get(a:opts, 'nopad', 0) && (empty(border) || get(border, 1, 0) == 0)
  221. let shadow = get(a:opts, 'shadow', 0)
  222. if get(a:opts, 'close', 0)
  223. call coc#float#nvim_close_btn(a:config, a:winid, border, borderhighlight, related)
  224. elseif exists
  225. call coc#float#close_related(a:winid, 'close')
  226. endif
  227. if !empty(buttons)
  228. call coc#float#nvim_buttons(a:config, a:winid, buttons, get(a:opts, 'getchar', 0), get(border, 2, 0), pad, borderhighlight, shadow, related)
  229. elseif exists
  230. call coc#float#close_related(a:winid, 'buttons')
  231. endif
  232. if !s:empty_border(border)
  233. let borderchars = s:get_borderchars(a:opts)
  234. call coc#float#nvim_border_win(a:config, borderchars, a:winid, border, get(a:opts, 'title', ''), !empty(buttons), borderhighlight, shadow, related)
  235. elseif exists
  236. call coc#float#close_related(a:winid, 'border')
  237. endif
  238. " Check right border
  239. if pad
  240. call coc#float#nvim_right_pad(a:config, a:winid, related)
  241. elseif exists
  242. call coc#float#close_related(a:winid, 'pad')
  243. endif
  244. call setwinvar(a:winid, 'related', filter(related, 'nvim_win_is_valid(v:val)'))
  245. endfunction
  246. " border window for neovim, content config with border
  247. function! coc#float#nvim_border_win(config, borderchars, winid, border, title, hasbtn, hlgroup, shadow, related) abort
  248. let winid = coc#float#get_related(a:winid, 'border')
  249. let row = a:border[0] ? a:config['row'] - 1 : a:config['row']
  250. let col = a:border[3] ? a:config['col'] - 1 : a:config['col']
  251. let width = a:config['width'] + a:border[1] + a:border[3]
  252. let height = a:config['height'] + a:border[0] + a:border[2] + (a:hasbtn ? 2 : 0)
  253. let lines = coc#float#create_border_lines(a:border, a:borderchars, a:title, a:config['width'], a:config['height'], a:hasbtn)
  254. let bufnr = winid ? winbufnr(winid) : 0
  255. let bufnr = coc#float#create_buf(bufnr, lines)
  256. let opt = {
  257. \ 'relative': a:config['relative'],
  258. \ 'width': width,
  259. \ 'height': height,
  260. \ 'row': row,
  261. \ 'col': col,
  262. \ 'focusable': v:false,
  263. \ 'style': 'minimal',
  264. \ }
  265. if has('nvim-0.5.0') && a:shadow && !a:hasbtn && a:border[2]
  266. let opt['border'] = 'shadow'
  267. endif
  268. if winid
  269. call nvim_win_set_config(winid, opt)
  270. call setwinvar(winid, '&winhl', 'Normal:'.a:hlgroup.',NormalNC:'.a:hlgroup)
  271. else
  272. noa let winid = nvim_open_win(bufnr, 0, opt)
  273. call setwinvar(winid, 'delta', -1)
  274. let winhl = 'Normal:'.a:hlgroup.',NormalNC:'.a:hlgroup
  275. call s:nvim_add_related(winid, a:winid, 'border', winhl, a:related)
  276. endif
  277. endfunction
  278. " neovim only
  279. function! coc#float#nvim_close_btn(config, winid, border, hlgroup, related) abort
  280. let winid = coc#float#get_related(a:winid, 'close')
  281. let config = {
  282. \ 'relative': a:config['relative'],
  283. \ 'width': 1,
  284. \ 'height': 1,
  285. \ 'row': get(a:border, 0, 0) ? a:config['row'] - 1 : a:config['row'],
  286. \ 'col': a:config['col'] + a:config['width'],
  287. \ 'focusable': v:true,
  288. \ 'style': 'minimal',
  289. \ }
  290. if has('nvim-0.5.1')
  291. let config['zindex'] = 300
  292. endif
  293. if winid
  294. call nvim_win_set_config(winid, coc#dict#pick(config, ['relative', 'row', 'col']))
  295. else
  296. let bufnr = coc#float#create_buf(0, ['X'])
  297. noa let winid = nvim_open_win(bufnr, 0, config)
  298. let winhl = 'Normal:'.a:hlgroup.',NormalNC:'.a:hlgroup
  299. call s:nvim_add_related(winid, a:winid, 'close', winhl, a:related)
  300. endif
  301. endfunction
  302. " Create padding window by config of current window & border config
  303. function! coc#float#nvim_right_pad(config, winid, related) abort
  304. let winid = coc#float#get_related(a:winid, 'pad')
  305. let config = {
  306. \ 'relative': a:config['relative'],
  307. \ 'width': 1,
  308. \ 'height': a:config['height'],
  309. \ 'row': a:config['row'],
  310. \ 'col': a:config['col'] + a:config['width'],
  311. \ 'focusable': v:false,
  312. \ 'style': 'minimal',
  313. \ }
  314. if has('nvim-0.5.1')
  315. let config['zindex'] = 300
  316. endif
  317. if winid && nvim_win_is_valid(winid)
  318. if has('nvim-0.5.0')
  319. call nvim_win_set_config(winid, coc#dict#pick(config, ['relative', 'row', 'col']))
  320. call nvim_win_set_height(winid, config['height'])
  321. return
  322. endif
  323. noa call nvim_win_close(winid, 1)
  324. endif
  325. let s:pad_bufnr = bufloaded(s:pad_bufnr) ? s:pad_bufnr : coc#float#create_buf(0, repeat([''], &lines), 'hide')
  326. noa let winid = nvim_open_win(s:pad_bufnr, 0, config)
  327. call s:nvim_add_related(winid, a:winid, 'pad', '', a:related)
  328. endfunction
  329. " draw buttons window for window with config
  330. function! coc#float#nvim_buttons(config, winid, buttons, getchar, borderbottom, pad, borderhighlight, shadow, related) abort
  331. let winid = coc#float#get_related(a:winid, 'buttons')
  332. let width = a:config['width'] + (a:pad ? 1 : 0)
  333. let config = {
  334. \ 'row': a:config['row'] + a:config['height'],
  335. \ 'col': a:config['col'],
  336. \ 'width': width,
  337. \ 'height': 2 + (a:borderbottom ? 1 : 0),
  338. \ 'relative': a:config['relative'],
  339. \ 'focusable': 1,
  340. \ 'style': 'minimal',
  341. \ }
  342. if has('nvim-0.5.1')
  343. let config['zindex'] = 300
  344. if a:shadow
  345. let config['border'] = 'shadow'
  346. endif
  347. endif
  348. if winid
  349. let bufnr = winbufnr(winid)
  350. call s:create_btns_buffer(bufnr, width, a:buttons, a:borderbottom)
  351. call nvim_win_set_config(winid, config)
  352. else
  353. let bufnr = s:create_btns_buffer(0, width, a:buttons, a:borderbottom)
  354. noa let winid = nvim_open_win(bufnr, 0, config)
  355. if winid
  356. call s:nvim_add_related(winid, a:winid, 'buttons', '', a:related)
  357. call s:nvim_create_keymap(winid)
  358. endif
  359. endif
  360. if bufnr
  361. call nvim_buf_clear_namespace(bufnr, -1, 0, -1)
  362. call nvim_buf_add_highlight(bufnr, 1, a:borderhighlight, 0, 0, -1)
  363. if a:borderbottom
  364. call nvim_buf_add_highlight(bufnr, 1, a:borderhighlight, 2, 0, -1)
  365. endif
  366. let vcols = getbufvar(bufnr, 'vcols', [])
  367. " TODO need change vol to col
  368. for col in vcols
  369. call nvim_buf_add_highlight(bufnr, 1, a:borderhighlight, 1, col, col + 3)
  370. endfor
  371. if a:getchar
  372. let keys = s:gen_filter_keys(getbufline(bufnr, 2)[0])
  373. call matchaddpos('MoreMsg', map(keys[0], "[2,v:val]"), 99, -1, {'window': winid})
  374. call timer_start(10, {-> coc#float#getchar(winid, keys[1])})
  375. endif
  376. endif
  377. endfunction
  378. function! coc#float#getchar(winid, keys) abort
  379. let ch = coc#prompt#getc()
  380. let target = getwinvar(a:winid, 'target_winid', 0)
  381. if ch ==# "\<esc>"
  382. call coc#float#close(target)
  383. return
  384. endif
  385. if ch ==# "\<LeftMouse>"
  386. if getwinvar(v:mouse_winid, 'kind', '') ==# 'close'
  387. call coc#float#close(target)
  388. return
  389. endif
  390. if v:mouse_winid == a:winid && v:mouse_lnum == 2
  391. let vcols = getbufvar(winbufnr(a:winid), 'vcols', [])
  392. let col = v:mouse_col - 1
  393. if index(vcols, col) < 0
  394. let filtered = filter(vcols, 'v:val < col')
  395. call coc#rpc#notify('FloatBtnClick', [winbufnr(target), len(filtered)])
  396. call coc#float#close(target)
  397. return
  398. endif
  399. endif
  400. else
  401. let idx = index(a:keys, ch)
  402. if idx >= 0
  403. call coc#rpc#notify('FloatBtnClick', [winbufnr(target), idx])
  404. call coc#float#close(target)
  405. return
  406. endif
  407. endif
  408. call coc#float#getchar(a:winid, a:keys)
  409. endfunction
  410. " Create or refresh scrollbar for winid
  411. " Need called on create, config, buffer change, scrolled
  412. function! coc#float#nvim_scrollbar(winid) abort
  413. if !has('nvim-0.4.0')
  414. return
  415. endif
  416. let winids = nvim_tabpage_list_wins(nvim_get_current_tabpage())
  417. if index(winids, a:winid) == -1
  418. return
  419. endif
  420. let config = nvim_win_get_config(a:winid)
  421. let [row, column] = nvim_win_get_position(a:winid)
  422. let relative = 'editor'
  423. if row == 0 && column == 0
  424. " fix bad value when ext_multigrid is enabled. https://github.com/neovim/neovim/issues/11935
  425. let [row, column] = [config.row, config.col]
  426. let relative = config.relative
  427. endif
  428. let width = nvim_win_get_width(a:winid)
  429. let height = nvim_win_get_height(a:winid)
  430. let bufnr = winbufnr(a:winid)
  431. let cw = getwinvar(a:winid, '&foldcolumn', 0) ? width - 1 : width
  432. let ch = coc#float#content_height(bufnr, cw, getwinvar(a:winid, '&wrap'))
  433. let closewin = coc#float#get_related(a:winid, 'close')
  434. let border = getwinvar(a:winid, 'border', [])
  435. let scrollinside = getwinvar(a:winid, 'scrollinside', 0) && get(border, 1, 0)
  436. let winblend = getwinvar(a:winid, '&winblend', 0)
  437. let move_down = closewin && !get(border, 0, 0)
  438. let id = coc#float#get_related(a:winid, 'scrollbar')
  439. if ch <= height || height <= 1
  440. " no scrollbar, remove exists
  441. if id
  442. call s:close_win(id)
  443. endif
  444. return
  445. endif
  446. if move_down
  447. let height = height - 1
  448. endif
  449. call coc#float#close_related(a:winid, 'pad')
  450. let sbuf = id ? winbufnr(id) : 0
  451. let sbuf = coc#float#create_buf(sbuf, repeat([' '], height))
  452. let opts = {
  453. \ 'row': move_down ? row + 1 : row,
  454. \ 'col': column + width - scrollinside,
  455. \ 'relative': relative,
  456. \ 'width': 1,
  457. \ 'height': height,
  458. \ 'focusable': v:false,
  459. \ 'style': 'minimal',
  460. \ }
  461. if has('nvim-0.5.1')
  462. let opts['zindex'] = 300
  463. endif
  464. if id
  465. call nvim_win_set_config(id, opts)
  466. else
  467. noa let id = nvim_open_win(sbuf, 0 , opts)
  468. if id == 0
  469. return
  470. endif
  471. if winblend
  472. call setwinvar(id, '&winblend', winblend)
  473. endif
  474. call setwinvar(id, 'kind', 'scrollbar')
  475. call setwinvar(id, 'target_winid', a:winid)
  476. call coc#float#add_related(id, a:winid)
  477. endif
  478. if !scrollinside
  479. call coc#float#nvim_scroll_adjust(a:winid)
  480. endif
  481. let thumb_height = max([1, float2nr(floor(height * (height + 0.0)/ch))])
  482. let wininfo = getwininfo(a:winid)[0]
  483. let start = 0
  484. if wininfo['topline'] != 1
  485. " needed for correct getwininfo
  486. let firstline = wininfo['topline']
  487. let lastline = s:nvim_get_botline(firstline, height, cw, bufnr)
  488. let linecount = nvim_buf_line_count(winbufnr(a:winid))
  489. if lastline >= linecount
  490. let start = height - thumb_height
  491. else
  492. let start = max([1, float2nr(round((height - thumb_height + 0.0)*(firstline - 1.0)/(ch - height)))])
  493. endif
  494. endif
  495. " add highlights
  496. call nvim_buf_clear_namespace(sbuf, -1, 0, -1)
  497. for idx in range(0, height - 1)
  498. if idx >= start && idx < start + thumb_height
  499. call nvim_buf_add_highlight(sbuf, -1, 'PmenuThumb', idx, 0, 1)
  500. else
  501. call nvim_buf_add_highlight(sbuf, -1, 'PmenuSbar', idx, 0, 1)
  502. endif
  503. endfor
  504. endfunction
  505. function! coc#float#create_border_lines(border, borderchars, title, width, height, hasbtn) abort
  506. let borderchars = a:borderchars
  507. let list = []
  508. if a:border[0]
  509. let top = (a:border[3] ? borderchars[4]: '')
  510. \.repeat(borderchars[0], a:width)
  511. \.(a:border[1] ? borderchars[5] : '')
  512. if !empty(a:title)
  513. let top = coc#string#compose(top, 1, a:title.' ')
  514. endif
  515. call add(list, top)
  516. endif
  517. let mid = (a:border[3] ? borderchars[3]: '')
  518. \.repeat(' ', a:width)
  519. \.(a:border[1] ? borderchars[1] : '')
  520. call extend(list, repeat([mid], a:height + (a:hasbtn ? 2 : 0)))
  521. if a:hasbtn
  522. let list[len(list) - 2] = (a:border[3] ? s:borderjoinchars[3]: '')
  523. \.repeat(' ', a:width)
  524. \.(a:border[1] ? s:borderjoinchars[1] : '')
  525. endif
  526. if a:border[2]
  527. let bot = (a:border[3] ? borderchars[7]: '')
  528. \.repeat(borderchars[2], a:width)
  529. \.(a:border[1] ? borderchars[6] : '')
  530. call add(list, bot)
  531. endif
  532. return list
  533. endfunction
  534. " Close float window by id
  535. function! coc#float#close(winid) abort
  536. call coc#float#close_related(a:winid)
  537. call s:close_win(a:winid)
  538. return 1
  539. endfunction
  540. " Get visible float windows
  541. function! coc#float#get_float_win_list(...) abort
  542. let res = []
  543. let list_all = get(a:, 1, 0)
  544. if s:is_vim
  545. if s:popup_list_api
  546. return filter(popup_list(), 'popup_getpos(v:val)["visible"]'.(list_all ? '' : '&& getwinvar(v:val, "float", 0)'))
  547. endif
  548. return filter(s:popup_list, 's:popup_visible(v:val)')
  549. else
  550. let res = []
  551. for i in range(1, winnr('$'))
  552. let id = win_getid(i)
  553. let config = nvim_win_get_config(id)
  554. if empty(config) || empty(config['relative'])
  555. continue
  556. endif
  557. " ignore border & button window & others
  558. if list_all == 0 && !getwinvar(id, 'float', 0)
  559. continue
  560. endif
  561. call add(res, id)
  562. endfor
  563. return res
  564. endif
  565. return []
  566. endfunction
  567. function! coc#float#get_float_by_kind(kind) abort
  568. if s:is_vim
  569. if s:popup_list_api
  570. return get(filter(popup_list(), 'popup_getpos(v:val)["visible"] && getwinvar(v:val, "kind", "") ==# "'.a:kind.'"'), 0, 0)
  571. endif
  572. return get(filter(s:popup_list, 's:popup_visible(v:val) && getwinvar(v:val, "kind", "") ==# "'.a:kind.'"'), 0, 0)
  573. else
  574. let res = []
  575. for i in range(1, winnr('$'))
  576. let winid = win_getid(i)
  577. let config = nvim_win_get_config(winid)
  578. if !empty(config['relative']) && getwinvar(winid, 'kind', '') ==# a:kind
  579. return winid
  580. endif
  581. endfor
  582. endif
  583. return 0
  584. endfunction
  585. " Check if a float window is scrollable
  586. function! coc#float#scrollable(winid) abort
  587. let bufnr = winbufnr(a:winid)
  588. if bufnr == -1
  589. return 0
  590. endif
  591. if s:is_vim
  592. let pos = popup_getpos(a:winid)
  593. if get(popup_getoptions(a:winid), 'scrollbar', 0)
  594. return get(pos, 'scrollbar', 0)
  595. endif
  596. let ch = coc#float#content_height(bufnr, pos['core_width'], getwinvar(a:winid, '&wrap'))
  597. return ch > pos['core_height']
  598. else
  599. let height = nvim_win_get_height(a:winid)
  600. let width = nvim_win_get_width(a:winid)
  601. if width > 1 && getwinvar(a:winid, '&foldcolumn', 0)
  602. " since we use foldcolumn for left padding
  603. let width = width - 1
  604. endif
  605. let ch = coc#float#content_height(bufnr, width, getwinvar(a:winid, '&wrap'))
  606. return ch > height
  607. endif
  608. endfunction
  609. function! coc#float#has_scroll() abort
  610. let win_ids = filter(coc#float#get_float_win_list(), 'coc#float#scrollable(v:val)')
  611. return !empty(win_ids)
  612. endfunction
  613. function! coc#float#scroll(forward, ...)
  614. if !has('nvim-0.4.0') && !has('patch-8.2.0750')
  615. throw 'coc#float#scroll() requires nvim >= 0.4.0 or vim >= 8.2.0750'
  616. endif
  617. let amount = get(a:, 1, 0)
  618. let winids = filter(coc#float#get_float_win_list(), 'coc#float#scrollable(v:val) && getwinvar(v:val,"kind","") !=# "pum"')
  619. if empty(winids)
  620. return mode() =~ '^i' || mode() ==# 'v' ? "" : "\<Ignore>"
  621. endif
  622. for winid in winids
  623. call s:scroll_win(winid, a:forward, amount)
  624. endfor
  625. return mode() =~ '^i' || mode() ==# 'v' ? "" : "\<Ignore>"
  626. endfunction
  627. function! coc#float#scroll_win(winid, forward, amount) abort
  628. let opts = s:get_options(a:winid)
  629. let lines = getbufline(winbufnr(a:winid), 1, '$')
  630. let maxfirst = s:max_firstline(lines, opts['height'], opts['width'])
  631. let topline = opts['topline']
  632. let height = opts['height']
  633. let width = opts['width']
  634. let scrolloff = getwinvar(a:winid, '&scrolloff', 0)
  635. if a:forward && topline >= maxfirst
  636. return
  637. endif
  638. if !a:forward && topline == 1
  639. return
  640. endif
  641. if a:amount == 0
  642. let topline = s:get_topline(opts['topline'], lines, a:forward, height, width)
  643. else
  644. let topline = topline + (a:forward ? a:amount : - a:amount)
  645. endif
  646. let topline = a:forward ? min([maxfirst, topline]) : max([1, topline])
  647. let lnum = s:get_cursorline(topline, lines, scrolloff, width, height)
  648. call s:win_setview(a:winid, topline, lnum)
  649. let top = s:get_options(a:winid)['topline']
  650. " not changed
  651. if top == opts['topline']
  652. if a:forward
  653. call s:win_setview(a:winid, topline + 1, lnum + 1)
  654. else
  655. call s:win_setview(a:winid, topline - 1, lnum - 1)
  656. endif
  657. endif
  658. endfunction
  659. function! coc#float#content_height(bufnr, width, wrap) abort
  660. if !bufloaded(a:bufnr)
  661. return 0
  662. endif
  663. if !a:wrap
  664. return has('nvim') ? nvim_buf_line_count(a:bufnr) : len(getbufline(a:bufnr, 1, '$'))
  665. endif
  666. let lines = has('nvim') ? nvim_buf_get_lines(a:bufnr, 0, -1, 0) : getbufline(a:bufnr, 1, '$')
  667. let total = 0
  668. for line in lines
  669. let dw = max([1, strdisplaywidth(line)])
  670. let total += float2nr(ceil(str2float(string(dw))/a:width))
  671. endfor
  672. return total
  673. endfunction
  674. function! coc#float#nvim_refresh_scrollbar(winid) abort
  675. let id = coc#float#get_related(a:winid, 'scrollbar')
  676. if id && nvim_win_is_valid(id)
  677. call coc#float#nvim_scrollbar(a:winid)
  678. endif
  679. endfunction
  680. function! coc#float#on_close(winid) abort
  681. let winids = coc#float#get_float_win_list()
  682. for winid in winids
  683. let target = getwinvar(winid, 'target_winid', -1)
  684. if target == a:winid
  685. call coc#float#close(winid)
  686. endif
  687. endfor
  688. endfunction
  689. " Close related windows, or specific kind
  690. function! coc#float#close_related(winid, ...) abort
  691. if !coc#float#valid(a:winid)
  692. return
  693. endif
  694. let timer = coc#window#get_var(a:winid, 'timer', 0)
  695. if timer
  696. call timer_stop(timer)
  697. endif
  698. let kind = get(a:, 1, '')
  699. let winids = coc#window#get_var(a:winid, 'related', [])
  700. for id in winids
  701. let curr = coc#window#get_var(id, 'kind', '')
  702. if empty(kind) || curr ==# kind
  703. if curr == 'list'
  704. call coc#float#close(id)
  705. elseif s:is_vim
  706. " vim doesn't throw
  707. noa call popup_close(id)
  708. else
  709. silent! noa call nvim_win_close(id, 1)
  710. endif
  711. endif
  712. endfor
  713. endfunction
  714. " Close related windows if target window is not visible.
  715. function! coc#float#check_related() abort
  716. let invalids = []
  717. let ids = coc#float#get_float_win_list(1)
  718. for id in ids
  719. let target = getwinvar(id, 'target_winid', 0)
  720. if (target && index(ids, target) == -1) || getwinvar(id, 'kind', '') == 'pumdetail'
  721. call add(invalids, id)
  722. endif
  723. endfor
  724. if !s:popup_list_api
  725. let s:popup_list = filter(ids, "index(invalids, v:val) == -1")
  726. endif
  727. for id in invalids
  728. call coc#float#close(id)
  729. endfor
  730. endfunction
  731. " Show float window/popup for user confirm.
  732. " Create buttons popup on vim
  733. function! coc#float#vim_buttons(winid, config) abort
  734. if !has('patch-8.2.0750')
  735. return
  736. endif
  737. let related = getwinvar(a:winid, 'related', [])
  738. let winid = coc#float#get_related(a:winid, 'buttons')
  739. let btns = get(a:config, 'buttons', [])
  740. if empty(btns)
  741. if winid
  742. call s:close_win(winid)
  743. " fix padding
  744. let opts = popup_getoptions(a:winid)
  745. let padding = get(opts, 'padding', v:null)
  746. if !empty(padding)
  747. let padding[2] = padding[2] - 2
  748. endif
  749. call popup_setoptions(a:winid, {'padding': padding})
  750. endif
  751. return
  752. endif
  753. let border = get(a:config, 'border', v:null)
  754. if !winid
  755. " adjusting popup padding
  756. let opts = popup_getoptions(a:winid)
  757. let padding = get(opts, 'padding', v:null)
  758. if type(padding) == 7
  759. let padding = [0, 0, 2, 0]
  760. elseif len(padding) == 0
  761. let padding = [1, 1, 3, 1]
  762. else
  763. let padding[2] = padding[2] + 2
  764. endif
  765. call popup_setoptions(a:winid, {'padding': padding})
  766. endif
  767. let borderhighlight = get(get(a:config, 'borderhighlight', []), 0, '')
  768. let pos = popup_getpos(a:winid)
  769. let bw = empty(border) ? 0 : get(border, 1, 0) + get(border, 3, 0)
  770. let borderbottom = empty(border) ? 0 : get(border, 2, 0)
  771. let borderleft = empty(border) ? 0 : get(border, 3, 0)
  772. let width = pos['width'] - bw + get(pos, 'scrollbar', 0)
  773. let bufnr = s:create_btns_buffer(winid ? winbufnr(winid): 0,width, btns, borderbottom)
  774. let height = 2 + (borderbottom ? 1 : 0)
  775. let keys = s:gen_filter_keys(getbufline(bufnr, 2)[0])
  776. let options = {
  777. \ 'filter': {id, key -> coc#float#vim_filter(id, key, keys[1])},
  778. \ 'highlight': get(opts, 'highlight', 'CocFloating')
  779. \ }
  780. let config = {
  781. \ 'line': pos['line'] + pos['height'] - height,
  782. \ 'col': pos['col'] + borderleft,
  783. \ 'minwidth': width,
  784. \ 'minheight': height,
  785. \ 'maxwidth': width,
  786. \ 'maxheight': height,
  787. \ }
  788. if winid != 0
  789. call popup_move(winid, config)
  790. call popup_setoptions(winid, options)
  791. call win_execute(winid, 'call clearmatches()')
  792. else
  793. let options = extend({
  794. \ 'filtermode': 'nvi',
  795. \ 'padding': [0, 0, 0, 0],
  796. \ 'fixed': 1,
  797. \ 'zindex': 99,
  798. \ }, options)
  799. call extend(options, config)
  800. let winid = popup_create(bufnr, options)
  801. if !s:popup_list_api
  802. call add(s:popup_list, winid)
  803. endif
  804. endif
  805. if winid != 0
  806. if !empty(borderhighlight)
  807. call coc#highlight#add_highlight(bufnr, -1, borderhighlight, 0, 0, -1)
  808. call coc#highlight#add_highlight(bufnr, -1, borderhighlight, 2, 0, -1)
  809. call win_execute(winid, 'call matchadd("'.borderhighlight.'", "'.s:borderchars[1].'")')
  810. endif
  811. call setwinvar(winid, 'kind', 'buttons')
  812. call setwinvar(winid, 'target_winid', a:winid)
  813. call add(related, winid)
  814. call setwinvar(a:winid, 'related', related)
  815. call matchaddpos('MoreMsg', map(keys[0], "[2,v:val]"), 99, -1, {'window': winid})
  816. endif
  817. endfunction
  818. function! coc#float#nvim_float_click() abort
  819. let kind = getwinvar(win_getid(), 'kind', '')
  820. if kind == 'buttons'
  821. if line('.') != 2
  822. return
  823. endif
  824. let vw = strdisplaywidth(strpart(getline('.'), 0, col('.') - 1))
  825. let vcols = getbufvar(bufnr('%'), 'vcols', [])
  826. if index(vcols, vw) >= 0
  827. return
  828. endif
  829. let idx = 0
  830. if !empty(vcols)
  831. let filtered = filter(vcols, 'v:val < vw')
  832. let idx = idx + len(filtered)
  833. endif
  834. let winid = win_getid()
  835. let target = getwinvar(winid, 'target_winid', 0)
  836. if target
  837. call coc#rpc#notify('FloatBtnClick', [winbufnr(target), idx])
  838. call coc#float#close(target)
  839. endif
  840. elseif kind == 'close'
  841. let target = getwinvar(win_getid(), 'target_winid', 0)
  842. call coc#float#close(target)
  843. endif
  844. endfunction
  845. " Add <LeftRelease> mapping if necessary
  846. function! coc#float#nvim_win_enter(winid) abort
  847. let kind = getwinvar(a:winid, 'kind', '')
  848. if kind == 'buttons' || kind == 'close'
  849. if empty(maparg('<LeftRelease>', 'n'))
  850. nnoremap <buffer><silent> <LeftRelease> :call coc#float#nvim_float_click()<CR>
  851. endif
  852. endif
  853. endfunction
  854. function! coc#float#vim_filter(winid, key, keys) abort
  855. let key = tolower(a:key)
  856. let idx = index(a:keys, key)
  857. let target = getwinvar(a:winid, 'target_winid', 0)
  858. if target && idx >= 0
  859. call coc#rpc#notify('FloatBtnClick', [winbufnr(target), idx])
  860. call coc#float#close(target)
  861. return 1
  862. endif
  863. return 0
  864. endfunction
  865. function! coc#float#get_related(winid, kind, ...) abort
  866. if coc#float#valid(a:winid)
  867. for winid in coc#window#get_var(a:winid, 'related', [])
  868. if coc#window#get_var(winid, 'kind', '') ==# a:kind
  869. return winid
  870. endif
  871. endfor
  872. endif
  873. return get(a:, 1, 0)
  874. endfunction
  875. function! coc#float#get_row(winid) abort
  876. let winid = s:is_vim ? a:winid : coc#float#get_related(a:winid, 'border', a:winid)
  877. if coc#float#valid(winid)
  878. if s:is_vim
  879. let pos = popup_getpos(winid)
  880. return pos['line'] - 1
  881. endif
  882. let pos = nvim_win_get_position(winid)
  883. return pos[0]
  884. endif
  885. endfunction
  886. " Create temporarily buffer with optional lines and &bufhidden
  887. function! coc#float#create_buf(bufnr, ...) abort
  888. if a:bufnr > 0 && bufloaded(a:bufnr)
  889. let bufnr = a:bufnr
  890. else
  891. if s:is_vim
  892. noa let bufnr = bufadd('')
  893. noa call bufload(bufnr)
  894. call setbufvar(bufnr, '&buflisted', 0)
  895. else
  896. noa let bufnr = nvim_create_buf(v:false, v:true)
  897. endif
  898. let bufhidden = get(a:, 2, 'wipe')
  899. call setbufvar(bufnr, '&buftype', 'nofile')
  900. call setbufvar(bufnr, '&bufhidden', bufhidden)
  901. call setbufvar(bufnr, '&swapfile', 0)
  902. call setbufvar(bufnr, '&undolevels', -1)
  903. " neovim's bug
  904. call setbufvar(bufnr, '&modifiable', 1)
  905. endif
  906. let lines = get(a:, 1, v:null)
  907. if type(lines) == v:t_list
  908. if has('nvim')
  909. call nvim_buf_set_lines(bufnr, 0, -1, v:false, lines)
  910. else
  911. silent noa call setbufline(bufnr, 1, lines)
  912. silent noa call deletebufline(bufnr, len(lines) + 1, '$')
  913. endif
  914. endif
  915. return bufnr
  916. endfunction
  917. " Change border window & close window when scrollbar is shown.
  918. function! coc#float#nvim_scroll_adjust(winid) abort
  919. let winid = coc#float#get_related(a:winid, 'border')
  920. if !winid
  921. return
  922. endif
  923. let bufnr = winbufnr(winid)
  924. let lines = nvim_buf_get_lines(bufnr, 0, -1, 0)
  925. if len(lines) >= 2
  926. let cw = nvim_win_get_width(a:winid)
  927. let width = nvim_win_get_width(winid)
  928. if width - cw != 1 + (strcharpart(lines[1], 0, 1) ==# s:borderchars[3] ? 1 : 0)
  929. return
  930. endif
  931. call nvim_win_set_width(winid, width + 1)
  932. let lastline = len(lines) - 1
  933. for i in range(0, lastline)
  934. let line = lines[i]
  935. if i == 0
  936. let add = s:borderchars[0]
  937. elseif i == lastline
  938. let add = s:borderchars[2]
  939. else
  940. let add = ' '
  941. endif
  942. let prev = strcharpart(line, 0, strchars(line) - 1)
  943. let lines[i] = prev . add . coc#string#last_character(line)
  944. endfor
  945. call nvim_buf_set_lines(bufnr, 0, -1, 0, lines)
  946. " Move right close button
  947. if coc#window#get_var(a:winid, 'right', 0) == 0
  948. let id = coc#float#get_related(a:winid, 'close')
  949. if id
  950. let [row, col] = nvim_win_get_position(id)
  951. call nvim_win_set_config(id, {
  952. \ 'relative': 'editor',
  953. \ 'row': row,
  954. \ 'col': col + 1,
  955. \ })
  956. endif
  957. elseif has('nvim-0.6.0')
  958. " Move winid and all related left by 1
  959. let winids = [a:winid] + coc#window#get_var(a:winid, 'related', [])
  960. for winid in winids
  961. if nvim_win_is_valid(winid)
  962. if coc#window#get_var(winid, 'kind', '') != 'close'
  963. let [row, column] = nvim_win_get_position(winid)
  964. call nvim_win_set_config(winid, {
  965. \ 'row': row,
  966. \ 'col': column - 1,
  967. \ 'relative': 'editor',
  968. \ })
  969. endif
  970. endif
  971. endfor
  972. endif
  973. endif
  974. endfunction
  975. function! coc#float#nvim_set_winblend(winid, winblend) abort
  976. if a:winblend is v:null
  977. return
  978. endif
  979. call coc#window#set_var(a:winid, '&winblend', a:winblend)
  980. for winid in coc#window#get_var(a:winid, 'related', [])
  981. call coc#window#set_var(winid, '&winblend', a:winblend)
  982. endfor
  983. endfunction
  984. function! s:popup_visible(id) abort
  985. let pos = popup_getpos(a:id)
  986. if !empty(pos) && get(pos, 'visible', 0)
  987. return 1
  988. endif
  989. return 0
  990. endfunction
  991. function! s:convert_config_nvim(config, create) abort
  992. let valids = ['relative', 'win', 'anchor', 'width', 'height', 'bufpos', 'col', 'row', 'focusable']
  993. let result = coc#dict#pick(a:config, valids)
  994. let border = get(a:config, 'border', [])
  995. if !s:empty_border(border)
  996. if result['relative'] ==# 'cursor' && result['row'] < 0
  997. " move top when has bottom border
  998. if get(border, 2, 0)
  999. let result['row'] = result['row'] - 1
  1000. endif
  1001. else
  1002. " move down when has top border
  1003. if get(border, 0, 0) && !get(a:config, 'prompt', 0)
  1004. let result['row'] = result['row'] + 1
  1005. endif
  1006. endif
  1007. " move right when has left border
  1008. if get(border, 3, 0)
  1009. let result['col'] = result['col'] + 1
  1010. endif
  1011. let result['width'] = float2nr(result['width'] + 1 - get(border,3, 0))
  1012. else
  1013. let result['width'] = float2nr(result['width'] + (get(a:config, 'nopad', 0) ? 0 : 1))
  1014. endif
  1015. if has('nvim-0.5.1') && a:create
  1016. let result['noautocmd'] = v:true
  1017. endif
  1018. let result['height'] = float2nr(result['height'])
  1019. return result
  1020. endfunction
  1021. function! s:create_btns_buffer(bufnr, width, buttons, borderbottom) abort
  1022. let n = len(a:buttons)
  1023. let spaces = a:width - n + 1
  1024. let tw = 0
  1025. for txt in a:buttons
  1026. let tw += strdisplaywidth(txt)
  1027. endfor
  1028. if spaces < tw
  1029. throw 'window is too small for buttons.'
  1030. endif
  1031. let ds = (spaces - tw)/n
  1032. let dl = ds/2
  1033. let dr = ds%2 == 0 ? ds/2 : ds/2 + 1
  1034. let btnline = ''
  1035. let idxes = []
  1036. for idx in range(0, n - 1)
  1037. let txt = toupper(a:buttons[idx][0]).a:buttons[idx][1:]
  1038. let btnline .= repeat(' ', dl).txt.repeat(' ', dr)
  1039. if idx != n - 1
  1040. call add(idxes, strdisplaywidth(btnline))
  1041. let btnline .= s:borderchars[1]
  1042. endif
  1043. endfor
  1044. let lines = [repeat(s:borderchars[0], a:width), btnline]
  1045. if a:borderbottom
  1046. call add(lines, repeat(s:borderchars[0], a:width))
  1047. endif
  1048. for idx in idxes
  1049. let lines[0] = strcharpart(lines[0], 0, idx).s:borderjoinchars[0].strcharpart(lines[0], idx + 1)
  1050. if a:borderbottom
  1051. let lines[2] = strcharpart(lines[0], 0, idx).s:borderjoinchars[2].strcharpart(lines[0], idx + 1)
  1052. endif
  1053. endfor
  1054. let bufnr = coc#float#create_buf(a:bufnr, lines)
  1055. call setbufvar(bufnr, 'vcols', idxes)
  1056. return bufnr
  1057. endfunction
  1058. function! s:gen_filter_keys(line) abort
  1059. let cols = []
  1060. let used = []
  1061. let next = 1
  1062. for idx in range(0, strchars(a:line) - 1)
  1063. let ch = strcharpart(a:line, idx, 1)
  1064. let nr = char2nr(ch)
  1065. if next
  1066. if (nr >= 65 && nr <= 90) || (nr >= 97 && nr <= 122)
  1067. let lc = tolower(ch)
  1068. if index(used, lc) < 0 && (!s:is_vim || empty(maparg(lc, 'n')))
  1069. let col = len(strcharpart(a:line, 0, idx)) + 1
  1070. call add(used, lc)
  1071. call add(cols, col)
  1072. let next = 0
  1073. endif
  1074. endif
  1075. else
  1076. if ch == s:borderchars[1]
  1077. let next = 1
  1078. endif
  1079. endif
  1080. endfor
  1081. return [cols, used]
  1082. endfunction
  1083. function! s:close_win(winid) abort
  1084. if a:winid <= 0
  1085. return
  1086. endif
  1087. " vim not throw for none exists winid
  1088. if s:is_vim
  1089. call popup_close(a:winid)
  1090. else
  1091. if nvim_win_is_valid(a:winid)
  1092. call nvim_win_close(a:winid, 1)
  1093. endif
  1094. endif
  1095. endfunction
  1096. function! s:nvim_create_keymap(winid) abort
  1097. if exists('*nvim_buf_set_keymap')
  1098. let bufnr = winbufnr(a:winid)
  1099. call nvim_buf_set_keymap(bufnr, 'n', '<LeftRelease>', ':call coc#float#nvim_float_click()<CR>', {
  1100. \ 'silent': v:true,
  1101. \ 'nowait': v:true
  1102. \ })
  1103. else
  1104. let curr = win_getid()
  1105. let m = mode()
  1106. if m == 'n' || m == 'i' || m == 'ic'
  1107. noa call win_gotoid(a:winid)
  1108. nnoremap <buffer><silent> <LeftRelease> :call coc#float#nvim_float_click()<CR>
  1109. noa call win_gotoid(curr)
  1110. endif
  1111. endif
  1112. endfunction
  1113. " getwininfo is buggy on neovim, use topline, width & height should for content
  1114. function! s:nvim_get_botline(topline, height, width, bufnr) abort
  1115. let lines = getbufline(a:bufnr, a:topline, a:topline + a:height - 1)
  1116. let botline = a:topline
  1117. let count = 0
  1118. for i in range(0, len(lines) - 1)
  1119. let w = max([1, strdisplaywidth(lines[i])])
  1120. let lh = float2nr(ceil(str2float(string(w))/a:width))
  1121. let count = count + lh
  1122. let botline = a:topline + i
  1123. if count >= a:height
  1124. break
  1125. endif
  1126. endfor
  1127. return botline
  1128. endfunction
  1129. " get popup position for vim8 based on config of neovim float window
  1130. function! s:popup_position(config) abort
  1131. let relative = get(a:config, 'relative', 'editor')
  1132. let border = get(a:config, 'border', [0, 0, 0, 0])
  1133. let delta = get(border, 0, 0) + get(border, 2, 0)
  1134. if relative ==# 'cursor'
  1135. if a:config['row'] < 0
  1136. let delta = - delta
  1137. elseif a:config['row'] == 0
  1138. let delta = - get(border, 0, 0)
  1139. else
  1140. let delta = 0
  1141. endif
  1142. return [s:popup_cursor(a:config['row'] + delta), s:popup_cursor(a:config['col'])]
  1143. endif
  1144. return [a:config['row'] + 1, a:config['col'] + 1]
  1145. endfunction
  1146. function! coc#float#add_related(winid, target) abort
  1147. let arr = coc#window#get_var(a:target, 'related', [])
  1148. if index(arr, a:winid) >= 0
  1149. return
  1150. endif
  1151. call add(arr, a:winid)
  1152. call coc#window#set_var(a:target, 'related', arr)
  1153. endfunction
  1154. function! s:popup_cursor(n) abort
  1155. if a:n == 0
  1156. return 'cursor'
  1157. endif
  1158. if a:n < 0
  1159. return 'cursor'.a:n
  1160. endif
  1161. return 'cursor+'.a:n
  1162. endfunction
  1163. " max firstline of lines, height > 0, width > 0
  1164. function! s:max_firstline(lines, height, width) abort
  1165. let max = len(a:lines)
  1166. let remain = a:height
  1167. for line in reverse(copy(a:lines))
  1168. let w = max([1, strdisplaywidth(line)])
  1169. let dh = float2nr(ceil(str2float(string(w))/a:width))
  1170. if remain - dh < 0
  1171. break
  1172. endif
  1173. let remain = remain - dh
  1174. let max = max - 1
  1175. endfor
  1176. return min([len(a:lines), max + 1])
  1177. endfunction
  1178. " Get best lnum by topline
  1179. function! s:get_cursorline(topline, lines, scrolloff, width, height) abort
  1180. let lastline = len(a:lines)
  1181. if a:topline == lastline
  1182. return lastline
  1183. endif
  1184. let bottomline = a:topline
  1185. let used = 0
  1186. for lnum in range(a:topline, lastline)
  1187. let w = max([1, strdisplaywidth(a:lines[lnum - 1])])
  1188. let dh = float2nr(ceil(str2float(string(w))/a:width))
  1189. if used + dh >= a:height || lnum == lastline
  1190. let bottomline = lnum
  1191. break
  1192. endif
  1193. let used += dh
  1194. endfor
  1195. let cursorline = a:topline + a:scrolloff
  1196. if cursorline + a:scrolloff > bottomline
  1197. " unable to satisfy scrolloff
  1198. let cursorline = (a:topline + bottomline)/2
  1199. endif
  1200. return cursorline
  1201. endfunction
  1202. " Get firstline for full scroll
  1203. function! s:get_topline(topline, lines, forward, height, width) abort
  1204. let used = 0
  1205. let lnums = a:forward ? range(a:topline, len(a:lines)) : reverse(range(1, a:topline))
  1206. let topline = a:forward ? len(a:lines) : 1
  1207. for lnum in lnums
  1208. let w = max([1, strdisplaywidth(a:lines[lnum - 1])])
  1209. let dh = float2nr(ceil(str2float(string(w))/a:width))
  1210. if used + dh >= a:height
  1211. let topline = lnum
  1212. break
  1213. endif
  1214. let used += dh
  1215. endfor
  1216. if topline == a:topline
  1217. if a:forward
  1218. let topline = min([len(a:lines), topline + 1])
  1219. else
  1220. let topline = max([1, topline - 1])
  1221. endif
  1222. endif
  1223. return topline
  1224. endfunction
  1225. " topline content_height content_width
  1226. function! s:get_options(winid) abort
  1227. if has('nvim')
  1228. let width = nvim_win_get_width(a:winid)
  1229. if coc#window#get_var(a:winid, '&foldcolumn', 0)
  1230. let width = width - 1
  1231. endif
  1232. let info = getwininfo(a:winid)[0]
  1233. return {
  1234. \ 'topline': info['topline'],
  1235. \ 'height': nvim_win_get_height(a:winid),
  1236. \ 'width': width
  1237. \ }
  1238. else
  1239. let pos = popup_getpos(a:winid)
  1240. return {
  1241. \ 'topline': pos['firstline'],
  1242. \ 'width': pos['core_width'],
  1243. \ 'height': pos['core_height']
  1244. \ }
  1245. endif
  1246. endfunction
  1247. function! s:win_setview(winid, topline, lnum) abort
  1248. if has('nvim')
  1249. call coc#compat#execute(a:winid, 'call winrestview({"lnum":'.a:lnum.',"topline":'.a:topline.'})')
  1250. call timer_start(10, { -> coc#float#nvim_refresh_scrollbar(a:winid) })
  1251. else
  1252. call coc#compat#execute(a:winid, 'exe '.a:lnum)
  1253. call popup_setoptions(a:winid, {
  1254. \ 'firstline': a:topline,
  1255. \ })
  1256. endif
  1257. endfunction
  1258. function! s:set_float_defaults(winid, config) abort
  1259. if has('nvim')
  1260. let hlgroup = get(a:config, 'highlight', 'CocFloating')
  1261. call setwinvar(a:winid, '&winhl', 'Normal:'.hlgroup.',NormalNC:'.hlgroup.',FoldColumn:'.hlgroup)
  1262. call setwinvar(a:winid, 'border', get(a:config, 'border', []))
  1263. call setwinvar(a:winid, 'scrollinside', get(a:config, 'scrollinside', 0))
  1264. if !get(a:config, 'nopad', 0)
  1265. call setwinvar(a:winid, '&foldcolumn', s:nvim_enable_foldcolumn(get(a:config, 'border', v:null)))
  1266. endif
  1267. endif
  1268. call setwinvar(a:winid, '&spell', 0)
  1269. call setwinvar(a:winid, '&linebreak', 1)
  1270. call setwinvar(a:winid, '&conceallevel', 0)
  1271. call setwinvar(a:winid, '&signcolumn', 'no')
  1272. call setwinvar(a:winid, '&list', 0)
  1273. call setwinvar(a:winid, '&number', 0)
  1274. call setwinvar(a:winid, '&relativenumber', 0)
  1275. call setwinvar(a:winid, '&cursorline', 0)
  1276. call setwinvar(a:winid, '&cursorcolumn', 0)
  1277. call setwinvar(a:winid, '&colorcolumn', 0)
  1278. call setwinvar(a:winid, '&wrap', !get(a:config, 'cursorline', 0))
  1279. if s:is_vim || has('nvim-0.5.0')
  1280. call setwinvar(a:winid, '&scrolloff', 0)
  1281. endif
  1282. if has('nvim-0.6.0') || has("patch-8.1.2281")
  1283. call setwinvar(a:winid, '&showbreak', 'NONE')
  1284. endif
  1285. if exists('*win_execute')
  1286. call win_execute(a:winid, 'setl fillchars+=eob:\ ')
  1287. endif
  1288. if get(a:config, 'autohide', 0)
  1289. call setwinvar(a:winid, 'autohide', 1)
  1290. endif
  1291. call setwinvar(a:winid, 'float', 1)
  1292. endfunction
  1293. function! s:nvim_add_related(winid, target, kind, winhl, related) abort
  1294. if a:winid <= 0
  1295. return
  1296. endif
  1297. " minimal not work
  1298. if !has('nvim-0.4.3')
  1299. call setwinvar(a:winid, '&colorcolumn', 0)
  1300. call setwinvar(a:winid, '&number', 0)
  1301. call setwinvar(a:winid, '&relativenumber', 0)
  1302. call setwinvar(a:winid, '&foldcolumn', 0)
  1303. call setwinvar(a:winid, '&signcolumn', 0)
  1304. call setwinvar(a:winid, '&list', 0)
  1305. endif
  1306. let winhl = empty(a:winhl) ? coc#window#get_var(a:target, '&winhl', '') : a:winhl
  1307. call setwinvar(a:winid, '&winhl', winhl)
  1308. call setwinvar(a:winid, 'target_winid', a:target)
  1309. call setwinvar(a:winid, 'kind', a:kind)
  1310. call add(a:related, a:winid)
  1311. endfunction
  1312. function! s:nvim_enable_foldcolumn(border) abort
  1313. if a:border is 1
  1314. return 0
  1315. endif
  1316. if type(a:border) == v:t_list && get(a:border, 3, 0) == 1
  1317. return 0
  1318. endif
  1319. return 1
  1320. endfunction
  1321. function! s:add_highlights(winid, config, create) abort
  1322. let codes = get(a:config, 'codes', [])
  1323. let highlights = get(a:config, 'highlights', [])
  1324. if empty(codes) && empty(highlights) && a:create
  1325. return
  1326. endif
  1327. let bgGroup = get(a:config, 'highlight', 'CocFloating')
  1328. for obj in codes
  1329. let hlGroup = get(obj, 'hlGroup', v:null)
  1330. if !empty(hlGroup)
  1331. let obj['hlGroup'] = coc#highlight#compose_hlgroup(hlGroup, bgGroup)
  1332. endif
  1333. endfor
  1334. call coc#highlight#add_highlights(a:winid, codes, highlights)
  1335. endfunction
  1336. function! s:empty_border(border) abort
  1337. if empty(a:border) || empty(filter(copy(a:border), 'v:val != 0'))
  1338. return 1
  1339. endif
  1340. return 0
  1341. endfunction
  1342. function! s:get_borderchars(config) abort
  1343. let borderchars = get(a:config, 'borderchars', [])
  1344. if !empty(borderchars)
  1345. return borderchars
  1346. endif
  1347. return get(a:config, 'rounded', 0) ? s:rounded_borderchars : s:borderchars
  1348. endfunction
  1349. function! s:scroll_win(winid, forward, amount) abort
  1350. if s:is_vim
  1351. call coc#float#scroll_win(a:winid, a:forward, a:amount)
  1352. else
  1353. call timer_start(0, { -> coc#float#scroll_win(a:winid, a:forward, a:amount)})
  1354. endif
  1355. endfunction
  1356. function! s:get_borderhighlight(config) abort
  1357. let hlgroup = get(a:config, 'highlight', 'CocFloating')
  1358. let borderhighlight = get(a:config, 'borderhighlight', v:null)
  1359. if empty(borderhighlight)
  1360. return hlgroup
  1361. endif
  1362. let highlight = type(borderhighlight) == 3 ? borderhighlight[0] : borderhighlight
  1363. return coc#highlight#compose_hlgroup(highlight, hlgroup)
  1364. endfunction