401 lines
12 KiB
VimL
401 lines
12 KiB
VimL
|
"=============================================================================
|
||
|
" 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
|