buffer.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. "=============================================================================
  2. " FILE: buffer.vim
  3. " AUTHOR: Shougo Matsushita <Shougo.Matsu@gmail.com>
  4. " License: MIT license {{{
  5. " Permission is hereby granted, free of charge, to any person obtaining
  6. " a copy of this software and associated documentation files (the
  7. " "Software"), to deal in the Software without restriction, including
  8. " without limitation the rights to use, copy, modify, merge, publish,
  9. " distribute, sublicense, and/or sell copies of the Software, and to
  10. " permit persons to whom the Software is furnished to do so, subject to
  11. " the following conditions:
  12. "
  13. " The above copyright notice and this permission notice shall be included
  14. " in all copies or substantial portions of the Software.
  15. "
  16. " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  17. " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  19. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  20. " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  21. " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  22. " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. " }}}
  24. "=============================================================================
  25. let s:save_cpo = &cpo
  26. set cpo&vim
  27. " Global options definition. "{{{
  28. let g:neocomplete#sources#buffer#cache_limit_size =
  29. \ get(g:, 'neocomplete#sources#buffer#cache_limit_size', 500000)
  30. let g:neocomplete#sources#buffer#disabled_pattern =
  31. \ get(g:, 'neocomplete#sources#buffer#disabled_pattern', '')
  32. let g:neocomplete#sources#buffer#max_keyword_width =
  33. \ get(g:, 'neocomplete#sources#buffer#max_keyword_width', 80)
  34. "}}}
  35. " Important variables.
  36. if !exists('s:buffer_sources')
  37. let s:buffer_sources = {}
  38. let s:async_dictionary_list = {}
  39. endif
  40. let s:source = {
  41. \ 'name' : 'buffer',
  42. \ 'kind' : 'manual',
  43. \ 'mark' : '[B]',
  44. \ 'rank' : 5,
  45. \ 'min_pattern_length' :
  46. \ g:neocomplete#auto_completion_start_length,
  47. \ 'hooks' : {},
  48. \ 'is_volatile' : 1,
  49. \}
  50. function! s:source.hooks.on_init(context) abort "{{{
  51. let s:buffer_sources = {}
  52. augroup neocomplete "{{{
  53. autocmd BufEnter,BufRead,BufWinEnter,BufWritePost *
  54. \ call s:check_source()
  55. autocmd InsertEnter,InsertLeave *
  56. \ call neocomplete#sources#buffer#make_cache_current_line()
  57. autocmd VimLeavePre * call s:clean()
  58. augroup END"}}}
  59. " Create cache directory.
  60. call neocomplete#cache#make_directory('buffer_cache')
  61. call neocomplete#cache#make_directory('buffer_temp')
  62. " Initialize script variables. "{{{
  63. let s:buffer_sources = {}
  64. let s:async_dictionary_list = {}
  65. "}}}
  66. call s:make_cache_buffer(bufnr('%'))
  67. call s:check_source()
  68. endfunction
  69. "}}}
  70. function! s:source.hooks.on_final(context) abort "{{{
  71. silent! delcommand NeoCompleteBufferMakeCache
  72. let s:buffer_sources = {}
  73. endfunction"}}}
  74. function! s:source.hooks.on_post_filter(context) abort "{{{
  75. " Filters too long word.
  76. call filter(a:context.candidates,
  77. \ 'len(v:val.word) < g:neocomplete#sources#buffer#max_keyword_width')
  78. endfunction"}}}
  79. function! s:source.gather_candidates(context) abort "{{{
  80. call s:check_async_cache(a:context)
  81. let keyword_list = []
  82. for source in s:get_sources_list(a:context)
  83. let keyword_list += source.words
  84. endfor
  85. return keyword_list
  86. endfunction"}}}
  87. function! neocomplete#sources#buffer#define() abort "{{{
  88. return s:source
  89. endfunction"}}}
  90. function! neocomplete#sources#buffer#get_frequencies() abort "{{{
  91. return get(get(s:buffer_sources, bufnr('%'), {}), 'frequencies', {})
  92. endfunction"}}}
  93. function! neocomplete#sources#buffer#make_cache_current_line() abort "{{{
  94. if neocomplete#is_locked()
  95. return
  96. endif
  97. " let start = reltime()
  98. call s:make_cache_current_buffer(
  99. \ max([1, line('.') - winline()]),
  100. \ min([line('$'), line('.') + winheight(0) - winline()]))
  101. " echomsg reltimestr(reltime(start))
  102. endfunction"}}}
  103. function! s:should_create_cache(bufnr) " {{{
  104. let filepath = fnamemodify(bufname(a:bufnr), ':p')
  105. return getfsize(filepath) < g:neocomplete#sources#buffer#cache_limit_size
  106. \ && getbufvar(a:bufnr, '&modifiable')
  107. \ && !getwinvar(bufwinnr(a:bufnr), '&previewwindow')
  108. \ && (g:neocomplete#sources#buffer#disabled_pattern == ''
  109. \ || filepath !~# g:neocomplete#sources#buffer#disabled_pattern)
  110. endfunction"}}}
  111. function! s:get_sources_list(context) abort "{{{
  112. let filetypes_dict = {}
  113. for filetype in a:context.filetypes
  114. let filetypes_dict[filetype] = 1
  115. endfor
  116. return values(filter(copy(s:buffer_sources),
  117. \ "has_key(filetypes_dict, v:val.filetype)
  118. \ || has_key(filetypes_dict, '_')
  119. \ || bufnr('%') == v:key
  120. \ || (bufname('%') ==# '[Command Line]' && bufwinnr('#') == v:key)"))
  121. endfunction"}}}
  122. function! s:initialize_source(srcname) abort "{{{
  123. let path = fnamemodify(bufname(a:srcname), ':p')
  124. let filename = fnamemodify(path, ':t')
  125. if filename == ''
  126. let filename = '[No Name]'
  127. let path .= '/[No Name]'
  128. endif
  129. let ft = getbufvar(a:srcname, '&filetype')
  130. if ft == ''
  131. let ft = 'nothing'
  132. endif
  133. let keyword_pattern = neocomplete#get_keyword_pattern(ft, s:source.name)
  134. let s:buffer_sources[a:srcname] = {
  135. \ 'words' : [],
  136. \ 'frequencies' : {},
  137. \ 'name' : filename, 'filetype' : ft,
  138. \ 'keyword_pattern' : keyword_pattern,
  139. \ 'cached_time' : 0,
  140. \ 'path' : path,
  141. \ 'cache_name' : neocomplete#cache#encode_name('buffer_cache', path),
  142. \}
  143. endfunction"}}}
  144. function! s:make_cache_file(srcname) abort "{{{
  145. " Initialize source.
  146. if !has_key(s:buffer_sources, a:srcname)
  147. call s:initialize_source(a:srcname)
  148. endif
  149. let source = s:buffer_sources[a:srcname]
  150. if !filereadable(source.path)
  151. \ || getbufvar(a:srcname, '&modified')
  152. \ || getbufvar(a:srcname, '&buftype') =~ 'nofile\|acwrite'
  153. call s:make_cache_buffer(a:srcname)
  154. return
  155. endif
  156. call neocomplete#print_debug('make_cache_buffer: ' . source.path)
  157. let source.cache_name =
  158. \ neocomplete#cache#async_load_from_file(
  159. \ 'buffer_cache', source.path,
  160. \ source.keyword_pattern, 'B')
  161. let source.cached_time = localtime()
  162. let source.filetype = getbufvar(a:srcname, '&filetype')
  163. let s:async_dictionary_list[source.path] = [{
  164. \ 'filename' : source.path,
  165. \ 'cachename' : source.cache_name,
  166. \ }]
  167. endfunction"}}}
  168. function! s:make_cache_buffer(srcname) abort "{{{
  169. if !s:should_create_cache(a:srcname)
  170. return
  171. endif
  172. call neocomplete#print_debug('make_cache_buffer: ' . a:srcname)
  173. if !s:exists_current_source()
  174. call s:initialize_source(a:srcname)
  175. if a:srcname ==# bufnr('%')
  176. " Force sync cache
  177. call s:make_cache_current_buffer(1, 1000)
  178. return
  179. endif
  180. endif
  181. let source = s:buffer_sources[a:srcname]
  182. let temp = neocomplete#cache#getfilename(
  183. \ 'buffer_temp', getpid() . '_' . a:srcname)
  184. let lines = getbufline(a:srcname, 1, '$')
  185. call writefile(lines, temp)
  186. " Create temporary file
  187. let source.cache_name =
  188. \ neocomplete#cache#async_load_from_file(
  189. \ 'buffer_cache', temp,
  190. \ source.keyword_pattern, 'B')
  191. let source.cached_time = localtime()
  192. let source.filetype = getbufvar(a:srcname, '&filetype')
  193. if source.filetype == ''
  194. let source.filetype = 'nothing'
  195. endif
  196. let s:async_dictionary_list[source.path] = [{
  197. \ 'filename' : temp,
  198. \ 'cachename' : source.cache_name,
  199. \ }]
  200. endfunction"}}}
  201. function! s:check_changed_buffer(bufnr) abort "{{{
  202. let source = s:buffer_sources[a:bufnr]
  203. let ft = getbufvar(a:bufnr, '&filetype')
  204. if ft == ''
  205. let ft = 'nothing'
  206. endif
  207. let filename = fnamemodify(bufname(a:bufnr), ':t')
  208. if filename == ''
  209. let filename = '[No Name]'
  210. endif
  211. return source.name != filename || source.filetype != ft
  212. endfunction"}}}
  213. function! s:check_source() abort "{{{
  214. " Check new buffer.
  215. call map(filter(range(1, bufnr('$')), "
  216. \ (v:val != bufnr('%') || neocomplete#has_vimproc())
  217. \ && (!has_key(s:buffer_sources, v:val) && buflisted(v:val)
  218. \ || (has_key(s:buffer_sources, v:val) &&
  219. \ s:buffer_sources[v:val].cached_time
  220. \ < getftime(s:buffer_sources[v:val].path)))
  221. \ && (!neocomplete#is_locked(v:val) ||
  222. \ g:neocomplete#disable_auto_complete)
  223. \ && s:should_create_cache(v:val)
  224. \ "), 's:make_cache_file(v:val)')
  225. " Remove unlisted buffers.
  226. call filter(s:buffer_sources,
  227. \ "v:key == bufnr('%') || buflisted(str2nr(v:key))")
  228. endfunction"}}}
  229. function! s:exists_current_source() abort "{{{
  230. return has_key(s:buffer_sources, bufnr('%')) &&
  231. \ !s:check_changed_buffer(bufnr('%'))
  232. endfunction"}}}
  233. function! s:make_cache_current_buffer(start, end) abort "{{{
  234. let srcname = bufnr('%')
  235. " Make cache from current buffer.
  236. if !s:should_create_cache(srcname)
  237. return
  238. endif
  239. if !s:exists_current_source()
  240. call s:initialize_source(srcname)
  241. endif
  242. let source = s:buffer_sources[srcname]
  243. let keyword_pattern = source.keyword_pattern
  244. if keyword_pattern == ''
  245. return
  246. endif
  247. let words = []
  248. lua << EOF
  249. do
  250. local words = vim.eval('words')
  251. local dup = {}
  252. local min_length = vim.eval('g:neocomplete#min_keyword_length')
  253. for linenr = vim.eval('a:start'), vim.eval('a:end') do
  254. local match = 0
  255. while 1 do
  256. local match_str = vim.eval('matchstr(getline('..linenr..
  257. '), keyword_pattern, ' .. match .. ')')
  258. if match_str == '' then
  259. break
  260. end
  261. if dup[match_str] == nil
  262. and string.len(match_str) >= min_length then
  263. dup[match_str] = 1
  264. words:add(match_str)
  265. end
  266. -- Next match.
  267. match = vim.eval('matchend(getline(' .. linenr ..
  268. '), keyword_pattern, ' .. match .. ')')
  269. end
  270. end
  271. end
  272. EOF
  273. let source.words = neocomplete#util#uniq(source.words + words)
  274. endfunction"}}}
  275. function! s:check_async_cache(context) abort "{{{
  276. for source in s:get_sources_list(a:context)
  277. if !has_key(s:async_dictionary_list, source.path)
  278. continue
  279. endif
  280. " Load from cache.
  281. let [loaded, file_cache] = neocomplete#cache#get_cache_list(
  282. \ 'buffer_cache', s:async_dictionary_list[source.path])
  283. if loaded
  284. let source.words = file_cache
  285. endif
  286. if empty(s:async_dictionary_list[source.path])
  287. call remove(s:async_dictionary_list, source.path)
  288. endif
  289. endfor
  290. endfunction"}}}
  291. function! s:clean() abort "{{{
  292. " Remove temporary files
  293. for file in glob(printf('%s/%d_*',
  294. \ neocomplete#get_data_directory() . '/buffer_temp',
  295. \ getpid()), 1, 1)
  296. call delete(file)
  297. let cachefile = neocomplete#get_data_directory() . '/buffer_cache/'
  298. \ . substitute(substitute(file, ':', '=-', 'g'), '[/\\]', '=+', 'g')
  299. if filereadable(cachefile)
  300. call delete(cachefile)
  301. endif
  302. endfor
  303. endfunction"}}}
  304. " Command functions. "{{{
  305. function! neocomplete#sources#buffer#make_cache(name) abort "{{{
  306. if !neocomplete#is_enabled()
  307. call neocomplete#initialize()
  308. endif
  309. if a:name == ''
  310. let number = bufnr('%')
  311. else
  312. let number = bufnr(a:name)
  313. if number < 0
  314. let bufnr = bufnr('%')
  315. " No swap warning.
  316. let save_shm = &shortmess
  317. set shortmess+=A
  318. " Open new buffer.
  319. execute 'silent! edit' fnameescape(a:name)
  320. let &shortmess = save_shm
  321. if bufnr('%') != bufnr
  322. setlocal nobuflisted
  323. execute 'buffer' bufnr
  324. endif
  325. endif
  326. let number = bufnr(a:name)
  327. endif
  328. call s:make_cache_file(number)
  329. endfunction"}}}
  330. "}}}
  331. let &cpo = s:save_cpo
  332. unlet s:save_cpo
  333. " vim: foldmethod=marker