1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-03-26 12:10:32 +08:00
SpaceVim/autoload/SpaceVim/plugins/projectmanager.vim

401 lines
14 KiB
VimL

"=============================================================================
" projectmanager.vim --- project manager for SpaceVim
" Copyright (c) 2016-2023 Wang Shidong & Contributors
" Author: Shidong Wang < wsdjeg@outlook.com >
" URL: https://spacevim.org
" License: GPLv3
"=============================================================================
if has('nvim-0.5.0')
function! SpaceVim#plugins#projectmanager#complete_project(ArgLead, CmdLine, CursorPos) abort
return luaeval('require("spacevim.plugin.projectmanager").complete_project('
\ .'require("spacevim").eval("a:ArgLead"),'
\ .'require("spacevim").eval("a:CmdLine"),'
\ .'require("spacevim").eval("a:CursorPos"))')
endfunction
function! SpaceVim#plugins#projectmanager#OpenProject(p) abort
lua require("spacevim.plugin.projectmanager").OpenProject(
\ require("spacevim").eval("a:p")
\ )
endfunction
function! SpaceVim#plugins#projectmanager#list() abort
lua require("spacevim.plugin.projectmanager").list()
endfunction
function! SpaceVim#plugins#projectmanager#open(project) abort
lua require("spacevim.plugin.projectmanager").open(
\ require("spacevim").eval("a:project")
\ )
endfunction
function! SpaceVim#plugins#projectmanager#current_name() abort
return luaeval('require("spacevim.plugin.projectmanager").current_name()')
endfunction
function! SpaceVim#plugins#projectmanager#RootchandgeCallback() abort
lua require("spacevim.plugin.projectmanager").RootchandgeCallback()
endfunction
function! SpaceVim#plugins#projectmanager#reg_callback(func) abort
lua require("spacevim.plugin.projectmanager").reg_callback(
\ require("spacevim").eval("string(a:func)")
\ )
endfunction
function! SpaceVim#plugins#projectmanager#current_root() abort
return luaeval('require("spacevim.plugin.projectmanager").current_root()')
endfunction
function! SpaceVim#plugins#projectmanager#kill_project() abort
lua require("spacevim.plugin.projectmanager").kill_project()
endfunction
else
" project item:
" {
" "path" : "path/to/root",
" "name" : "name of the project, by default it is name of root directory",
" "type" : "git maven or svn",
" }
"
let s:BUFFER = SpaceVim#api#import('vim#buffer')
let s:FILE = SpaceVim#api#import('file')
" the name projectmanager is too long
" use rooter instead
let s:LOGGER =SpaceVim#logger#derive('rooter')
call s:LOGGER.start_debug()
let s:TIME = SpaceVim#api#import('time')
let s:JSON = SpaceVim#api#import('data#json')
let s:LIST = SpaceVim#api#import('data#list')
let s:VIM = SpaceVim#api#import('vim')
" use cd or lcd or tcd
"
if exists(':tcd')
let s:cd = 'tcd'
elseif exists(':lcd')
let s:cd = 'lcd'
else
let s:cd = 'cd'
endif
function! s:update_rooter_patterns() abort
let s:project_rooter_patterns = filter(copy(g:spacevim_project_rooter_patterns), 'v:val !~# "^!"')
let s:project_rooter_ignores = map(filter(copy(g:spacevim_project_rooter_patterns), 'v:val =~# "^!"'), 'v:val[1:]')
endfunction
function! s:is_ignored_dir(dir) abort
return len(filter(copy(s:project_rooter_ignores), 'a:dir =~# v:val')) > 0
endfunction
call add(g:spacevim_project_rooter_patterns, '.SpaceVim.d/')
let s:spacevim_project_rooter_patterns = copy(g:spacevim_project_rooter_patterns)
call s:update_rooter_patterns()
let s:project_paths = {}
let s:project_cache_path = s:FILE.unify_path(g:spacevim_data_dir, ':p') . 'SpaceVim/projects.json'
function! s:cache() abort
call writefile([s:JSON.json_encode(s:project_paths)], s:FILE.unify_path(s:project_cache_path, ':p'))
endfunction
function! s:load_cache() abort
if filereadable(s:project_cache_path)
call s:LOGGER.info('Load projects cache from: ' . s:project_cache_path)
let cache_context = join(readfile(s:project_cache_path, ''), '')
if !empty(cache_context)
let cache_object = s:JSON.json_decode(cache_context)
if s:VIM.is_dict(cache_object)
let s:project_paths = filter(cache_object, '!empty(v:key)')
endif
endif
else
call s:LOGGER.info('projects cache file does not exists!')
endif
endfunction
if g:spacevim_enable_projects_cache
call s:load_cache()
endif
let g:unite_source_menu_menus =
\ get(g:,'unite_source_menu_menus',{})
let g:unite_source_menu_menus.Projects = {'description':
\ 'Custom mapped keyboard shortcuts [SPC] p p'}
let g:unite_source_menu_menus.Projects.command_candidates =
\ get(g:unite_source_menu_menus.Projects,'command_candidates', [])
function! s:cache_project(prj) abort
let s:project_paths[a:prj.path] = a:prj
let g:unite_source_menu_menus.Projects.command_candidates = []
for key in s:sort_by_opened_time()
let desc = '[' . s:project_paths[key].name . '] ' . s:project_paths[key].path . ' <' . strftime('%Y-%m-%d %T', s:project_paths[key].opened_time) . '>'
let cmd = "call SpaceVim#plugins#projectmanager#open('" . s:project_paths[key].path . "')"
call add(g:unite_source_menu_menus.Projects.command_candidates, [desc, cmd, s:project_paths[key]])
endfor
if g:spacevim_enable_projects_cache
call s:cache()
endif
endfunction
" sort projects based on opened_time, and remove extra projects based on
" projects_cache_num
function! s:sort_by_opened_time() abort
let paths = keys(s:project_paths)
let paths = sort(paths, function('s:compare_time'))
if g:spacevim_projects_cache_num > 0 && s:LIST.has_index(paths, g:spacevim_projects_cache_num)
for path in paths[g:spacevim_projects_cache_num :]
call remove(s:project_paths, path)
endfor
let paths = paths[:g:spacevim_projects_cache_num - 1]
endif
return paths
endfunction
function! s:compare_time(d1, d2) abort
let proj1 = get(s:project_paths, a:d1, {})
let proj1time = get(proj1, 'opened_time', 0)
let proj2 = get(s:project_paths, a:d2, {})
let proj2time = get(proj2, 'opened_time', 0)
return proj2time - proj1time
endfunction
function! s:change_dir(dir) abort
let bufname = bufname('%')
if empty(bufname)
let bufname = 'No Name'
endif
call s:LOGGER.info('buffer name: ' . bufname)
if a:dir ==# s:FILE.unify_path(getcwd())
call s:LOGGER.info('same as current directory, no need to change.')
else
call s:LOGGER.info('change to root: ' . a:dir)
exe s:cd fnameescape(fnamemodify(a:dir, ':p'))
try
let b:git_dir = fugitive#extract_git_dir(expand('%:p'))
catch
endtry
endif
endfunction
if g:spacevim_project_auto_root
augroup spacevim_project_rooter
autocmd!
autocmd VimEnter,BufEnter * call SpaceVim#plugins#projectmanager#current_root()
autocmd BufWritePost * :call setbufvar('%', 'rootDir', '') | call SpaceVim#plugins#projectmanager#current_root()
augroup END
endif
function! s:find_root_directory() abort
" @question confused about expand and fnamemodify
" ref: https://github.com/vim/vim/issues/6793
" get the current path of buffer or working dir
let fd = expand('%:p')
if empty(fd)
let fd = getcwd()
endif
let dirs = []
call s:LOGGER.info('Start to find root for: ' . s:FILE.unify_path(fd))
for pattern in s:project_rooter_patterns
if stridx(pattern, '/') != -1
if g:spacevim_project_rooter_outermost
let find_path = s:FILE.finddir(pattern, fd, -1)
else
let find_path = s:FILE.finddir(pattern, fd)
endif
else
if g:spacevim_project_rooter_outermost
let find_path = s:FILE.findfile(pattern, fd, -1)
else
let find_path = s:FILE.findfile(pattern, fd)
endif
endif
let path_type = getftype(find_path)
if ( path_type ==# 'dir' || path_type ==# 'file' )
\ && !s:is_ignored_dir(find_path)
let find_path = s:FILE.unify_path(find_path, ':p')
if path_type ==# 'dir'
let dir = s:FILE.unify_path(find_path, ':h:h')
else
let dir = s:FILE.unify_path(find_path, ':h')
endif
if dir !=# s:FILE.unify_path(expand('$HOME'))
call s:LOGGER.info(' (' . pattern . '):' . dir)
call add(dirs, dir)
endif
endif
endfor
return s:sort_dirs(deepcopy(dirs))
endfunction
function! s:sort_dirs(dirs) abort
let dir = get(sort(a:dirs, function('s:compare')), 0, '')
let bufdir = getbufvar('%', 'rootDir', '')
if bufdir ==# dir
return ''
else
return dir
endif
endfunction
function! s:compare(d1, d2) abort
if !g:spacevim_project_rooter_outermost
return len(split(a:d2, '/')) - len(split(a:d1, '/'))
else
return len(split(a:d1, '/')) - len(split(a:d2, '/'))
endif
endfunction
let s:FILE = SpaceVim#api#import('file')
function! SpaceVim#plugins#projectmanager#complete_project(ArgLead, CmdLine, CursorPos) abort
call SpaceVim#commands#debug#completion_debug(a:ArgLead, a:CmdLine, a:CursorPos)
let dir = get(g:,'spacevim_src_root', '~')
"return globpath(dir, '*')
let result = split(globpath(dir, '*'), "\n")
let ps = []
for p in result
if isdirectory(p) && isdirectory(p . s:FILE.separator . '.git')
call add(ps, fnamemodify(p, ':t'))
endif
endfor
return join(ps, "\n")
endfunction
function! SpaceVim#plugins#projectmanager#OpenProject(p) abort
let dir = get(g:, 'spacevim_src_root', '~')
let project_root = s:FILE.unify_path(dir, ':p') . a:p
if isdirectory(project_root)
call execute('tabnew | cd ' . project_root . ' | Startify')
endif
endfunction
" this function will use fuzzy find layer, now only denite and unite are
" supported.
function! SpaceVim#plugins#projectmanager#list() abort
if SpaceVim#layers#isLoaded('unite')
Unite menu:Projects
elseif SpaceVim#layers#isLoaded('denite')
Denite menu:Projects
elseif SpaceVim#layers#isLoaded('fzf')
FzfMenu Projects
elseif SpaceVim#layers#isLoaded('leaderf')
call SpaceVim#layers#leaderf#run_menu('Projects')
elseif SpaceVim#layers#isLoaded('telescope')
Telescope project
else
call SpaceVim#logger#warn('fuzzy find layer is needed to find project!')
endif
endfunction
function! SpaceVim#plugins#projectmanager#open(project) abort
let path = s:project_paths[a:project]['path']
tabnew
exe s:cd path
if g:spacevim_filemanager ==# 'vimfiler'
Startify | VimFiler
elseif g:spacevim_filemanager ==# 'nerdtree'
Startify | NERDTree
elseif g:spacevim_filemanager ==# 'defx'
Startify | Defx
endif
endfunction
function! SpaceVim#plugins#projectmanager#current_name() abort
return get(b:, '_spacevim_project_name', '')
endfunction
" This function is called when projectmanager change the directory.
"
" What should be cached?
" only the directory and project name.
function! SpaceVim#plugins#projectmanager#RootchandgeCallback() abort
let project = {
\ 'path' : getcwd(),
\ 'name' : fnamemodify(getcwd(), ':t'),
\ 'opened_time' : localtime()
\ }
if empty(project.path)
return
endif
call s:cache_project(project)
let g:_spacevim_project_name = project.name
let b:_spacevim_project_name = g:_spacevim_project_name
for Callback in s:project_callback
call call(Callback, [])
endfor
endfunction
let s:project_callback = []
function! SpaceVim#plugins#projectmanager#reg_callback(func) abort
if type(a:func) == 2
call add(s:project_callback, a:func)
else
call SpaceVim#logger#warn('can not register the project callback: ' . string(a:func))
endif
endfunction
function! SpaceVim#plugins#projectmanager#current_root() abort
" @todo skip some plugin buffer
if bufname('%') =~# '\[denite\]'
\ || bufname('%') ==# 'denite-filter'
\ || bufname('%') ==# '\[defx\]'
return
endif
if join(g:spacevim_project_rooter_patterns, ':') !=# join(s:spacevim_project_rooter_patterns, ':')
call s:LOGGER.info('project_rooter_patterns option has been change, clear b:rootDir')
call setbufvar('%', 'rootDir', '')
let s:spacevim_project_rooter_patterns = copy(g:spacevim_project_rooter_patterns)
call s:update_rooter_patterns()
endif
let rootdir = getbufvar('%', 'rootDir', '')
if empty(rootdir)
let rootdir = s:find_root_directory()
if !empty(rootdir)
call setbufvar('%', 'rootDir', rootdir)
endif
endif
if !empty(rootdir)
call s:change_dir(rootdir)
call SpaceVim#plugins#projectmanager#RootchandgeCallback()
else
if g:spacevim_project_non_root ==# 'current'
let dir = fnamemodify(expand('%'), ':p:h')
if isdirectory(dir)
call s:change_dir(dir)
endif
elseif g:spacevim_project_non_root ==# 'home' && filereadable(expand('%'))
let dir = fnamemodify(expand('~'), ':p')
if isdirectory(dir)
call s:change_dir(dir)
endif
endif
endif
return rootdir
endfunction
function! SpaceVim#plugins#projectmanager#kill_project() abort
let name = get(b:, '_spacevim_project_name', '')
if name !=# ''
call s:BUFFER.filter_do(
\ {
\ 'expr' : [
\ 'buflisted(v:val)',
\ 'getbufvar(v:val, "_spacevim_project_name") == "' . name . '"',
\ ],
\ 'do' : 'bd %d'
\ }
\ )
endif
endfunction
endif
" vim:set et nowrap sw=2 cc=80: