list.vim 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. scriptencoding utf-8
  2. let s:is_vim = !has('nvim')
  3. let s:prefix = '[List Preview]'
  4. let s:sign_group = 'CocList'
  5. " filetype detect could be slow.
  6. let s:filetype_map = {
  7. \ 'c': 'c',
  8. \ 'py': 'python',
  9. \ 'vim': 'vim',
  10. \ 'ts': 'typescript',
  11. \ 'js': 'javascript',
  12. \ 'html': 'html',
  13. \ 'css': 'css'
  14. \ }
  15. function! coc#list#getchar() abort
  16. return coc#prompt#getchar()
  17. endfunction
  18. function! coc#list#setlines(bufnr, lines, append)
  19. if a:append
  20. silent call appendbufline(a:bufnr, '$', a:lines)
  21. else
  22. if exists('*deletebufline')
  23. silent call deletebufline(a:bufnr, len(a:lines) + 1, '$')
  24. else
  25. let n = len(a:lines) + 1
  26. let saved_reg = @"
  27. silent execute n.',$d'
  28. let @" = saved_reg
  29. endif
  30. silent call setbufline(a:bufnr, 1, a:lines)
  31. endif
  32. endfunction
  33. function! coc#list#options(...)
  34. let list = ['--top', '--tab', '--normal', '--no-sort', '--input', '--strict',
  35. \ '--regex', '--interactive', '--number-select', '--auto-preview',
  36. \ '--ignore-case', '--no-quit', '--first', '--reverse']
  37. if get(g:, 'coc_enabled', 0)
  38. let names = coc#rpc#request('listNames', [])
  39. call extend(list, names)
  40. endif
  41. return join(list, "\n")
  42. endfunction
  43. function! coc#list#names(...) abort
  44. let names = coc#rpc#request('listNames', [])
  45. return join(names, "\n")
  46. endfunction
  47. function! coc#list#status(name)
  48. if !exists('b:list_status') | return '' | endif
  49. return get(b:list_status, a:name, '')
  50. endfunction
  51. function! coc#list#create(position, height, name, numberSelect)
  52. if a:position ==# 'tab'
  53. execute 'silent tabe list:///'.a:name
  54. else
  55. execute 'silent keepalt '.(a:position ==# 'top' ? '' : 'botright').a:height.'sp list:///'.a:name
  56. execute 'resize '.a:height
  57. endif
  58. if a:numberSelect
  59. setl norelativenumber
  60. setl number
  61. else
  62. setl nonumber
  63. setl norelativenumber
  64. endif
  65. return [bufnr('%'), win_getid(), tabpagenr()]
  66. endfunction
  67. " close list windows
  68. function! coc#list#clean_up() abort
  69. for i in range(1, winnr('$'))
  70. let bufname = bufname(winbufnr(i))
  71. if bufname =~# 'list://'
  72. execute i.'close!'
  73. endif
  74. endfor
  75. endfunction
  76. function! coc#list#setup(source)
  77. let b:list_status = {}
  78. setl buftype=nofile nobuflisted nofen nowrap
  79. setl norelativenumber bufhidden=wipe nocursorline winfixheight
  80. setl tabstop=1 nolist nocursorcolumn undolevels=-1
  81. setl signcolumn=auto
  82. if has('nvim-0.5.0') || has('patch-8.1.0864')
  83. setl scrolloff=0
  84. endif
  85. setl filetype=list
  86. syntax case ignore
  87. let source = a:source[8:]
  88. let name = toupper(source[0]).source[1:]
  89. execute 'syntax match Coc'.name.'Line /\v^.*$/'
  90. if !s:is_vim
  91. " Repeat press <C-f> and <C-b> would invoke <esc> on vim
  92. nnoremap <silent><nowait><buffer> <esc> <C-w>c
  93. endif
  94. endfunction
  95. function! coc#list#select(bufnr, line) abort
  96. call sign_unplace(s:sign_group, { 'buffer': a:bufnr })
  97. if a:line > 0
  98. call sign_place(6, s:sign_group, 'CocListCurrent', a:bufnr, {'lnum': a:line})
  99. endif
  100. endfunction
  101. " Check if previewwindow exists on current tab.
  102. function! coc#list#has_preview()
  103. for i in range(1, winnr('$'))
  104. let preview = getwinvar(i, 'previewwindow', getwinvar(i, '&previewwindow', 0))
  105. if preview
  106. return i
  107. endif
  108. endfor
  109. return 0
  110. endfunction
  111. " Get previewwindow from tabnr, use 0 for current tab
  112. function! coc#list#get_preview(...) abort
  113. let tabnr = get(a:, 1, 0) == 0 ? tabpagenr() : a:1
  114. let info = gettabinfo(tabnr)
  115. if !empty(info)
  116. for win in info[0]['windows']
  117. if gettabwinvar(tabnr, win, 'previewwindow', 0)
  118. return win
  119. endif
  120. endfor
  121. endif
  122. return -1
  123. endfunction
  124. function! coc#list#scroll_preview(dir) abort
  125. let winnr = coc#list#has_preview()
  126. if !winnr
  127. return
  128. endif
  129. let winid = win_getid(winnr)
  130. if exists('*win_execute')
  131. call win_execute(winid, "normal! ".(a:dir ==# 'up' ? "\<C-u>" : "\<C-d>"))
  132. else
  133. let id = win_getid()
  134. noa call win_gotoid(winid)
  135. execute "normal! ".(a:dir ==# 'up' ? "\<C-u>" : "\<C-d>")
  136. noa call win_gotoid(id)
  137. endif
  138. endfunction
  139. function! coc#list#close_preview(tabnr) abort
  140. let winid = coc#list#get_preview(a:tabnr)
  141. if winid != -1
  142. call coc#window#close(winid)
  143. endif
  144. endfunction
  145. " Improve preview performance by reused window & buffer.
  146. " lines - list of lines
  147. " config.position - could be 'below' 'top' 'tab'.
  148. " config.winid - id of original window.
  149. " config.name - (optional )name of preview buffer.
  150. " config.splitRight - (optional) split to right when 1.
  151. " config.lnum - (optional) current line number
  152. " config.filetype - (optional) filetype of lines.
  153. " config.hlGroup - (optional) highlight group.
  154. " config.maxHeight - (optional) max height of window, valid for 'below' & 'top' position.
  155. function! coc#list#preview(lines, config) abort
  156. let name = fnamemodify(get(a:config, 'name', ''), ':.')
  157. let lines = a:lines
  158. if empty(lines)
  159. if get(a:config, 'scheme', 'file') != 'file'
  160. let bufnr = s:load_buffer(name)
  161. let lines = bufnr == 0 ? [''] : getbufline(bufnr, 1, '$')
  162. else
  163. " Show empty lines so not close window.
  164. let lines = ['']
  165. endif
  166. endif
  167. let winid = coc#list#get_preview(0)
  168. let bufnr = winid == -1 ? 0 : winbufnr(winid)
  169. " Try reuse buffer & window
  170. let bufnr = coc#float#create_buf(bufnr, lines)
  171. if bufnr == 0
  172. return
  173. endif
  174. call setbufvar(bufnr, '&synmaxcol', 500)
  175. let filetype = get(a:config, 'filetype', '')
  176. let extname = matchstr(name, '\.\zs[^.]\+$')
  177. if empty(filetype) && !empty(extname)
  178. let filetype = get(s:filetype_map, extname, '')
  179. endif
  180. let range = get(a:config, 'range', v:null)
  181. let hlGroup = get(a:config, 'hlGroup', 'Search')
  182. let lnum = get(a:config, 'lnum', 1)
  183. let position = get(a:config, 'position', 'below')
  184. let original = get(a:config, 'winid', -1)
  185. if winid == -1
  186. let change = position != 'tab' && get(a:config, 'splitRight', 0)
  187. let curr = win_getid()
  188. if change
  189. if original && win_id2win(original)
  190. noa call win_gotoid(original)
  191. else
  192. noa wincmd t
  193. endif
  194. execute 'noa belowright vert sb '.bufnr
  195. let winid = win_getid()
  196. elseif position == 'tab' || get(a:config, 'splitRight', 0)
  197. execute 'noa belowright vert sb '.bufnr
  198. let winid = win_getid()
  199. else
  200. let mod = position == 'top' ? 'below' : 'above'
  201. let height = s:get_height(lines, a:config)
  202. execute 'noa '.mod.' sb +resize\ '.height.' '.bufnr
  203. let winid = win_getid()
  204. endif
  205. noa call winrestview({"lnum": lnum ,"topline":s:get_topline(a:config, lnum, winid)})
  206. call s:set_preview_options(winid)
  207. noa call win_gotoid(curr)
  208. else
  209. let height = s:get_height(lines, a:config)
  210. if height > 0
  211. if s:is_vim
  212. let curr = win_getid()
  213. noa call win_gotoid(winid)
  214. execute 'silent! noa resize '.height
  215. noa call win_gotoid(curr)
  216. else
  217. call nvim_win_set_height(winid, height)
  218. endif
  219. endif
  220. call coc#compat#execute(winid, ['syntax clear', 'noa call winrestview({"lnum":'.lnum.',"topline":'.s:get_topline(a:config, lnum, winid).'})'])
  221. endif
  222. call setwinvar(winid, '&foldenable', 0)
  223. if s:prefix.' '.name != bufname(bufnr)
  224. if s:is_vim
  225. call win_execute(winid, 'noa file '.fnameescape(s:prefix.' '.name), 'silent!')
  226. else
  227. silent! noa call nvim_buf_set_name(bufnr, s:prefix.' '.name)
  228. endif
  229. endif
  230. " highlights
  231. if !empty(filetype)
  232. let start = max([0, lnum - 300])
  233. let end = min([len(lines), lnum + 300])
  234. call coc#highlight#highlight_lines(winid, [{'filetype': filetype, 'startLine': start, 'endLine': end}])
  235. call coc#compat#execute(winid, 'syn sync fromstart')
  236. else
  237. call coc#compat#execute(winid, 'filetype detect')
  238. let ft = getbufvar(bufnr, '&filetype', '')
  239. if !empty(extname) && !empty(ft)
  240. let s:filetype_map[extname] = ft
  241. endif
  242. endif
  243. call sign_unplace('CocCursorLine', {'buffer': bufnr})
  244. call coc#compat#execute(winid, 'call clearmatches()')
  245. if !s:is_vim
  246. " vim send <esc> to buffer on FocusLost, <C-w> and other cases
  247. call coc#compat#execute(winid, 'nnoremap <silent><nowait><buffer> <esc> :call CocActionAsync("listCancel")<CR>')
  248. endif
  249. if !empty(range)
  250. call sign_place(1, 'CocCursorLine', 'CocCurrentLine', bufnr, {'lnum': lnum})
  251. call coc#highlight#match_ranges(winid, bufnr, [range], hlGroup, 10)
  252. endif
  253. endfunction
  254. function! s:get_height(lines, config) abort
  255. if get(a:config, 'splitRight', 0) || get(a:config, 'position', 'below') == 'tab'
  256. return 0
  257. endif
  258. let height = min([get(a:config, 'maxHeight', 10), len(a:lines), &lines - &cmdheight - 2])
  259. return height
  260. endfunction
  261. function! s:load_buffer(name) abort
  262. if exists('*bufadd') && exists('*bufload')
  263. let bufnr = bufadd(a:name)
  264. call bufload(bufnr)
  265. return bufnr
  266. endif
  267. return 0
  268. endfunction
  269. function! s:get_topline(config, lnum, winid) abort
  270. let toplineStyle = get(a:config, 'toplineStyle', 'offset')
  271. if toplineStyle == 'middle'
  272. return max([1, a:lnum - winheight(a:winid)/2])
  273. endif
  274. let toplineOffset = get(a:config, 'toplineOffset', 3)
  275. return max([1, a:lnum - toplineOffset])
  276. endfunction
  277. function! s:set_preview_options(winid) abort
  278. call setwinvar(a:winid, '&foldmethod', 'manual')
  279. call setwinvar(a:winid, '&signcolumn', 'no')
  280. call setwinvar(a:winid, '&number', 1)
  281. call setwinvar(a:winid, '&cursorline', 0)
  282. call setwinvar(a:winid, '&relativenumber', 0)
  283. call setwinvar(a:winid, 'previewwindow', 1)
  284. endfunction