# HG changeset patch # User Stephen Kent # Date 1469240705 25200 # Node ID 286e5b3095d09b29465de472decbf1f1a8fc272b # Parent 4c9e2de7d46ac0f0c15d664f7d5aadbffe2ea7f5 Allow restricting tag generation to files listed by custom commands This adds a new setting, g:gutentags_file_list_command, which specifies command(s) to use to list files for which tags should be generated, instead of recursively examining all files within the project root. This is useful in projects using source control to restrict tag generation to only files tracked in the repository. This setting is conceptually similar to CtrlP's ctrlp_user_command option. This implements the feature requested in https://github.com/ludovicchabant/vim-gutentags/issues/90 diff -r 4c9e2de7d46a -r 286e5b3095d0 autoload/gutentags.vim --- a/autoload/gutentags.vim Fri Jul 22 19:25:01 2016 -0700 +++ b/autoload/gutentags.vim Fri Jul 22 19:25:05 2016 -0700 @@ -78,6 +78,29 @@ let s:known_projects[a:path] = l:result endfunction +function! gutentags#validate_cmd(cmd) abort + if !empty(a:cmd) && executable(split(a:cmd)[0]) + return a:cmd + endif + return "" +endfunction + +function! gutentags#get_project_file_list_cmd(path) abort + if type(g:gutentags_file_list_command) == type("") + return gutentags#validate_cmd(g:gutentags_file_list_command) + elseif type(g:gutentags_file_list_command) == type({}) + let l:markers = get(g:gutentags_file_list_command, 'markers', []) + if type(l:markers) == type({}) + for [marker, file_list_cmd] in items(l:markers) + if getftype(a:path . '/' . marker) != "" + return gutentags#validate_cmd(file_list_cmd) + endif + endfor + endif + endif + return "" +endfunction + " Finds the first directory with a project marker by walking up from the given " file path. function! gutentags#get_project_root(path) abort diff -r 4c9e2de7d46a -r 286e5b3095d0 autoload/gutentags/cscope.vim --- a/autoload/gutentags/cscope.vim Fri Jul 22 19:25:01 2016 -0700 +++ b/autoload/gutentags/cscope.vim Fri Jul 22 19:25:05 2016 -0700 @@ -55,6 +55,11 @@ let l:cmd .= ' -e ' . g:gutentags_cscope_executable let l:cmd .= ' -p ' . a:proj_dir let l:cmd .= ' -f ' . a:tags_file + let l:file_list_cmd = + \ gutentags#get_project_file_list_cmd(l:proj_dir) + if !empty(l:file_list_cmd) + let l:cmd .= ' -L "' . l:file_list_cmd . '"' + endif let l:cmd .= ' ' let l:cmd .= gutentags#get_execute_cmd_suffix() diff -r 4c9e2de7d46a -r 286e5b3095d0 autoload/gutentags/ctags.vim --- a/autoload/gutentags/ctags.vim Fri Jul 22 19:25:01 2016 -0700 +++ b/autoload/gutentags/ctags.vim Fri Jul 22 19:25:05 2016 -0700 @@ -79,10 +79,18 @@ let l:cur_file_path = fnamemodify(l:cur_file_path, ':.') endif let l:cmd .= ' -s "' . l:cur_file_path . '"' + else + let l:file_list_cmd = gutentags#get_project_file_list_cmd(l:actual_proj_dir) + if !empty(l:file_list_cmd) + let l:cmd .= ' -L ' . '"' . l:file_list_cmd. '"' + endif endif - " Pass the Gutentags recursive options file before the project - " options file, so that users can override --recursive. - let l:cmd .= ' -o "' . gutentags#get_res_file('ctags_recursive.options') . '"' + if empty(get(l:, 'file_list_cmd', '')) + " Pass the Gutentags recursive options file before the project + " options file, so that users can override --recursive. + " Omit --recursive if this project uses a file list command. + let l:cmd .= ' -o "' . gutentags#get_res_file('ctags_recursive.options') . '"' + endif let l:proj_options_file = a:proj_dir . '/' . \g:gutentags_ctags_options_file if filereadable(l:proj_options_file) diff -r 4c9e2de7d46a -r 286e5b3095d0 doc/gutentags.txt --- a/doc/gutentags.txt Fri Jul 22 19:25:01 2016 -0700 +++ b/doc/gutentags.txt Fri Jul 22 19:25:05 2016 -0700 @@ -417,6 +417,40 @@ See |gutentags_ctags_executable_{filetype}| for more information. + *gutentags_file_list_command* +g:gutentags_file_list_command + Specifies command(s) to use to list files for which + tags should be generated, instead of recursively + examining all files within the project root. When + invoked, file list commands will execute in the + project root directory. + + This setting is useful in projects using source + control to restrict tag generation to only files + tracked in the repository. + + This variable may be set in one of two ways. If + set as a |String|, the specified command will be used to + list files for all projects. For example: > + + let g:gutentags_file_list_command = 'find . -type f' +< + If set as a |Dictionary|, this variable should be set + as a mapping of project root markers to the desired + file list command for that root marker. (See + |gutentags_project_root| for how Gutentags uses root + markerts to locate the project.) For example: > + + let g:gutentags_file_list_command = { + \ 'markers': { + \ '.git': 'git ls-files', + \ '.hg': 'hg locate', + \ }, + \ } +< + Note: If a custom ctags executable is specified, it + must support the '-L' command line option in order to + read the list of files to be examined. ============================================================================= 5. Project Settings *gutentags-project-settings* diff -r 4c9e2de7d46a -r 286e5b3095d0 plat/unix/update_scopedb.sh --- a/plat/unix/update_scopedb.sh Fri Jul 22 19:25:01 2016 -0700 +++ b/plat/unix/update_scopedb.sh Fri Jul 22 19:25:05 2016 -0700 @@ -4,8 +4,10 @@ PROG_NAME=$0 CSCOPE_EXE=cscope +CSCOPE_ARGS= DB_FILE=cscope.out PROJECT_ROOT= +FILE_LIST_CMD= ShowUsage() { echo "Usage:" @@ -14,11 +16,12 @@ echo " -e [exe=cscope]: The cscope executable to run" echo " -f [file=cscope.out]: The path to the ctags file to update" echo " -p [dir=]: The path to the project root" + echo " -L [cmd=]: The file list command to run" echo "" } -while getopts "h?e:f:p:" opt; do +while getopts "h?e:f:p:L:" opt; do case $opt in h|\?) ShowUsage @@ -33,6 +36,9 @@ p) PROJECT_ROOT=$OPTARG ;; + L) + FILE_LIST_CMD=$OPTARG + ;; esac done @@ -47,16 +53,30 @@ echo $$ > "$DB_FILE.lock" # Remove lock and temp file if script is stopped unexpectedly. -trap 'rm -f "$DB_FILE.lock" "$DB_FILE.temp"' INT QUIT TERM EXIT +trap 'rm -f "$DB_FILE.lock" "$DB_FILE.files" "$DB_FILE.temp"' INT QUIT TERM EXIT PREVIOUS_DIR=$(pwd) if [ -d "$PROJECT_ROOT" ]; then cd "$PROJECT_ROOT" fi +if [ -n "${FILE_LIST_CMD}" ]; then + if [ "${PROJECT_ROOT}" = "." ]; then + $FILE_LIST_CMD > "${DB_FILE}.files" + else + # If using a tags cache directory, use absolute paths + $FILE_LIST_CMD | while read -r l; do + echo "${PROJECT_ROOT%/}/${l}" + done > "${DB_FILE}.files" + fi + CSCOPE_ARGS="${CSCOPE_ARGS} -i ${DB_FILE}.files" +else + CSCOPE_ARGS="${CSCOPE_ARGS} -R" +fi + echo "Running cscope" -echo "$CSCOPE_EXE -R -b -k -f \"$DB_FILE.temp\"" -"$CSCOPE_EXE" -R -v -b -k -f "$DB_FILE.temp" +echo "$CSCOPE_EXE $CSCOPE_ARGS -b -k -f \"$DB_FILE.temp\"" +"$CSCOPE_EXE" $CSCOPE_ARGS -v -b -k -f "$DB_FILE.temp" if [ -d "$PROJECT_ROOT" ]; then cd "$PREVIOUS_DIR" diff -r 4c9e2de7d46a -r 286e5b3095d0 plat/unix/update_tags.sh --- a/plat/unix/update_tags.sh Fri Jul 22 19:25:01 2016 -0700 +++ b/plat/unix/update_tags.sh Fri Jul 22 19:25:05 2016 -0700 @@ -7,6 +7,7 @@ CTAGS_ARGS= TAGS_FILE=tags PROJECT_ROOT= +FILE_LIST_CMD= UPDATED_SOURCE= PAUSE_BEFORE_EXIT=0 @@ -18,6 +19,7 @@ echo " -e [exe=ctags]: The ctags executable to run" echo " -t [file=tags]: The path to the ctags file to update" echo " -p [dir=]: The path to the project root" + echo " -L [cmd=]: The file list command to run" echo " -s [file=]: The path to the source file that needs updating" echo " -x [pattern=]: A pattern of files to exclude" echo " -o [options=]: An options file to read additional options from" @@ -26,7 +28,7 @@ } -while getopts "h?e:x:t:p:s:o:c" opt; do +while getopts "h?e:x:t:p:L:s:o:c" opt; do case $opt in h|\?) ShowUsage @@ -44,6 +46,9 @@ p) PROJECT_ROOT=$OPTARG ;; + L) + FILE_LIST_CMD=$OPTARG + ;; s) UPDATED_SOURCE=$OPTARG ;; @@ -67,7 +72,7 @@ echo $$ > "$TAGS_FILE.lock" # Remove lock and temp file if script is stopped unexpectedly. -trap 'errorcode=$?; rm -f "$TAGS_FILE.lock" "$TAGS_FILE.temp"; exit $errorcode' INT QUIT TERM EXIT +trap 'errorcode=$?; rm -f "$TAGS_FILE.lock" "$TAGS_FILE.files" "$TAGS_FILE.temp"; exit $errorcode' INT QUIT TERM EXIT INDEX_WHOLE_PROJECT=1 if [ -f "$TAGS_FILE" ]; then @@ -82,6 +87,17 @@ fi if [ $INDEX_WHOLE_PROJECT -eq 1 ]; then + if [ -n "${FILE_LIST_CMD}" ]; then + if [ "${PROJECT_ROOT}" = "." ]; then + $FILE_LIST_CMD > "${TAGS_FILE}.files" + else + # If using a tags cache directory, use absolute paths + $FILE_LIST_CMD | while read -r l; do + echo "${PROJECT_ROOT%/}/${l}" + done > "${TAGS_FILE}.files" + fi + CTAGS_ARGS="${CTAGS_ARGS} -L ${TAGS_FILE}.files" + fi echo "Running ctags on whole project" echo "$CTAGS_EXE -f \"$TAGS_FILE.temp\" $CTAGS_ARGS \"$PROJECT_ROOT\"" $CTAGS_EXE -f "$TAGS_FILE.temp" $CTAGS_ARGS "$PROJECT_ROOT" diff -r 4c9e2de7d46a -r 286e5b3095d0 plat/win32/update_scopedb.cmd --- a/plat/win32/update_scopedb.cmd Fri Jul 22 19:25:01 2016 -0700 +++ b/plat/win32/update_scopedb.cmd Fri Jul 22 19:25:05 2016 -0700 @@ -6,7 +6,9 @@ rem ========================================== set CSCOPE_EXE=cscope +set CSCOPE_ARGS= set DB_FILE=cscope.out +set FILE_LIST_CMD= :ParseArgs if [%1]==[] goto :DoneParseArgs @@ -25,6 +27,11 @@ shift goto :LoopParseArgs ) +if [%1]==[-L] ( + set FILE_LIST_CMD=%~2 + shift + goto :LoopParseArgs +) echo Invalid Argument: %1 goto :Usage @@ -43,13 +50,25 @@ echo locked > "%DB_FILE%.lock" echo Running cscope -"%CSCOPE_EXE%" -R -b -k -f "%DB_FILE%" +if NOT ["%FILE_LIST_CMD%"]==[""] ( + if ["%PROJECT_ROOT%"]==["."] ( + call %FILE_LIST_CMD% > %DB_FILE%.files + ) else ( + rem Potentially useful: + rem http://stackoverflow.com/questions/9749071/cmd-iterate-stdin-piped-from-another-command + %FILE_LIST_CMD% | for /F "usebackq delims=" %%F in (`findstr "."`) do @echo %PROJECT_ROOT%\%%F > %DB_FILE%.files + ) + set CSCOPE_ARGS=%CSCOPE_ARGS% -i %TAGS_FILE%.files +) ELSE ( + set CSCOPE_ARGS=%CSCOPE_ARGS% -R +) +"%CSCOPE_EXE%" %CSCOPE_ARGS% -b -k -f "%DB_FILE%" if ERRORLEVEL 1 ( echo ERROR: Cscope executable returned non-zero code. ) echo Unlocking db file -del /F "%DB_FILE%.lock" +del /F "%DB_FILE%.files" "%DB_FILE%.lock" if ERRORLEVEL 1 ( echo ERROR: Unable to remove file lock. ) @@ -70,5 +89,6 @@ echo -e [exe=cscope]: The cscope executable to run echo -f [file=scope.out]: The path to the database file to create echo -p [dir=]: The path to the project root +echo -L [cmd=]: The file list command to run echo. diff -r 4c9e2de7d46a -r 286e5b3095d0 plat/win32/update_tags.cmd --- a/plat/win32/update_tags.cmd Fri Jul 22 19:25:01 2016 -0700 +++ b/plat/win32/update_tags.cmd Fri Jul 22 19:25:05 2016 -0700 @@ -9,6 +9,7 @@ set CTAGS_ARGS= set TAGS_FILE=tags set PROJECT_ROOT= +set FILE_LIST_CMD= set UPDATED_SOURCE= set PAUSE_BEFORE_EXIT=0 set LOG_FILE= @@ -35,6 +36,11 @@ shift goto :LoopParseArgs ) +if [%1]==[-L] ( + set FILE_LIST_CMD=%~2 + shift + goto :LoopParseArgs +) if [%1]==[-s] ( set UPDATED_SOURCE=%~2 shift @@ -85,6 +91,16 @@ ) if ["%INDEX_WHOLE_PROJECT%"]==["1"] ( set CTAGS_ARGS=%CTAGS_ARGS% "%PROJECT_ROOT%" + if NOT ["%FILE_LIST_CMD%"]==[""] ( + if ["%PROJECT_ROOT%"]==["."] ( + call %FILE_LIST_CMD% > %TAGS_FILE%.files + ) else ( + rem Potentially useful: + rem http://stackoverflow.com/questions/9749071/cmd-iterate-stdin-piped-from-another-command + %FILE_LIST_CMD% | for /F "usebackq delims=" %%F in (`findstr "."`) do @echo %PROJECT_ROOT%\%%F > %TAGS_FILE%.files + ) + set CTAGS_ARGS=%CTAGS_ARGS% -L %TAGS_FILE%.files + ) ) echo Running ctags >> %LOG_FILE% @@ -105,7 +121,7 @@ :Unlock echo Unlocking tags file... >> %LOG_FILE% -del /F "%TAGS_FILE%.lock" +del /F "%TAGS_FILE.files" "%TAGS_FILE%.lock" if ERRORLEVEL 1 ( echo ERROR: Unable to remove file lock. >> %LOG_FILE% ) @@ -129,6 +145,7 @@ echo -e [exe=ctags]: The ctags executable to run echo -t [file=tags]: The path to the ctags file to update echo -p [dir=]: The path to the project root +echo -L [cmd=]: The file list command to run echo -s [file=]: The path to the source file that needs updating echo -l [log=]: The log file to output to echo -o [options=]: An options file to read additional options from diff -r 4c9e2de7d46a -r 286e5b3095d0 plugin/gutentags.vim --- a/plugin/gutentags.vim Fri Jul 22 19:25:01 2016 -0700 +++ b/plugin/gutentags.vim Fri Jul 22 19:25:05 2016 -0700 @@ -44,6 +44,7 @@ let g:gutentags_generate_on_new = get(g:, 'gutentags_generate_on_new', 1) let g:gutentags_generate_on_missing = get(g:, 'gutentags_generate_on_missing', 1) let g:gutentags_generate_on_write = get(g:, 'gutentags_generate_on_write', 1) +let g:gutentags_file_list_command = get(g:, 'gutentags_file_list_command', '') if !exists('g:gutentags_cache_dir') let g:gutentags_cache_dir = ''