changeset 3:60adce96ac2d

Lots of changes: * Add Unix script for tag file generation. * Add status-line indicator. * More options to customize Autotags behaviour. * Use 'wildignore' to exclude things from ctags. * Generate tag file in a project missing it.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 20 Jul 2014 14:25:09 -0700
parents d073ca33c5b8
children 512eaa56c7db
files plat/unix/update_tags.sh plat/win32/update_tags.cmd plugin/autotags.vim
diffstat 3 files changed, 222 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/plat/unix/update_tags.sh	Sun Jul 20 14:21:43 2014 -0700
+++ b/plat/unix/update_tags.sh	Sun Jul 20 14:25:09 2014 -0700
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+set -e
+
+PROG_NAME=$0
+CTAGS_EXE=ctags
+CTAGS_ARGS=
+TAGS_FILE=tags
+UPDATED_SOURCE=
+PAUSE_BEFORE_EXIT=0
+
+
+ShowUsage() {
+    echo "Usage:"
+    echo "    $PROG_NAME <options>"
+    echo ""
+    echo "    -e [exe=ctags]: The ctags executable to run"
+    echo "    -t [file=tags]: The path to the ctags file to update"
+    echo "    -s [file=]:     The path to the source file that needs updating"
+    echo "    -x [pattern=]:  A pattern of files to exclude"
+    echo ""
+}
+
+
+while getopts "h?e:x:t:s:l:" opt; do
+    case $opt in 
+        h|\?)
+            ShowUsage
+            exit 0
+            ;;
+        e)
+            CTAGS_EXE=$OPTARG
+            ;;
+        x)
+            CTAGS_ARGS="$CTAGS_ARGS --exclude=$OPTARG"
+            ;;
+        t)
+            TAGS_FILE=$OPTARG
+            ;;
+        s)
+            UPDATED_SOURCE=$OPTARG
+            ;;
+        p)
+            PAUSE_BEFORE_EXIT=1
+            ;;
+    esac
+done
+
+shift $((OPTIND - 1))
+
+if [[ "$1" -ne "" ]]; then
+    echo "Invalid Argument: $1"
+    exit 1
+fi
+
+echo "Locking tags file..."
+echo "locked" > "$TAGS_FILE.lock"
+
+if [[ -f "$TAGS_FILE" ]]; then
+    if [[ "$UPDATED_SOURCE" != "" ]]; then
+        echo "Removing references to: $UPDATED_SOURCE"
+        echo "grep -v $UPDATED_SOURCE \"$TAGS_FILE\" > \"$TAGS_FILE.filter\""
+        grep -v $UPDATED_SOURCE "$TAGS_FILE" > "$TAGS_FILE.filter"
+        mv "$TAGS_FILE.filter" "$TAGS_FILE"
+        CTAGS_ARGS="$CTAGS_ARGS --append $UPDATED_SOURCE"
+    fi
+fi
+
+echo "Running ctags"
+echo "$CTAGS_EXE -R -f \"$TAGS_FILE\" $CTAGS_ARGS"
+$CTAGS_EXE -R -f "$TAGS_FILE" $CTAGS_ARGS
+
+echo "Unlocking tags file..."
+rm -f "$TAGS_FILE.lock"
+
+echo "Done."
+
+if [[ $PAUSE_BEFORE_EXIT -eq 1 ]]; then
+    read -p "Press ENTER to exit..."
+fi
+
--- a/plat/win32/update_tags.cmd	Sun Jul 20 14:21:43 2014 -0700
+++ b/plat/win32/update_tags.cmd	Sun Jul 20 14:25:09 2014 -0700
@@ -13,26 +13,26 @@
 
 :ParseArgs
 if [%1]==[] goto :DoneParseArgs
-if [%1]==[--exe] (
+if [%1]==[-e] (
     set CTAGS_EXE=%~2
     shift
     goto :LoopParseArgs
 )
-if [%1]==[--tags] (
+if [%1]==[-t] (
     set TAGS_FILE=%~2
     shift
     goto :LoopParseArgs
 )
-if [%1]==[--source] (
+if [%1]==[-s] (
     set UPDATED_SOURCE=%~2
     shift
     goto :LoopParseArgs
 )
-if [%1]==[--pause] (
+if [%1]==[-p] (
     set PAUSE_BEFORE_EXIT=1
     goto :LoopParseArgs
 )
-if [%1]==[--log] (
+if [%1]==[-l] (
     set LOG_FILE=%~2
     shift
     goto :LoopParseArgs
@@ -91,8 +91,9 @@
 echo Usage:
 echo    %~n0 ^<options^>
 echo.
-echo    --exe [exe=ctags]:  The ctags executable to run
-echo    --tags [file=tags]: The path to the ctags file to update
-echo    --source [file=]:   The path to the source file that needs updating
+echo    -e [exe=ctags]: The ctags executable to run
+echo    -t [file=tags]: The path to the ctags file to update
+echo    -s [file=]:     The path to the source file that needs updating
+echo    -l [log=]:      The log file to output to
 echo.
 
--- a/plugin/autotags.vim	Sun Jul 20 14:21:43 2014 -0700
+++ b/plugin/autotags.vim	Sun Jul 20 14:25:09 2014 -0700
@@ -17,7 +17,7 @@
 let g:loaded_autotags = 1
 
 if !exists('g:autotags_trace')
-    let g:autotags_trace = 1
+    let g:autotags_trace = 0
 endif
 
 if !exists('g:autotags_fake')
@@ -45,6 +45,22 @@
 endif
 let g:autotags_project_root += ['.git', '.hg', '.bzr', '_darcs']
 
+if !exists('g:autotags_exclude')
+    let g:autotags_exclude = []
+endif
+
+if !exists('g:autotags_generate_on_missing')
+    let g:autotags_generate_on_missing = 1
+endif
+
+if !exists('g:autotags_generate_on_write')
+    let g:autotags_generate_on_write = 1
+endif
+
+if !exists('g:autotags_auto_set_tags')
+    let g:autotags_auto_set_tags = 1
+endif
+
 " }}}
 
 " Utilities {{{
@@ -111,25 +127,43 @@
 
 " Setup autotags for the current buffer.
 function! s:setup_autotags() abort
-    call s:trace("Scanning buffer '" . bufname('%') . "' for autotags setup...")
-    if exists('b:autotags_file')
+    if exists('b:autotags_file') && !g:autotags_debug
+        " This buffer already has autotags support.
         return
     endif
+
+    " Try and file what tags file we should manage.
+    call s:trace("Scanning buffer '" . bufname('%') . "' for autotags setup...")
     try
         let b:autotags_file = s:get_tagfile_for(expand('%:h'))
     catch /^autotags\:/
+        call s:trace("Can't figure out what tag file to use... no autotags support.")
         return
     endtry
 
+    " We know what tags file to manage! Now set things up.
     call s:trace("Setting autotags for buffer '" . bufname('%') . "' with tagfile: " . b:autotags_file)
 
+    " Set the tags file for Vim to use.
+    if g:autotags_auto_set_tags
+        execute 'setlocal tags^=' . b:autotags_file
+    endif
+
+    " Autocommands for updating the tags on save.
     let l:bn = bufnr('%')
     execute 'augroup autotags_buffer_' . l:bn
     execute '  autocmd!'
-    execute '  autocmd BufWritePost <buffer=' . l:bn . '> if g:autotags_enabled|call s:update_tags(0, 1)|endif'
+    execute '  autocmd BufWritePost <buffer=' . l:bn . '> call s:write_triggered_update_tags()'
     execute 'augroup end'
 
+    " Miscellaneous commands.
     command! -buffer -bang AutotagsUpdate :call s:manual_update_tags(<bang>0)
+
+    " If the tags file doesn't exist, start generating it now.
+    if g:autotags_generate_on_missing && !filereadable(b:autotags_file)
+        call s:trace("Generating missing tags file: " . b:autotags_file)
+        call s:update_tags(1, 0)
+    endif
 endfunction
 
 augroup autotags_detect
@@ -148,6 +182,7 @@
 endif
 
 let s:update_queue = []
+let s:maybe_in_progress = []
 
 " Get how to execute an external command depending on debug settings.
 function! s:get_execute_cmd() abort
@@ -162,11 +197,27 @@
     endif
 endfunction
 
+" Get the suffix for how to execute an external command.
+function! s:get_execute_cmd_suffix() abort
+    if has('win32')
+        return ''
+    else
+        return ' &'
+    endif
+endfunction
+
 " (Re)Generate the tags file for the current buffer's file.
 function! s:manual_update_tags(bang) abort
     call s:update_tags(a:bang, 0)
 endfunction
 
+" (Re)Generate the tags file for a buffer that just go saved.
+function! s:write_triggered_update_tags() abort
+    if g:autotags_enabled && g:autotags_generate_on_write
+        call s:update_tags(0, 1)
+    endif
+endfunction
+
 " Update the tags file for the current buffer's file.
 " write_mode:
 "   0: update the tags file if it exists, generate it otherwise.
@@ -180,7 +231,6 @@
 " is specified, it will go to the autotags-defined file.
 function! s:update_tags(write_mode, queue_mode, ...) abort
     " Figure out where to save.
-    let l:tags_file = 0
     if a:0 == 1
         let l:tags_file = a:1
     else
@@ -214,22 +264,38 @@
     try
         " Build the command line.
         let l:cmd = s:get_execute_cmd() . s:runner_exe
-        let l:cmd .= ' --exe "' . g:autotags_executable . '"'
-        let l:cmd .= ' --tags "' . fnamemodify(l:tags_file, ':t') . '"'
+        let l:cmd .= ' -e "' . g:autotags_executable . '"'
+        let l:cmd .= ' -t "' . fnamemodify(l:tags_file, ':t') . '"'
         if a:write_mode == 0 && filereadable(l:tags_file)
             " CTags specifies paths relative to the tags file with a `./`
             " prefix, so we need to specify the same prefix otherwise it will
             " think those are different files and we'll end up with duplicate
             " entries.
             let l:rel_path = s:normalizepath('./' . expand('%:.'))
-            let l:cmd .= ' --source "' . l:rel_path . '"'
+            let l:cmd .= ' -s "' . l:rel_path . '"'
         endif
+        for ign in split(&wildignore, ',')
+            let l:cmd .= ' -x ' . ign
+        endfor
+        for exc in g:autotags_exclude
+            let l:cmd .= ' -x ' . exc
+        endfor
         if g:autotags_trace
-            let l:cmd .= ' --log "' . fnamemodify(l:tags_file, ':t') . '.log"'
+            if has('win32')
+                let l:cmd .= ' -l "' . fnamemodify(l:tags_file, ':t') . '.log"'
+            else
+                let l:cmd .= ' > "' . fnamemodify(l:tags_file, ':t') . '.log"'
+            endif
         endif
+        let l:cmd .= s:get_execute_cmd_suffix()
+
+        " Run the background process.
         call s:trace("Running: " . l:cmd)
         call s:trace("In:      " . l:work_dir)
         if !g:autotags_fake
+            " Flag this tags file as being in progress
+            call add(s:maybe_in_progress, fnamemodify(l:tags_file, ':p'))
+
             if !g:autotags_trace
                 silent execute l:cmd
             else
@@ -257,7 +323,7 @@
 
 " }}}
 
-" Toggles {{{
+" Toggles and Miscellaneous Commands {{{
 
 command! AutotagsToggleEnabled :let g:autotags_enabled=!g:autotags_enabled
 command! AutotagsToggleTrace   :call autotags#trace()
@@ -308,5 +374,61 @@
     echom ""
 endfunction
 
+function! autotags#inprogress()
+    echom "autotags: generations in progress:"
+    for mip in s:maybe_in_progress
+        echom mip
+    endfor
+    echom ""
+endfunction
+
 " }}}
 
+" Statusline Functions {{{
+
+" Prints whether a tag file is being generated right now for the current
+" buffer in the status line.
+"
+" Arguments can be passed:
+" - args 1 and 2 are the prefix and suffix, respectively, of whatever output,
+"   if any, is going to be produced.
+"   (defaults to empty strings)
+" - arg 3 is the text to be shown if tags are currently being generated.
+"   (defaults to 'TAGS')
+"
+function! autotags#statusline(...) abort
+    if !exists('b:autotags_file')
+        " This buffer doesn't have autotags.
+        return ''
+    endif
+
+    " Figure out what the user is customizing.
+    let l:gen_msg = 'TAGS'
+    if a:0 >= 0
+        let l:gen_msg = a:1
+    endif
+
+    " To make this function as fast as possible, we first check whether the
+    " current buffer's tags file is 'maybe' being generated. This provides a
+    " nice and quick bail out for 99.9% of cases before we need to this the
+    " file-system to check the lock file.
+    let l:abs_tag_file = fnamemodify(b:autotags_file, ':p')
+    let l:found = index(s:maybe_in_progress, l:abs_tag_file)
+    if l:found < 0
+        return ''
+    endif
+    " It's maybe generating! Check if the lock file is still there.
+    if !filereadable(l:abs_tag_file . '.lock')
+        call remove(s:maybe_in_progress, l:found)
+        return ''
+    endif
+    " It's still there! So probably `ctags` is still running...
+    " (although there's a chance it crashed, or the script had a problem, and
+    " the lock file has been left behind... we could try and run some
+    " additional checks here to see if it's legitimately running, and
+    " otherwise delete the lock file... maybe in the future...)
+    return l:gen_msg
+endfunction
+
+" }}}
+