From b518b77e4944fba793bed9ad09772d4fbbe52945 Mon Sep 17 00:00:00 2001 From: Wang Shidong Date: Tue, 17 Aug 2021 22:54:06 +0800 Subject: [PATCH] Add lua projectmanager (#4401) --- .projections.json | 9 +- autoload/SpaceVim/api/file.vim | 15 +- autoload/SpaceVim/plugins/projectmanager.vim | 645 ++++++++++--------- lua/spacevim.lua | 12 - lua/spacevim/layer.lua | 31 +- lua/spacevim/opt.lua | 32 +- lua/spacevim/plugin/a.lua | 15 +- lua/spacevim/plugin/projectmanager.lua | 351 ++++++++++ 8 files changed, 763 insertions(+), 347 deletions(-) create mode 100644 lua/spacevim/plugin/projectmanager.lua diff --git a/.projections.json b/.projections.json index 0ffbf2af6..851f0060d 100644 --- a/.projections.json +++ b/.projections.json @@ -3,7 +3,14 @@ "alternate": "test/api/{}.vader", "doc": "docs/api/{}.md" }, - "autoload/SpaceVim/plugins/a.vim": { "alternate": "test/plugin/a.vader" }, + "autoload/SpaceVim/plugins/a.vim": { + "alternate": "test/plugin/a.vader", + "lua" : "lua/spacevim/plugin/a.lua" + }, + "lua/spacevim/plugin/a.lua": { + "alternate": "test/lua/plugin/a.vader", + "vim" : "autoload/SpaceVim/plugins/a.vim" + }, "test/plugin/a.vader": { "alternate": "autoload/SpaceVim/plugins/a.vim" }, "autoload/SpaceVim/layers/lang/*.vim": { "doc": "docs/layers/lang/{}.md" }, "test/api/*.vader": { "alternate": "autoload/SpaceVim/api/{}.vim" }, diff --git a/autoload/SpaceVim/api/file.vim b/autoload/SpaceVim/api/file.vim index 56afe4284..247263e68 100644 --- a/autoload/SpaceVim/api/file.vim +++ b/autoload/SpaceVim/api/file.vim @@ -233,25 +233,16 @@ let s:file['updateFiles'] = function('s:updatefiles') " 2. if it is a dir, end with / " 3. if a:path end with /, then return path also end with / function! s:unify_path(path, ...) abort - call SpaceVim#logger#info('a:path is :' . a:path) + if empty(a:path) + return '' + endif let mod = a:0 > 0 ? a:1 : ':p' let path = fnamemodify(a:path, mod . ':gs?[\\/]?/?') - call SpaceVim#logger#info('a:path is :' . a:path) - call SpaceVim#logger#info('path is :' . path) if isdirectory(path) && path[-1:] !=# '/' - call SpaceVim#logger#info('case one') - call SpaceVim#logger#info('a:path is :' . a:path) - call SpaceVim#logger#info('path is :' . path) return path . '/' elseif a:path[-1:] ==# '/' && path[-1:] !=# '/' - call SpaceVim#logger#info('case two') - call SpaceVim#logger#info('a:path is :' . a:path) - call SpaceVim#logger#info('path is :' . path) return path . '/' else - call SpaceVim#logger#info('case three') - call SpaceVim#logger#info('a:path is :' . a:path) - call SpaceVim#logger#info('path is :' . path) return path endif endfunction diff --git a/autoload/SpaceVim/plugins/projectmanager.vim b/autoload/SpaceVim/plugins/projectmanager.vim index de4ccec1a..317d27d99 100644 --- a/autoload/SpaceVim/plugins/projectmanager.vim +++ b/autoload/SpaceVim/plugins/projectmanager.vim @@ -6,323 +6,366 @@ " License: GPLv3 "============================================================================= -" 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') -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') - -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]) - 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 - - -" 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') - 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 'lcd ' . 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) - let rootdir = s:FILE.unify_path(getcwd()) - endif - call setbufvar('%', 'rootDir', rootdir) - endif - call s:change_dir(rootdir) - call SpaceVim#plugins#projectmanager#RootchandgeCallback() - return rootdir -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 'cd ' . fnameescape(fnamemodify(a:dir, ':p')) - try - let b:git_dir = fugitive#extract_git_dir(expand('%:p')) - catch - endtry - endif -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' - \ } +if $SPACEVIM_LUA + function! SpaceVim#plugins#projectmanager#complete_project(ArgLead, CmdLine, CursorPos) abort + return luaeval('require("spacevim.plugin.projectmanager").complete(' + \ .'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") \ ) - endif - -endfunction - -if g:spacevim_project_rooter_automatically - 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 + 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 - " get the current path of buffer or working dir + " 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 fd = expand('%:p') - if empty(fd) - let fd = getcwd() - endif + 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') + 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') - 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) + 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 - 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 + call s:LOGGER.info('projects cache file does not exists!') 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 + 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 + if g:spacevim_enable_projects_cache + call s:load_cache() 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 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', []) -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')) + 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]) + endfor + if g:spacevim_enable_projects_cache + call s:cache() endif - endfor - return join(ps, "\n") -endfunction + endfunction -function! SpaceVim#plugins#projectmanager#OpenProject(p) abort - let dir = get(g:, 'spacevim_src_root', '~') . a:p - exe 'CtrlP '. dir -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 '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_rooter_automatically + 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', '~') . a:p + exe 'CtrlP '. dir + 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') + 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 'lcd ' . 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) + let rootdir = s:FILE.unify_path(getcwd()) + endif + call setbufvar('%', 'rootDir', rootdir) + endif + call s:change_dir(rootdir) + call SpaceVim#plugins#projectmanager#RootchandgeCallback() + 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: diff --git a/lua/spacevim.lua b/lua/spacevim.lua index 2990abb4f..fd14d2ae2 100644 --- a/lua/spacevim.lua +++ b/lua/spacevim.lua @@ -1,18 +1,6 @@ local M = {} -local options = require('spacevim.opt') -local layers = require('spacevim.layer') - - - -function M.bootstrap() - - options.init() - layers.init() - -end - function M.eval(l) if vim.api ~= nil then return vim.api.nvim_eval(l) diff --git a/lua/spacevim/layer.lua b/lua/spacevim/layer.lua index 166298d20..677926024 100644 --- a/lua/spacevim/layer.lua +++ b/lua/spacevim/layer.lua @@ -1,9 +1,32 @@ -local layer = {} +--============================================================================= +-- layer.lua --- spacevim layer module +-- Copyright (c) 2016-2019 Wang Shidong & Contributors +-- Author: Wang Shidong < wsdjeg@outlook.com > +-- URL: https://spacevim.org +-- License: GPLv3 +--============================================================================= -function layer.init() - +local M = {} +local sp = require('spacevim') + +-- local mt = { + -- __newindex = function(layer, layer_name, layer_obj) + -- rawset(layer, layer_name, layer_obj) + -- end, + -- __index = function(layer, layer_name) + -- if vim.g ~= nil then + -- return vim.g['spacevim_' .. key] or nil + -- else + -- return sp.eval('get(g:, "spacevim_' .. key .. '", v:null)') + -- end + -- end +-- } +-- setmetatable(M, mt) + +function M.isLoaded(layer) + return sp.call('SpaceVim#layers#isLoaded', layer) == 1 end -return layer +return M diff --git a/lua/spacevim/opt.lua b/lua/spacevim/opt.lua index 93e86a2b6..c6d144cb9 100644 --- a/lua/spacevim/opt.lua +++ b/lua/spacevim/opt.lua @@ -1,14 +1,24 @@ -local options = {} +local M = {} +local sp = require('spacevim') -options._opts = {} +local mt = { + -- this is call when we use opt.xxxx = xxx + __newindex = function(table, key, value) + if vim.g ~= nil then + vim.g['spacevim_' .. key] = value + else + end + end, + -- this is call when we use opt.xxxx + __index = function(table, key) + if vim.g ~= nil then + return vim.g['spacevim_' .. key] or nil + else + return sp.eval('get(g:, "spacevim_' .. key .. '", v:null)') + end + end +} +setmetatable(M, mt) -function options.init() - options._opts.version = '1.2.0' - -- Change the default indentation of SpaceVim, default is 2. - options._opts.default_indent = 2 - options._opts.expand_tab = true -end - - -return options +return M diff --git a/lua/spacevim/plugin/a.lua b/lua/spacevim/plugin/a.lua index 6c17bff74..7742a7309 100644 --- a/lua/spacevim/plugin/a.lua +++ b/lua/spacevim/plugin/a.lua @@ -44,13 +44,16 @@ function M.set_config_name(path, name) end function M.alt(request_parse, ...) - local arg={...} - local type = 'alternate' + local argvs=... + local alt_type = 'alternate' + if argvs ~= nil then + alt_type = argvs[1] or alt_type + end local alt = nil if fn.exists('b:alternate_file_config') ~= 1 then local conf_file_path = M.getConfigPath() local file = sp_file.unify_path(fn.bufname('%'), ':.') - alt = M.get_alt(file, conf_file_path, request_parse, type) + alt = M.get_alt(file, conf_file_path, request_parse, alt_type) end logger.debug('alt is:' .. alt) if alt ~= nil and alt ~= '' then @@ -87,7 +90,7 @@ local function _keys(val) end local function _comp(a, b) if (string.match(a, '*') == '*' - and string.match(b, '*') == '*') + and string.match(b, '*') == '*') then return #a < #b elseif string.match(a, '*') == '*' then @@ -119,8 +122,8 @@ local function parse(alt_config_json) logger.debug(file) project_config[alt_config_json.root][file] = {} if alt_config_json.config[file] ~= nil then - for type, type_v in pairs(alt_config_json.config[file]) do - project_config[alt_config_json.root][file][type] = type_v + for alt_type, type_v in pairs(alt_config_json.config[file]) do + project_config[alt_config_json.root][file][alt_type] = type_v end else for a_type, _ in pairs(alt_config_json.config[key]) do diff --git a/lua/spacevim/plugin/projectmanager.lua b/lua/spacevim/plugin/projectmanager.lua new file mode 100644 index 000000000..1b3c75bf4 --- /dev/null +++ b/lua/spacevim/plugin/projectmanager.lua @@ -0,0 +1,351 @@ +--============================================================================= +-- projectmanager.lua --- The lua version of projectmanager..vim +-- Copyright (c) 2016-2019 Wang Shidong & Contributors +-- Author: Wang Shidong < wsdjeg@outlook.com > +-- URL: https://spacevim.org +-- License: GPLv3 +--============================================================================= + +local logger = require('spacevim.logger').derive('roter') +local sp = require('spacevim') +local sp_file = require('spacevim.api.file') +local sp_json = require('spacevim.api.data.json') +local sp_opt = require('spacevim.opt') +local fn = sp.fn +local layer = require('spacevim.layer') +local project_paths = {} +local project_cache_path = sp_file.unify_path(sp_opt.data_dir, ':p') .. 'SpaceVim/projects.json' +local spacevim_project_rooter_patterns = {} +local project_rooter_patterns = {} +local project_rooter_ignores = {} +local project_callback = {} + +local function update_rooter_patterns() + project_rooter_patterns = {} + project_rooter_ignores = {} + for _,v in pairs(sp_opt.project_rooter_patterns) do + if string.match(v, '^!') == nil then + table.insert(project_rooter_patterns, v) + else + table.insert(project_rooter_ignores, string.sub(v, 2, -1)) + end + end +end +local function is_ignored_dir(dir) + for _,v in pairs(project_rooter_ignores) do + if string.match(dir, v) ~= nil then + logger.debug('this is an ignored dir:' .. dir) + return true + end + end + return false +end +local function cache() + local path = sp_file.unify_path(project_cache_path, ':p') + local file = io.open(path, 'w') + if file then + if file:write(sp_json.json_encode(project_paths)) == nil then + logger.debug('failed to write to file:' .. path) + end + io.close(file) + else + logger.debug('failed to open file:' .. path) + end +end + +local function readfile(path) + local file = io.open(path, "r") + if file then + local content = file:read("*a") + io.close(file) + return content + end + return nil +end + +local function filereadable(fpath) + local f = io.open(fpath, 'r') + if f ~= nil then io.close(f) return true else return false end +end + +local function load_cache() + if filereadable(project_cache_path) then + logger.info('Load projects cache from: ' .. project_cache_path) + local cache_context = readfile(project_cache_path) + if cache_context == nil then + local cache_object = sp_json.json_decode(cache_context) + if type(cache_object) == 'table' then + project_paths = fn.filter(cache_object, '!empty(v:key)') + end + end + else + logger.info('projects cache file does not exists!') + end +end + +local function sort_by_opened_time() + local paths = {} + for k,v in pairs(project_paths) do table.insert(paths, k) end + table.sort(paths, compare_time) + if sp_opt.projects_cache_num > 0 and #paths >= sp_opt.projects_cache_num then + for i = sp_opt.projects_cache_num, #paths, 1 do + project_paths[paths[sp_opt.projects_cache_num]] = nil + table.remove(paths, sp_opt.projects_cache_num) + end + end + return paths +end + +local function compare_time(d1, d2) + local proj1 = project_paths[d1] or {} + local proj1time = proj1['opened_time'] or 0 + local proj2 = project_paths[d2] or {} + local proj2time = proj2['opened_time'] or 0 + return proj2time - proj1time +end +local function change_dir(dir) + if dir == sp_file.unify_path(fn.getcwd()) then + logger.debug('same as current directory, no need to change.') + else + if dir ~= nil then + logger.info('change to root: ' .. dir) + sp.cmd('cd ' .. sp.fn.fnameescape(sp.fn.fnamemodify(dir, ':p'))) + end + end +end + +local function sort_dirs(dirs) + table.sort(dirs, compare) + local dir = dirs[1] + local bufdir = fn.getbufvar('%', 'rootDir', '') + if bufdir == dir then + return '' + else + return dir + end +end + +local function compare(d1, d2) + local _, al = string.gsub(d1, "/", "") + local _, bl = string.gsub(d2, "/", "") + if sp_opt.project_rooter_outermost == 0 then + return bl - al + else + return al - bl + end +end + +local function find_root_directory() + local fd = fn.bufname('%') + if fn == '' then + logger.debug('bufname is empty') + fd = fn.getcwd() + end + logger.debug('start to find root for: ' .. fd) + local dirs = {} + for _,pattern in pairs(project_rooter_patterns) do + logger.debug('searching rooter_patterns:' .. pattern) + local find_path = '' + if string.sub(pattern, -1) == '/' then + if sp_opt.project_rooter_outermost == 1 then + find_path = sp_file.finddir(pattern, fd, -1) + else + find_path = sp_file.finddir(pattern, fd) + end + else + if sp_opt.project_rooter_outermost == 1 then + find_path = sp_file.findfile(pattern, fd, -1) + else + find_path = sp_file.findfile(pattern, fd) + end + end + logger.debug('find_path is:' .. find_path) + local path_type = fn.getftype(find_path) + logger.debug('path_type is:' .. path_type) + if ( path_type == 'dir' or path_type == 'file' ) + and not(is_ignored_dir(find_path)) then + find_path = sp_file.unify_path(find_path, ':p') + if path_type == 'dir' then + find_path = sp_file.unify_path(find_path, ':h:h') + else + find_path = sp_file.unify_path(find_path, ':h') + end + logger.debug('find_path is:' .. find_path) + if find_path ~= sp_file.unify_path(fn.expand('$HOME')) then + logger.info(' (' .. pattern .. '):' .. find_path) + table.insert(dirs, find_path) + else + logger.info('ignore $HOME directory:' .. find_path) + end + end + end + return sort_dirs(dirs) +end +local function cache_project(prj) + project_paths[prj.path] = prj + sp.cmd('let g:unite_source_menu_menus.Projects.command_candidates = []') + for _, key in pairs(sort_by_opened_time()) do + local desc = '[' .. project_paths[key].name .. '] ' .. project_paths[key].path .. ' <' .. fn.strftime('%Y-%m-%d %T', project_paths[key].opened_time) .. '>' + local cmd = "call SpaceVim#plugins#projectmanager#open('" .. project_paths[key].path .. "')" + sp.cmd('call add(g:unite_source_menu_menus.Projects.command_candidates, ["' + .. desc + .. '", "' + .. cmd + .. '"])') + end + if sp_opt.enable_projects_cache then + cache() + end +end + +-- call add(g:spacevim_project_rooter_patterns, '.SpaceVim.d/') + +-- let s:spacevim_project_rooter_patterns = copy(g:spacevim_project_rooter_patterns) +update_rooter_patterns() + +if sp_opt.enable_projects_cache == 1 then + load_cache() +end + +sp.cmd([[ + 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', []) + ]]) + +if sp_opt.project_rooter_automatically == 1 then + sp.cmd("augroup spacevim_project_rooter") + sp.cmd("autocmd!") + sp.cmd("autocmd VimEnter,BufEnter * call SpaceVim#plugins#projectmanager#current_root()") + sp.cmd("autocmd BufWritePost * :call setbufvar('%', 'rootDir', '') | call SpaceVim#plugins#projectmanager#current_root()") + sp.cmd("augroup END") +end +local M = {} + +function M.list() + if layer.isLoaded('unite') then + sp.cmd('Unite menu:Projects') + elseif layer.isLoaded('denite') then + sp.cmd('Denite menu:Projects') + elseif layer.isLoaded('fzf') then + sp.cmd('FzfMenu Projects') + elseif layer.isLoaded('leaderf') then + sp.cmd("call SpaceVim#layers#leaderf#run_menu('Projects')") + else + logger.warn('fuzzy find layer is needed to find project!') + end +end + +function M.open(project) + local path = project_paths[project]['path'] + sp.cmd('tabnew') + sp.cmd('lcd ' .. path) + if sp_opt.filemanager == 'vimfiler' then + sp.cmd('Startify | VimFiler') + elseif sp_opt.filemanager == 'nerdtree' then + sp.cmd('Startify | NERDTree') + elseif sp_opt.filemanager == 'defx' then + sp.cmd('Startify | Defx') + end +end + +function M.current_name() + return sp.eval('b:_spacevim_project_name') +end + + +function M.RootchandgeCallback() + local path = sp_file.unify_path(fn.getcwd(), ':p') + local name = fn.fnamemodify(path, ':t') + local project = { + ['path'] = path, + ['name'] = name, + ['opened_time'] = os.time() + } + if project.path == '' then + return + end + cache_project(project) + -- let g:_spacevim_project_name = project.name + -- let b:_spacevim_project_name = g:_spacevim_project_name + fn.setbufvar('%', '_spacevim_project_name', project.name) + for _, Callback in pairs(project_callback) do + logger.debug('run callback:' .. Callback) + fn.call(Callback, {}) + end +end + +function M.reg_callback(func) + if type(func) == 'string' then + if string.match(func, '^function%(') ~= nil then + table.insert(project_callback, string.sub(func, 11, -3)) + else + table.insert(project_callback, func) + end + else + logger.warn('type of func is:' .. type(func)) + logger.warn('can not register the project callback: ' .. fn.string(func)) + end +end + +function M.kill_project() + local name = sp.eval('b:_spacevim_project_name') + if name ~= '' then + sp_buffer.filter_do( + { + ['expr'] = { + 'buflisted(v:val)', + 'getbufvar(v:val, "_spacevim_project_name") == "' .. name .. '"', + }, + ['do'] = 'bd %d' + } + ) + end +end + +function M.complete_project(arglead, cmdline, cursorpos) + local dir = '~' + local result = fn.split(fn.globpath(dir, '*'), "\n") + local ps = {} + for p in result do + if fn.isdirectory(p) == 1 and fn.isdirectory(p .. sp_file.separator .. '.git') == 1 then + table.insert(ps, fn.fnamemodify(p, ':t')) + end + end + return fn.join(ps, "\n") +end + + +function M.OpenProject(p) + sp.cmd('CtrlP '.. dir) +end + + +function M.current_root() + local bufname = fn.bufname('%') + if bufname == '[denite]' + or bufname == 'denite-filter' + or bufname == '[defx]' then + return + end + if table.concat(sp_opt.project_rooter_patterns, ':') ~= table.concat(spacevim_project_rooter_patterns, ':') then + logger.info('project_rooter_patterns option has been change, clear b:rootDir') + fn.setbufvar('%', 'rootDir', '') + spacevim_project_rooter_patterns = sp_opt.project_rooter_patterns + update_rooter_patterns() + end + local rootdir = fn.getbufvar('%', 'rootDir', '') + -- @bug fn.getbufvar('%', 'rootDir', '') return nil + if rootdir == nil or rootdir == '' then + rootdir = find_root_directory() + if rootdir == nil or rootdir == '' then + rootdir = sp_file.unify_path(fn.getcwd()) + end + fn.setbufvar('%', 'rootDir', rootdir) + end + change_dir(rootdir) + M.RootchandgeCallback() + return rootdir +end + +return M