"=============================================================================
" FILE: buffer.vim
" AUTHOR: Shougo Matsushita <Shougo.Matsu@gmail.com>
" License: MIT license {{{
" Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
"=============================================================================
let s:save_cpo = &cpo
set cpo&vim
" Global options definition. "{{{
let g:neocomplete#sources#buffer#cache_limit_size =
\ get(g:, 'neocomplete#sources#buffer#cache_limit_size', 500000)
let g:neocomplete#sources#buffer#disabled_pattern =
\ get(g:, 'neocomplete#sources#buffer#disabled_pattern', '')
let g:neocomplete#sources#buffer#max_keyword_width =
\ get(g:, 'neocomplete#sources#buffer#max_keyword_width', 80)
"}}}
" Important variables.
if !exists('s:buffer_sources')
let s:buffer_sources = {}
let s:async_dictionary_list = {}
endif
let s:source = {
\ 'name' : 'buffer',
\ 'kind' : 'manual',
\ 'mark' : '[B]',
\ 'rank' : 5,
\ 'min_pattern_length' :
\ g:neocomplete#auto_completion_start_length,
\ 'hooks' : {},
\ 'is_volatile' : 1,
\}
function! s:source.hooks.on_init(context) abort "{{{
let s:buffer_sources = {}
augroup neocomplete "{{{
autocmd BufEnter,BufRead,BufWinEnter,BufWritePost *
\ call s:check_source()
autocmd InsertEnter,InsertLeave *
\ call neocomplete#sources#buffer#make_cache_current_line()
autocmd VimLeavePre * call s:clean()
augroup END"}}}
" Create cache directory.
call neocomplete#cache#make_directory('buffer_cache')
call neocomplete#cache#make_directory('buffer_temp')
" Initialize script variables. "{{{
let s:buffer_sources = {}
let s:async_dictionary_list = {}
"}}}
call s:make_cache_buffer(bufnr('%'))
call s:check_source()
endfunction
"}}}
function! s:source.hooks.on_final(context) abort "{{{
silent! delcommand NeoCompleteBufferMakeCache
let s:buffer_sources = {}
endfunction"}}}
function! s:source.hooks.on_post_filter(context) abort "{{{
" Filters too long word.
call filter(a:context.candidates,
\ 'len(v:val.word) < g:neocomplete#sources#buffer#max_keyword_width')
endfunction"}}}
function! s:source.gather_candidates(context) abort "{{{
call s:check_async_cache(a:context)
let keyword_list = []
for source in s:get_sources_list(a:context)
let keyword_list += source.words
endfor
return keyword_list
endfunction"}}}
function! neocomplete#sources#buffer#define() abort "{{{
return s:source
endfunction"}}}
function! neocomplete#sources#buffer#get_frequencies() abort "{{{
return get(get(s:buffer_sources, bufnr('%'), {}), 'frequencies', {})
endfunction"}}}
function! neocomplete#sources#buffer#make_cache_current_line() abort "{{{
if neocomplete#is_locked()
return
endif
" let start = reltime()
call s:make_cache_current_buffer(
\ max([1, line('.') - winline()]),
\ min([line('$'), line('.') + winheight(0) - winline()]))
" echomsg reltimestr(reltime(start))
endfunction"}}}
function! s:should_create_cache(bufnr) " {{{
let filepath = fnamemodify(bufname(a:bufnr), ':p')
return getfsize(filepath) < g:neocomplete#sources#buffer#cache_limit_size
\ && getbufvar(a:bufnr, '&modifiable')
\ && !getwinvar(bufwinnr(a:bufnr), '&previewwindow')
\ && (g:neocomplete#sources#buffer#disabled_pattern == ''
\ || filepath !~# g:neocomplete#sources#buffer#disabled_pattern)
endfunction"}}}
function! s:get_sources_list(context) abort "{{{
let filetypes_dict = {}
for filetype in a:context.filetypes
let filetypes_dict[filetype] = 1
endfor
return values(filter(copy(s:buffer_sources),
\ "has_key(filetypes_dict, v:val.filetype)
\ || has_key(filetypes_dict, '_')
\ || bufnr('%') == v:key
\ || (bufname('%') ==# '[Command Line]' && bufwinnr('#') == v:key)"))
endfunction"}}}
function! s:initialize_source(srcname) abort "{{{
let path = fnamemodify(bufname(a:srcname), ':p')
let filename = fnamemodify(path, ':t')
if filename == ''
let filename = '[No Name]'
let path .= '/[No Name]'
endif
let ft = getbufvar(a:srcname, '&filetype')
if ft == ''
let ft = 'nothing'
endif
let keyword_pattern = neocomplete#get_keyword_pattern(ft, s:source.name)
let s:buffer_sources[a:srcname] = {
\ 'words' : [],
\ 'frequencies' : {},
\ 'name' : filename, 'filetype' : ft,
\ 'keyword_pattern' : keyword_pattern,
\ 'cached_time' : 0,
\ 'path' : path,
\ 'cache_name' : neocomplete#cache#encode_name('buffer_cache', path),
\}
endfunction"}}}
function! s:make_cache_file(srcname) abort "{{{
" Initialize source.
if !has_key(s:buffer_sources, a:srcname)
call s:initialize_source(a:srcname)
endif
let source = s:buffer_sources[a:srcname]
if !filereadable(source.path)
\ || getbufvar(a:srcname, '&modified')
\ || getbufvar(a:srcname, '&buftype') =~ 'nofile\|acwrite'
call s:make_cache_buffer(a:srcname)
return
endif
call neocomplete#print_debug('make_cache_buffer: ' . source.path)
let source.cache_name =
\ neocomplete#cache#async_load_from_file(
\ 'buffer_cache', source.path,
\ source.keyword_pattern, 'B')
let source.cached_time = localtime()
let source.filetype = getbufvar(a:srcname, '&filetype')
let s:async_dictionary_list[source.path] = [{
\ 'filename' : source.path,
\ 'cachename' : source.cache_name,
\ }]
endfunction"}}}
function! s:make_cache_buffer(srcname) abort "{{{
if !s:should_create_cache(a:srcname)
return
endif
call neocomplete#print_debug('make_cache_buffer: ' . a:srcname)
if !s:exists_current_source()
call s:initialize_source(a:srcname)
if a:srcname ==# bufnr('%')
" Force sync cache
call s:make_cache_current_buffer(1, 1000)
return
endif
endif
let source = s:buffer_sources[a:srcname]
let temp = neocomplete#cache#getfilename(
\ 'buffer_temp', getpid() . '_' . a:srcname)
let lines = getbufline(a:srcname, 1, '$')
call writefile(lines, temp)
" Create temporary file
let source.cache_name =
\ neocomplete#cache#async_load_from_file(
\ 'buffer_cache', temp,
\ source.keyword_pattern, 'B')
let source.cached_time = localtime()
let source.filetype = getbufvar(a:srcname, '&filetype')
if source.filetype == ''
let source.filetype = 'nothing'
endif
let s:async_dictionary_list[source.path] = [{
\ 'filename' : temp,
\ 'cachename' : source.cache_name,
\ }]
endfunction"}}}
function! s:check_changed_buffer(bufnr) abort "{{{
let source = s:buffer_sources[a:bufnr]
let ft = getbufvar(a:bufnr, '&filetype')
if ft == ''
let ft = 'nothing'
endif
let filename = fnamemodify(bufname(a:bufnr), ':t')
if filename == ''
let filename = '[No Name]'
endif
return source.name != filename || source.filetype != ft
endfunction"}}}
function! s:check_source() abort "{{{
" Check new buffer.
call map(filter(range(1, bufnr('$')), "
\ (v:val != bufnr('%') || neocomplete#has_vimproc())
\ && (!has_key(s:buffer_sources, v:val) && buflisted(v:val)
\ || (has_key(s:buffer_sources, v:val) &&
\ s:buffer_sources[v:val].cached_time
\ < getftime(s:buffer_sources[v:val].path)))
\ && (!neocomplete#is_locked(v:val) ||
\ g:neocomplete#disable_auto_complete)
\ && s:should_create_cache(v:val)
\ "), 's:make_cache_file(v:val)')
" Remove unlisted buffers.
call filter(s:buffer_sources,
\ "v:key == bufnr('%') || buflisted(str2nr(v:key))")
endfunction"}}}
function! s:exists_current_source() abort "{{{
return has_key(s:buffer_sources, bufnr('%')) &&
\ !s:check_changed_buffer(bufnr('%'))
endfunction"}}}
function! s:make_cache_current_buffer(start, end) abort "{{{
let srcname = bufnr('%')
" Make cache from current buffer.
if !s:should_create_cache(srcname)
return
endif
if !s:exists_current_source()
call s:initialize_source(srcname)
endif
let source = s:buffer_sources[srcname]
let keyword_pattern = source.keyword_pattern
if keyword_pattern == ''
return
endif
let words = []
lua << EOF
do
local words = vim.eval('words')
local dup = {}
local min_length = vim.eval('g:neocomplete#min_keyword_length')
for linenr = vim.eval('a:start'), vim.eval('a:end') do
local match = 0
while 1 do
local match_str = vim.eval('matchstr(getline('..linenr..
'), keyword_pattern, ' .. match .. ')')
if match_str == '' then
break
end
if dup[match_str] == nil
and string.len(match_str) >= min_length then
dup[match_str] = 1
words:add(match_str)
end
-- Next match.
match = vim.eval('matchend(getline(' .. linenr ..
'), keyword_pattern, ' .. match .. ')')
end
end
end
EOF
let source.words = neocomplete#util#uniq(source.words + words)
endfunction"}}}
function! s:check_async_cache(context) abort "{{{
for source in s:get_sources_list(a:context)
if !has_key(s:async_dictionary_list, source.path)
continue
endif
" Load from cache.
let [loaded, file_cache] = neocomplete#cache#get_cache_list(
\ 'buffer_cache', s:async_dictionary_list[source.path])
if loaded
let source.words = file_cache
endif
if empty(s:async_dictionary_list[source.path])
call remove(s:async_dictionary_list, source.path)
endif
endfor
endfunction"}}}
function! s:clean() abort "{{{
" Remove temporary files
for file in glob(printf('%s/%d_*',
\ neocomplete#get_data_directory() . '/buffer_temp',
\ getpid()), 1, 1)
call delete(file)
let cachefile = neocomplete#get_data_directory() . '/buffer_cache/'
\ . substitute(substitute(file, ':', '=-', 'g'), '[/\\]', '=+', 'g')
if filereadable(cachefile)
call delete(cachefile)
endif
endfor
endfunction"}}}
" Command functions. "{{{
function! neocomplete#sources#buffer#make_cache(name) abort "{{{
if !neocomplete#is_enabled()
call neocomplete#initialize()
endif
if a:name == ''
let number = bufnr('%')
else
let number = bufnr(a:name)
if number < 0
let bufnr = bufnr('%')
" No swap warning.
let save_shm = &shortmess
set shortmess+=A
" Open new buffer.
execute 'silent! edit' fnameescape(a:name)
let &shortmess = save_shm
if bufnr('%') != bufnr
setlocal nobuflisted
execute 'buffer' bufnr
endif
endif
let number = bufnr(a:name)
endif
call s:make_cache_file(number)
endfunction"}}}
"}}}
let &cpo = s:save_cpo
unlet s:save_cpo
" vim: foldmethod=marker