util.vim 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. scriptencoding utf-8
  2. let s:root = expand('<sfile>:h:h:h')
  3. let s:is_win = has('win32') || has('win64')
  4. let s:is_vim = !has('nvim')
  5. let s:vim_api_version = 31
  6. function! coc#util#remote_fns(name)
  7. let fns = ['init', 'complete', 'should_complete', 'refresh', 'get_startcol', 'on_complete', 'on_enter']
  8. let res = []
  9. for fn in fns
  10. if exists('*coc#source#'.a:name.'#'.fn)
  11. call add(res, fn)
  12. endif
  13. endfor
  14. return res
  15. endfunction
  16. function! coc#util#do_complete(name, opt, cb) abort
  17. let handler = 'coc#source#'.a:name.'#complete'
  18. let l:Cb = {res -> a:cb(v:null, res)}
  19. let args = [a:opt, l:Cb]
  20. call call(handler, args)
  21. endfunction
  22. function! coc#util#suggest_variables(bufnr) abort
  23. return {
  24. \ 'disable': getbufvar(a:bufnr, 'coc_suggest_disable', 0),
  25. \ 'disabled_sources': getbufvar(a:bufnr, 'coc_disabled_sources', []),
  26. \ 'blacklist': getbufvar(a:bufnr, 'coc_suggest_blacklist', []),
  27. \ }
  28. endfunction
  29. function! coc#util#api_version() abort
  30. return s:vim_api_version
  31. endfunction
  32. function! coc#util#semantic_hlgroups() abort
  33. let res = split(execute('hi'), "\n")
  34. let filtered = filter(res, "v:val =~# '^CocSem'")
  35. return map(filtered, "matchstr(v:val,'\\v^CocSem\\w+')")
  36. endfunction
  37. " get cursor position
  38. function! coc#util#cursor()
  39. return [line('.') - 1, strchars(strpart(getline('.'), 0, col('.') - 1))]
  40. endfunction
  41. function! coc#util#change_info() abort
  42. return {'lnum': line('.'), 'col': col('.'), 'line': getline('.'), 'changedtick': b:changedtick}
  43. endfunction
  44. function! coc#util#jumpTo(line, character) abort
  45. echohl WarningMsg | echon 'coc#util#jumpTo is deprecated, use coc#cursor#move_to instead.' | echohl None
  46. call coc#cursor#move_to(a:line, a:character)
  47. endfunction
  48. function! coc#util#root_patterns() abort
  49. return coc#rpc#request('rootPatterns', [bufnr('%')])
  50. endfunction
  51. function! coc#util#get_config(key) abort
  52. return coc#rpc#request('getConfig', [a:key])
  53. endfunction
  54. function! coc#util#open_terminal(opts) abort
  55. return coc#ui#open_terminal(a:opts)
  56. endfunction
  57. function! coc#util#synname() abort
  58. return synIDattr(synID(line('.'), col('.') - 1, 1), 'name')
  59. endfunction
  60. function! coc#util#setline(lnum, line)
  61. keepjumps call setline(a:lnum, a:line)
  62. endfunction
  63. function! coc#util#path_replace_patterns() abort
  64. if has('win32unix') && exists('g:coc_cygqwin_path_prefixes')
  65. echohl WarningMsg
  66. echon 'g:coc_cygqwin_path_prefixes is deprecated, use g:coc_uri_prefix_replace_patterns instead'
  67. echohl None
  68. return g:coc_cygqwin_path_prefixes
  69. endif
  70. if exists('g:coc_uri_prefix_replace_patterns')
  71. return g:coc_uri_prefix_replace_patterns
  72. endif
  73. return v:null
  74. endfunction
  75. function! coc#util#version()
  76. if s:is_vim
  77. return string(v:versionlong)
  78. endif
  79. let c = execute('silent version')
  80. let lines = split(matchstr(c, 'NVIM v\zs[^\n-]*'))
  81. return lines[0]
  82. endfunction
  83. function! coc#util#check_refresh(bufnr)
  84. if !bufloaded(a:bufnr)
  85. return 0
  86. endif
  87. if getbufvar(a:bufnr, 'coc_diagnostic_disable', 0)
  88. return 0
  89. endif
  90. if get(g: , 'EasyMotion_loaded', 0)
  91. return EasyMotion#is_active() != 1
  92. endif
  93. return 1
  94. endfunction
  95. function! coc#util#diagnostic_info(bufnr, checkInsert) abort
  96. let checked = coc#util#check_refresh(a:bufnr)
  97. if !checked
  98. return v:null
  99. endif
  100. if a:checkInsert && mode() =~# '^i'
  101. return v:null
  102. endif
  103. let locationlist = ''
  104. let winid = -1
  105. for info in getwininfo()
  106. if info['bufnr'] == a:bufnr
  107. let winid = info['winid']
  108. let locationlist = get(getloclist(winid, {'title': 1}), 'title', '')
  109. break
  110. endif
  111. endfor
  112. return {
  113. \ 'bufnr': bufnr('%'),
  114. \ 'winid': winid,
  115. \ 'lnum': line('.'),
  116. \ 'locationlist': locationlist
  117. \ }
  118. endfunction
  119. function! coc#util#open_file(cmd, file)
  120. execute a:cmd .' '.fnameescape(fnamemodify(a:file, ':~:.'))
  121. return bufnr('%')
  122. endfunction
  123. function! coc#util#job_command()
  124. if (has_key(g:, 'coc_node_path'))
  125. let node = expand(g:coc_node_path)
  126. else
  127. let node = $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH
  128. endif
  129. if !executable(node)
  130. echohl Error | echom '[coc.nvim] "'.node.'" is not executable, checkout https://nodejs.org/en/download/' | echohl None
  131. return
  132. endif
  133. if !filereadable(s:root.'/build/index.js')
  134. if isdirectory(s:root.'/src')
  135. echohl Error | echom '[coc.nvim] build/index.js not found, please install dependencies and compile coc.nvim by: yarn install' | echohl None
  136. else
  137. echohl Error | echon '[coc.nvim] your coc.nvim is broken.' | echohl None
  138. endif
  139. return
  140. endif
  141. return [node] + get(g:, 'coc_node_args', ['--no-warnings']) + [s:root.'/build/index.js']
  142. endfunction
  143. function! coc#util#jump(cmd, filepath, ...) abort
  144. if a:cmd != 'pedit'
  145. silent! normal! m'
  146. endif
  147. let path = a:filepath
  148. if (has('win32unix'))
  149. let path = substitute(a:filepath, '\v\\', '/', 'g')
  150. endif
  151. let file = fnamemodify(path, ":~:.")
  152. if a:cmd == 'pedit'
  153. let extra = empty(get(a:, 1, [])) ? '' : '+'.(a:1[0] + 1)
  154. exe 'pedit '.extra.' '.fnameescape(file)
  155. return
  156. elseif a:cmd == 'drop' && exists('*bufadd')
  157. let dstbuf = bufadd(path)
  158. let binfo = getbufinfo(dstbuf)
  159. if len(binfo) == 1 && empty(binfo[0].windows)
  160. exec 'buffer '.dstbuf
  161. let &buflisted = 1
  162. else
  163. exec 'drop '.fnameescape(file)
  164. endif
  165. elseif a:cmd == 'edit' && bufloaded(file)
  166. exe 'b '.bufnr(file)
  167. else
  168. call s:safer_open(a:cmd, file)
  169. endif
  170. if !empty(get(a:, 1, []))
  171. let line = getline(a:1[0] + 1)
  172. " TODO need to use utf16 here
  173. let col = byteidx(line, a:1[1]) + 1
  174. if col == 0
  175. let col = 999
  176. endif
  177. call cursor(a:1[0] + 1, col)
  178. endif
  179. if &filetype ==# ''
  180. filetype detect
  181. endif
  182. if s:is_vim
  183. redraw
  184. endif
  185. endfunction
  186. function! s:safer_open(cmd, file) abort
  187. " How to support :pedit and :drop?
  188. let is_supported_cmd = index(["edit", "split", "vsplit", "tabe"], a:cmd) >= 0
  189. " Use special handling only for URI.
  190. let looks_like_uri = match(a:file, "^.*://") >= 0
  191. if looks_like_uri && is_supported_cmd && has('win32') && exists('*bufadd')
  192. " Workaround a bug for Win32 paths.
  193. "
  194. " reference:
  195. " - https://github.com/vim/vim/issues/541
  196. " - https://github.com/neoclide/coc-java/issues/82
  197. " - https://github.com/vim-jp/issues/issues/6
  198. let buf = bufadd(a:file)
  199. if a:cmd != 'edit'
  200. " Open split, tab, etc. by a:cmd.
  201. exe a:cmd
  202. endif
  203. " Set current buffer to the file
  204. exe 'keepjumps buffer ' . buf
  205. else
  206. exe a:cmd.' '.fnameescape(a:file)
  207. endif
  208. endfunction
  209. function! coc#util#variables(bufnr) abort
  210. let info = getbufinfo(a:bufnr)
  211. let variables = empty(info) ? {} : copy(info[0]['variables'])
  212. for key in keys(variables)
  213. if key !~# '\v^coc'
  214. unlet variables[key]
  215. endif
  216. endfor
  217. return variables
  218. endfunction
  219. function! coc#util#with_callback(method, args, cb)
  220. function! s:Cb() closure
  221. try
  222. let res = call(a:method, a:args)
  223. call a:cb(v:null, res)
  224. catch /.*/
  225. call a:cb(v:exception)
  226. endtry
  227. endfunction
  228. let timeout = s:is_vim ? 10 : 0
  229. call timer_start(timeout, {-> s:Cb() })
  230. endfunction
  231. function! coc#util#timer(method, args)
  232. call timer_start(0, { -> s:Call(a:method, a:args)})
  233. endfunction
  234. function! s:Call(method, args)
  235. try
  236. call call(a:method, a:args)
  237. redraw
  238. catch /.*/
  239. return 0
  240. endtry
  241. endfunction
  242. function! coc#util#vim_info()
  243. return {
  244. \ 'apiversion': s:vim_api_version,
  245. \ 'mode': mode(),
  246. \ 'config': get(g:, 'coc_user_config', {}),
  247. \ 'floating': has('nvim') && exists('*nvim_open_win') ? v:true : v:false,
  248. \ 'extensionRoot': coc#util#extension_root(),
  249. \ 'globalExtensions': get(g:, 'coc_global_extensions', []),
  250. \ 'lines': &lines,
  251. \ 'columns': &columns,
  252. \ 'cmdheight': &cmdheight,
  253. \ 'pid': coc#util#getpid(),
  254. \ 'filetypeMap': get(g:, 'coc_filetype_map', {}),
  255. \ 'version': coc#util#version(),
  256. \ 'completeOpt': &completeopt,
  257. \ 'pumevent': 1,
  258. \ 'isVim': has('nvim') ? v:false : v:true,
  259. \ 'isCygwin': has('win32unix') ? v:true : v:false,
  260. \ 'isMacvim': has('gui_macvim') ? v:true : v:false,
  261. \ 'isiTerm': $TERM_PROGRAM ==# "iTerm.app",
  262. \ 'colorscheme': get(g:, 'colors_name', ''),
  263. \ 'workspaceFolders': get(g:, 'WorkspaceFolders', v:null),
  264. \ 'background': &background,
  265. \ 'runtimepath': join(globpath(&runtimepath, '', 0, 1), ','),
  266. \ 'locationlist': get(g:,'coc_enable_locationlist', 1),
  267. \ 'progpath': v:progpath,
  268. \ 'guicursor': &guicursor,
  269. \ 'pumwidth': exists('&pumwidth') ? &pumwidth : 15,
  270. \ 'tabCount': tabpagenr('$'),
  271. \ 'updateHighlight': has('nvim-0.5.0') || has('patch-8.1.1719') ? v:true : v:false,
  272. \ 'vimCommands': get(g:, 'coc_vim_commands', []),
  273. \ 'sign': exists('*sign_place') && exists('*sign_unplace'),
  274. \ 'ambiguousIsNarrow': &ambiwidth ==# 'single' ? v:true : v:false,
  275. \ 'textprop': has('textprop') && has('patch-8.1.1719') && !has('nvim') ? v:true : v:false,
  276. \ 'dialog': has('nvim-0.4.0') || has('patch-8.2.0750') ? v:true : v:false,
  277. \ 'semanticHighlights': coc#util#semantic_hlgroups()
  278. \}
  279. endfunction
  280. function! coc#util#all_state()
  281. return {
  282. \ 'bufnr': bufnr('%'),
  283. \ 'winid': win_getid(),
  284. \ 'bufnrs': map(getbufinfo({'bufloaded': 1}),'v:val["bufnr"]'),
  285. \ 'winids': map(getwininfo(),'v:val["winid"]'),
  286. \ }
  287. endfunction
  288. function! coc#util#install() abort
  289. let yarncmd = get(g:, 'coc_install_yarn_cmd', executable('yarnpkg') ? 'yarnpkg' : 'yarn')
  290. call coc#ui#open_terminal({
  291. \ 'cwd': s:root,
  292. \ 'cmd': yarncmd.' install --frozen-lockfile --ignore-engines',
  293. \ 'autoclose': 0,
  294. \ })
  295. endfunction
  296. function! coc#util#extension_root() abort
  297. if get(g:, 'coc_node_env', '') ==# 'test'
  298. return s:root.'/src/__tests__/extensions'
  299. endif
  300. if !empty(get(g:, 'coc_extension_root', ''))
  301. echohl Error | echon 'g:coc_extension_root not used any more, use g:coc_data_home instead' | echohl None
  302. endif
  303. return coc#util#get_data_home().'/extensions'
  304. endfunction
  305. function! coc#util#update_extensions(...) abort
  306. let async = get(a:, 1, 0)
  307. if async
  308. call coc#rpc#notify('updateExtensions', [])
  309. else
  310. call coc#rpc#request('updateExtensions', [v:true])
  311. endif
  312. endfunction
  313. function! coc#util#install_extension(args) abort
  314. let names = filter(copy(a:args), 'v:val !~# "^-"')
  315. let isRequest = index(a:args, '-sync') != -1
  316. if isRequest
  317. call coc#rpc#request('installExtensions', names)
  318. else
  319. call coc#rpc#notify('installExtensions', names)
  320. endif
  321. endfunction
  322. function! coc#util#do_autocmd(name) abort
  323. if exists('#User#'.a:name)
  324. exe 'doautocmd <nomodeline> User '.a:name
  325. endif
  326. endfunction
  327. function! coc#util#rebuild()
  328. let dir = coc#util#extension_root()
  329. if !isdirectory(dir) | return | endif
  330. call coc#ui#open_terminal({
  331. \ 'cwd': dir,
  332. \ 'cmd': 'npm rebuild',
  333. \ 'keepfocus': 1,
  334. \})
  335. endfunction
  336. function! coc#util#unmap(bufnr, keys) abort
  337. if bufnr('%') == a:bufnr
  338. for key in a:keys
  339. exe 'silent! nunmap <buffer> '.key
  340. endfor
  341. endif
  342. endfunction
  343. function! coc#util#refactor_foldlevel(lnum) abort
  344. if a:lnum <= 2 | return 0 | endif
  345. let line = getline(a:lnum)
  346. if line =~# '^\%u3000\s*$' | return 0 | endif
  347. return 1
  348. endfunction
  349. function! coc#util#refactor_fold_text(lnum) abort
  350. let range = ''
  351. let info = get(b:line_infos, a:lnum, [])
  352. if !empty(info)
  353. let range = info[0].':'.info[1]
  354. endif
  355. return trim(getline(a:lnum)[3:]).' '.range
  356. endfunction
  357. " get tabsize & expandtab option
  358. function! coc#util#get_format_opts(bufnr) abort
  359. let bufnr = a:bufnr && bufloaded(a:bufnr) ? a:bufnr : bufnr('%')
  360. let tabsize = getbufvar(bufnr, '&shiftwidth')
  361. if tabsize == 0
  362. let tabsize = getbufvar(bufnr, '&tabstop')
  363. endif
  364. return {
  365. \ 'tabsize': tabsize,
  366. \ 'expandtab': getbufvar(bufnr, '&expandtab'),
  367. \ 'insertFinalNewline': getbufvar(bufnr, '&eol'),
  368. \ 'trimTrailingWhitespace': getbufvar(bufnr, 'coc_trim_trailing_whitespace', 0),
  369. \ 'trimFinalNewlines': getbufvar(bufnr, 'coc_trim_final_newlines', 0)
  370. \ }
  371. endfunction
  372. function! coc#util#get_editoroption(winid) abort
  373. if !coc#compat#win_is_valid(a:winid)
  374. return v:null
  375. endif
  376. if has('nvim') && exists('*nvim_win_get_config')
  377. " avoid float window
  378. let config = nvim_win_get_config(a:winid)
  379. if !empty(get(config, 'relative', ''))
  380. return v:null
  381. endif
  382. endif
  383. let info = getwininfo(a:winid)[0]
  384. let bufnr = info['bufnr']
  385. let buftype = getbufvar(bufnr, '&buftype')
  386. " avoid window for other purpose.
  387. if buftype !=# '' && buftype !=# 'acwrite'
  388. return v:null
  389. endif
  390. let tabSize = getbufvar(bufnr, '&shiftwidth')
  391. if tabSize == 0
  392. let tabSize = getbufvar(bufnr, '&tabstop')
  393. endif
  394. return {
  395. \ 'bufnr': bufnr,
  396. \ 'winid': a:winid,
  397. \ 'winids': map(getwininfo(), 'v:val["winid"]'),
  398. \ 'tabpagenr': info['tabnr'],
  399. \ 'winnr': winnr(),
  400. \ 'visibleRanges': s:visible_ranges(a:winid),
  401. \ 'tabSize': tabSize,
  402. \ 'insertSpaces': getbufvar(bufnr, '&expandtab') ? v:true : v:false
  403. \ }
  404. endfunction
  405. function! coc#util#getpid()
  406. if !has('win32unix')
  407. return getpid()
  408. endif
  409. let cmd = 'cat /proc/' . getpid() . '/winpid'
  410. return substitute(system(cmd), '\v\n', '', 'gi')
  411. endfunction
  412. " Get indentkeys for indent on TextChangedP, consider = for word indent only.
  413. function! coc#util#get_indentkeys() abort
  414. if empty(&indentexpr)
  415. return ''
  416. endif
  417. if &indentkeys !~# '='
  418. return ''
  419. endif
  420. return &indentkeys
  421. endfunction
  422. function! coc#util#get_bufoptions(bufnr) abort
  423. if !bufloaded(a:bufnr) | return v:null | endif
  424. let bufname = bufname(a:bufnr)
  425. let buftype = getbufvar(a:bufnr, '&buftype')
  426. let winid = bufwinid(a:bufnr)
  427. let size = -1
  428. if bufnr('%') == a:bufnr
  429. let size = line2byte(line("$") + 1)
  430. elseif !empty(bufname)
  431. let size = getfsize(bufname)
  432. endif
  433. let lines = v:null
  434. if getbufvar(a:bufnr, 'coc_enabled', 1) && (buftype == '' || buftype == 'acwrite') && size < get(g:, 'coc_max_filesize', 2097152)
  435. let lines = getbufline(a:bufnr, 1, '$')
  436. endif
  437. return {
  438. \ 'bufnr': a:bufnr,
  439. \ 'size': size,
  440. \ 'lines': lines,
  441. \ 'winid': winid,
  442. \ 'bufname': bufname,
  443. \ 'buftype': buftype,
  444. \ 'previewwindow': v:false,
  445. \ 'eol': getbufvar(a:bufnr, '&eol'),
  446. \ 'indentkeys': coc#util#get_indentkeys(),
  447. \ 'variables': coc#util#variables(a:bufnr),
  448. \ 'filetype': getbufvar(a:bufnr, '&filetype'),
  449. \ 'iskeyword': getbufvar(a:bufnr, '&iskeyword'),
  450. \ 'changedtick': getbufvar(a:bufnr, 'changedtick'),
  451. \ 'fullpath': empty(bufname) ? '' : fnamemodify(bufname, ':p'),
  452. \}
  453. endfunction
  454. function! coc#util#get_config_home()
  455. if !empty(get(g:, 'coc_config_home', ''))
  456. return resolve(expand(g:coc_config_home))
  457. endif
  458. if exists('$VIMCONFIG')
  459. return resolve($VIMCONFIG)
  460. endif
  461. if has('nvim')
  462. if exists('$XDG_CONFIG_HOME')
  463. return resolve($XDG_CONFIG_HOME."/nvim")
  464. endif
  465. if s:is_win
  466. return resolve($HOME.'/AppData/Local/nvim')
  467. endif
  468. return resolve($HOME.'/.config/nvim')
  469. else
  470. if s:is_win
  471. return resolve($HOME."/vimfiles")
  472. endif
  473. return resolve($HOME.'/.vim')
  474. endif
  475. endfunction
  476. function! coc#util#get_data_home()
  477. if !empty(get(g:, 'coc_data_home', ''))
  478. let dir = resolve(expand(g:coc_data_home))
  479. else
  480. if exists('$XDG_CONFIG_HOME')
  481. let dir = resolve($XDG_CONFIG_HOME."/coc")
  482. else
  483. if s:is_win
  484. let dir = resolve(expand('~/AppData/Local/coc'))
  485. else
  486. let dir = resolve(expand('~/.config/coc'))
  487. endif
  488. endif
  489. endif
  490. if !isdirectory(dir)
  491. call coc#notify#create(['creating data directory: '.dir], {
  492. \ 'borderhighlight': 'CocInfoSign',
  493. \ 'timeout': 5000,
  494. \ 'kind': 'info',
  495. \ })
  496. call mkdir(dir, "p", 0755)
  497. endif
  498. return dir
  499. endfunction
  500. function! coc#util#get_complete_option()
  501. if get(b:,"coc_suggest_disable",0)
  502. return v:null
  503. endif
  504. let pos = getcurpos()
  505. let line = getline(pos[1])
  506. let input = matchstr(strpart(line, 0, pos[2] - 1), '\k*$')
  507. let col = pos[2] - strlen(input)
  508. return {
  509. \ 'word': matchstr(strpart(line, col - 1), '^\k\+'),
  510. \ 'input': empty(input) ? '' : input,
  511. \ 'line': line,
  512. \ 'filetype': &filetype,
  513. \ 'filepath': expand('%:p'),
  514. \ 'bufnr': bufnr('%'),
  515. \ 'linenr': pos[1],
  516. \ 'colnr' : pos[2],
  517. \ 'col': col - 1,
  518. \ 'changedtick': b:changedtick,
  519. \ 'blacklist': get(b:, 'coc_suggest_blacklist', []),
  520. \ 'disabled': get(b:, 'coc_disabled_sources', []),
  521. \ 'indentkeys': coc#util#get_indentkeys()
  522. \}
  523. endfunction
  524. " used by vim
  525. function! coc#util#get_buf_lines(bufnr, changedtick)
  526. if !bufloaded(a:bufnr)
  527. return v:null
  528. endif
  529. let changedtick = getbufvar(a:bufnr, 'changedtick')
  530. if changedtick == a:changedtick
  531. return v:null
  532. endif
  533. return {
  534. \ 'lines': getbufline(a:bufnr, 1, '$'),
  535. \ 'changedtick': getbufvar(a:bufnr, 'changedtick')
  536. \ }
  537. endfunction
  538. " used for TextChangedI with InsertCharPre
  539. function! coc#util#get_changeinfo()
  540. return {
  541. \ 'bufnr': bufnr('%'),
  542. \ 'lnum': line('.'),
  543. \ 'line': getline('.'),
  544. \ 'changedtick': b:changedtick,
  545. \}
  546. endfunction
  547. function! s:visible_ranges(winid) abort
  548. let info = getwininfo(a:winid)[0]
  549. let res = []
  550. if !has_key(info, 'topline') || !has_key(info, 'botline')
  551. return res
  552. endif
  553. let begin = 0
  554. let curr = info['topline']
  555. let max = info['botline']
  556. if win_getid() != a:winid
  557. return [[curr, max]]
  558. endif
  559. while curr <= max
  560. let closedend = foldclosedend(curr)
  561. if closedend == -1
  562. let begin = begin == 0 ? curr : begin
  563. if curr == max
  564. call add(res, [begin, curr])
  565. endif
  566. let curr = curr + 1
  567. else
  568. if begin != 0
  569. call add(res, [begin, curr - 1])
  570. let begin = closedend + 1
  571. endif
  572. let curr = closedend + 1
  573. endif
  574. endwhile
  575. return res
  576. endfunction