1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 05:20:04 +08:00
SpaceVim/bundle/neomru.vim/autoload/neomru.vim
2022-02-08 23:29:34 +08:00

456 lines
12 KiB
VimL

"=============================================================================
" FILE: neomru.vim
" AUTHOR: Zhao Cai <caizhaoff@gmail.com>
" Shougo Matsushita <Shougo.Matsu at gmail.com>
" License: MIT license
"=============================================================================
function! neomru#set_default(var, val, ...) abort
if !exists(a:var) || type({a:var}) != type(a:val)
let alternate_var = get(a:000, 0, '')
let {a:var} = exists(alternate_var) ?
\ {alternate_var} : a:val
endif
endfunction
function! s:substitute_path_separator(path) abort
return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path
endfunction
function! s:expand(path) abort
return s:substitute_path_separator(expand(a:path))
endfunction
function! s:fnamemodify(fname, mods) abort
return s:substitute_path_separator(fnamemodify(a:fname, a:mods))
endfunction
" Variables
" The version of MRU file format.
let s:VERSION = '0.3.0'
let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95')
let s:base = s:expand($XDG_CACHE_HOME != '' ?
\ $XDG_CACHE_HOME . '/neomru' : '~/.cache/neomru')
call neomru#set_default(
\ 'g:neomru#do_validate', 1,
\ 'g:unite_source_mru_do_validate')
call neomru#set_default(
\ 'g:neomru#update_interval', 0,
\ 'g:unite_source_mru_update_interval')
call neomru#set_default(
\ 'g:neomru#time_format', '',
\ 'g:unite_source_file_mru_time_format')
call neomru#set_default(
\ 'g:neomru#filename_format', '',
\ 'g:unite_source_file_mru_filename_format')
call neomru#set_default(
\ 'g:neomru#file_mru_path',
\ s:substitute_path_separator(s:base.'/file'))
call neomru#set_default(
\ 'g:neomru#file_mru_limit',
\ 1000, 'g:unite_source_file_mru_limit')
call neomru#set_default(
\ 'g:neomru#file_mru_ignore_pattern',
\'\~$\|\.\%(o\|exe\|dll\|bak\|zwc\|pyc\|sw[po]\)$'.
\'\|\%(^\|/\)\.\%(hg\|git\|bzr\|svn\)\%($\|/\)'.
\'\|^\%(\\\\\|/mnt/\|/media/\|/temp/\|/tmp/\|\%(/private\)\=/var/folders/\)'.
\'\|\%(^\%(fugitive\)://\)'.
\'\|\%(^\%(term\)://\)'
\, 'g:unite_source_file_mru_ignore_pattern')
call neomru#set_default(
\ 'g:neomru#directory_mru_path',
\ s:substitute_path_separator(s:base.'/directory'))
call neomru#set_default(
\ 'g:neomru#directory_mru_limit',
\ 1000, 'g:unite_source_directory_mru_limit')
call neomru#set_default(
\ 'g:neomru#directory_mru_ignore_pattern',
\'\%(^\|/\)\.\%(hg\|git\|bzr\|svn\)\%($\|/\)'.
\'\|^\%(\\\\\|/mnt/\|/media/\|/temp/\|/tmp/\|\%(/private\)\=/var/folders/\)',
\ 'g:unite_source_directory_mru_ignore_pattern')
call neomru#set_default('g:neomru#follow_links', 0)
" MRUs
let s:MRUs = {}
" Template MRU: 2
"---------------------%>---------------------
" @candidates:
" ------------
" [full_path, ... ]
"
" @mtime
" ------
" the last modified time of the mru file.
" - set once when loading the mru_file
" - update when #save()
"
" @is_loaded
" ----------
" 0: empty
" 1: loaded
" -------------------%<---------------------
let s:mru = {
\ 'candidates' : [],
\ 'type' : '',
\ 'mtime' : 0,
\ 'update_interval' : g:neomru#update_interval,
\ 'mru_file' : '',
\ 'limit' : {},
\ 'do_validate' : g:neomru#do_validate,
\ 'is_loaded' : 0,
\ 'version' : s:VERSION,
\ }
function! s:mru.is_a(type) abort
return self.type == a:type
endfunction
function! s:mru.validate() abort
throw 'unite(mru) umimplemented method: validate()!'
endfunction
function! s:mru.gather_candidates(args, context) abort
if !self.is_loaded
call self.load()
endif
if a:context.is_redraw && g:neomru#do_validate
call self.reload()
endif
return copy(self.candidates)
endfunction
function! s:mru.delete(candidates) abort
for candidate in a:candidates
call filter(self.candidates,
\ 'v:val !=# candidate.action__path')
endfor
call self.save()
endfunction
function! s:mru.has_external_update() abort
return self.mtime < getftime(self.mru_file)
endfunction
function! s:mru.save(...) abort
if s:is_sudo()
return
endif
let opts = a:0 >= 1 && type(a:1) == type({}) ? a:1 : {}
if self.has_external_update() && filereadable(self.mru_file)
" only need to get the list which contains the latest MRUs
let lines = readfile(self.mru_file)
if !empty(lines)
let [ver; items] = lines
if self.version_check(ver)
call extend(self.candidates, items)
endif
endif
endif
let self.candidates = s:uniq(self.candidates)
if len(self.candidates) > self.limit
let self.candidates = self.candidates[: self.limit - 1]
endif
if get(opts, 'event') ==# 'VimLeavePre'
call self.validate()
endif
call s:writefile(self.mru_file,
\ [self.version] + self.candidates)
let self.mtime = getftime(self.mru_file)
endfunction
function! s:mru.load(...) abort
let is_force = get(a:000, 0, 0)
" everything is loaded, done!
if !is_force && self.is_loaded && !self.has_external_update()
return
endif
let mru_file = self.mru_file
if !filereadable(mru_file)
return
endif
let file = readfile(mru_file)
if empty(file)
return
endif
let [ver; items] = file
if !self.version_check(ver)
return
endif
if self.type ==# 'file'
endif
" Assume properly saved and sorted. unique sort is not necessary here
call extend(self.candidates, items)
if self.is_loaded
let self.candidates = s:uniq(self.candidates)
endif
let self.mtime = getftime(mru_file)
let self.is_loaded = 1
endfunction
function! s:mru.reload() abort
call self.load(1)
call filter(self.candidates,
\ ((self.type == 'file') ?
\ "s:is_file_exist(v:val)" : "s:is_directory_exist(v:val)"))
endfunction
function! s:mru.append(path) abort
call self.load()
let index = index(self.candidates, a:path)
if index == 0
return
endif
if index > 0
call remove(self.candidates, index)
endif
call insert(self.candidates, a:path)
if len(self.candidates) > self.limit
let self.candidates = self.candidates[: self.limit - 1]
endif
if localtime() > getftime(self.mru_file) + self.update_interval
call self.save()
endif
endfunction
function! s:mru.version_check(ver) abort
let check = 1
try
let check = str2float(string(a:ver)) < str2float(string(self.version))
catch
" Ignore errors
endtry
if check
call s:print_error('Sorry, the version of MRU file is too old.')
return 0
else
return 1
endif
endfunction
function! s:resolve(fpath) abort
return g:neomru#follow_links ? resolve(a:fpath) : a:fpath
endfunction
" File MRU: 2
"
let s:file_mru = extend(deepcopy(s:mru), {
\ 'type' : 'file',
\ 'mru_file' : s:expand(g:neomru#file_mru_path),
\ 'limit' : g:neomru#file_mru_limit,
\ }
\)
function! s:file_mru.validate() abort
if self.do_validate
call filter(self.candidates, 's:is_file_exist(v:val)')
endif
endfunction
" Directory MRU: 2
let s:directory_mru = extend(deepcopy(s:mru), {
\ 'type' : 'directory',
\ 'mru_file' : s:expand(g:neomru#directory_mru_path),
\ 'limit' : g:neomru#directory_mru_limit,
\ }
\)
function! s:directory_mru.validate() abort
if self.do_validate
call filter(self.candidates, 'getftype(v:val) ==# "dir"')
endif
endfunction
" Public Interface: 2
let s:MRUs.file = s:file_mru
let s:MRUs.directory = s:directory_mru
function! neomru#init() abort
endfunction
function! neomru#_import_file(path) abort
let path = a:path
if path == ''
let path = s:expand('~/.unite/file_mru')
endif
let candidates = s:file_mru.candidates
let candidates += s:import(path)
" Load from v:oldfiles
let candidates += map(v:oldfiles, "s:substitute_path_separator(v:val)")
let s:file_mru.candidates = s:uniq(candidates)
call s:file_mru.save()
endfunction
function! neomru#_import_directory(path) abort
let path = a:path
if path == ''
let path = s:expand('~/.unite/directory_mru')
endif
let s:directory_mru.candidates = s:uniq(
\ s:directory_mru.candidates + s:import(path))
call s:directory_mru.save()
endfunction
function! neomru#_get_mrus() abort
return s:MRUs
endfunction
function! neomru#_append() abort
if &l:buftype =~ 'help\|nofile' || &l:previewwindow
return
endif
call neomru#append(s:expand('%:p'))
endfunction
function! neomru#_gather_file_candidates() abort
return neomru#_get_mrus().file.gather_candidates([], {'is_redraw': 0})
endfunction
function! neomru#_gather_directory_candidates() abort
return neomru#_get_mrus().directory.gather_candidates([], {'is_redraw': 0})
endfunction
function! neomru#append(filename) abort
let path = s:fnamemodify(a:filename, ':p')
if path !~ '\a\+:'
let path = s:substitute_path_separator(
\ simplify(s:resolve(path)))
endif
" Append the current buffer to the mru list.
if s:is_file_exist(path)
call s:file_mru.append(path)
endif
let filetype = getbufvar(bufnr(a:filename), '&filetype')
if filetype ==# 'vimfiler' &&
\ type(getbufvar(bufnr(a:filename), 'vimfiler')) == type({})
let path = getbufvar(bufnr(a:filename), 'vimfiler').current_dir
elseif filetype ==# 'vimshell' &&
\ type(getbufvar(bufnr(a:filename), 'vimshell')) == type({})
let path = getbufvar(bufnr(a:filename), 'vimshell').current_dir
else
let path = s:fnamemodify(path, ':p:h')
endif
let path = s:substitute_path_separator(simplify(s:resolve(path)))
" Chomp last /.
let path = substitute(path, '/$', '', '')
" Append the current buffer to the mru list.
if s:is_directory_exist(path)
call s:directory_mru.append(path)
endif
endfunction
function! neomru#_reload() abort
for m in values(s:MRUs)
call m.reload()
endfor
endfunction
function! neomru#_save(...) abort
let opts = a:0 >= 1 && type(a:1) == type({}) ? a:1 : {}
for m in values(s:MRUs)
call m.save(opts)
endfor
endfunction
function! neomru#_abbr(path, time) abort
let abbr = (g:neomru#time_format == '') ? '' :
\ strftime('(' . g:neomru#time_format . ') ', a:time)
let abbr .= a:path
return abbr
endfunction
" Misc
function! s:writefile(path, list) abort
let path = fnamemodify(a:path, ':p')
if !isdirectory(fnamemodify(path, ':h'))
call mkdir(fnamemodify(path, ':h'), 'p')
endif
call writefile(a:list, path)
endfunction
function! s:uniq(list, ...) abort
return s:uniq_by(a:list, 'tolower(v:val)')
endfunction
function! s:uniq_by(list, f) abort
let list = map(copy(a:list), printf('[v:val, %s]', a:f))
let i = 0
let seen = {}
while i < len(list)
let key = string(list[i][1])
if has_key(seen, key)
call remove(list, i)
else
let seen[key] = 1
let i += 1
endif
endwhile
return map(list, 'v:val[0]')
endfunction
function! s:is_file_exist(path) abort
let ignore = !empty(g:neomru#file_mru_ignore_pattern)
\ && a:path =~ g:neomru#file_mru_ignore_pattern
return !ignore && (getftype(a:path) ==# 'file' ||
\ getftype(a:path) ==# 'link' ||
\ a:path =~ '^\h\w\+:')
endfunction
function! s:is_directory_exist(path) abort
let ignore = !empty(g:neomru#directory_mru_ignore_pattern)
\ && a:path =~ g:neomru#directory_mru_ignore_pattern
return !ignore && (isdirectory(a:path) || a:path =~ '^\h\w\+:')
endfunction
function! s:import(path) abort
if !filereadable(a:path)
call s:print_error(printf('path "%s" is not found.', a:path))
return []
endif
let [ver; items] = readfile(a:path)
if ver != '0.2.0'
call s:print_error('Sorry, the version of MRU file is too old.')
return []
endif
let candidates = map(items, "split(v:val, '\t')[0]")
" Load long file.
if filereadable(a:path . '_long')
let [ver; items] = readfile(a:path . '_long')
let candidates += map(items, "split(v:val, '\t')[0]")
endif
return map(candidates, "substitute(s:substitute_path_separator(
\ v:val), '/$', '', '')")
endfunction
function! s:print_error(msg) abort
echohl Error | echomsg '[neomru] ' . a:msg | echohl None
endfunction
function! s:is_sudo() abort
return $SUDO_USER != '' && $USER !=# $SUDO_USER
\ && $HOME !=# expand('~'.$USER)
\ && $HOME ==# expand('~'.$SUDO_USER)
endfunction