changeset 41:99328cb71e75

Refactor Gutentags so functionality can be implemented in "modules". The first module is Ctags generation, obviously.
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 20 Feb 2015 14:13:51 -0800
parents 8b3c611a4d3b
children 5c1baa6007d8
files autoload/gutentags.vim autoload/gutentags/ctags.vim plugin/gutentags.vim
diffstat 3 files changed, 498 insertions(+), 411 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/autoload/gutentags.vim	Fri Feb 20 14:13:51 2015 -0800
@@ -0,0 +1,376 @@
+" gutentags.vim - Automatic ctags management for Vim
+
+" Utilities {{{
+
+" Throw an exception message.
+function! gutentags#throw(message)
+    let v:errmsg = "gutentags: " . a:message
+    throw v:errmsg
+endfunction
+
+" Prints a message if debug tracing is enabled.
+function! gutentags#trace(message, ...)
+   if g:gutentags_trace || (a:0 && a:1)
+       let l:message = "gutentags: " . a:message
+       echom l:message
+   endif
+endfunction
+
+" Strips the ending slash in a path.
+function! gutentags#stripslash(path)
+    return fnamemodify(a:path, ':s?[/\\]$??')
+endfunction
+
+" Normalizes the slashes in a path.
+function! gutentags#normalizepath(path)
+    if exists('+shellslash') && &shellslash
+        return substitute(a:path, '\v/', '\\', 'g')
+    elseif has('win32')
+        return substitute(a:path, '\v/', '\\', 'g')
+    else
+        return a:path
+    endif
+endfunction
+
+" Shell-slashes the path (opposite of `normalizepath`).
+function! gutentags#shellslash(path)
+  if exists('+shellslash') && !&shellslash
+    return substitute(a:path, '\v\\', '/', 'g')
+  else
+    return a:path
+  endif
+endfunction
+
+" Gets a file path in the correct `plat` folder.
+function! gutentags#get_plat_file(filename) abort
+    return g:gutentags_plat_dir . a:filename . g:gutentags_script_ext
+endfunction
+
+" }}}
+
+" Gutentags Setup {{{
+
+let s:known_files = []
+
+" Finds the first directory with a project marker by walking up from the given
+" file path.
+function! gutentags#get_project_root(path) abort
+    let l:path = gutentags#stripslash(a:path)
+    let l:previous_path = ""
+    let l:markers = g:gutentags_project_root[:]
+    if exists('g:ctrlp_root_markers')
+        let l:markers += g:ctrlp_root_markers
+    endif
+    while l:path != l:previous_path
+        for root in g:gutentags_project_root
+            if getftype(l:path . '/' . root) != ""
+                let l:proj_dir = simplify(fnamemodify(l:path, ':p'))
+                return gutentags#stripslash(l:proj_dir)
+            endif
+        endfor
+        let l:previous_path = l:path
+        let l:path = fnamemodify(l:path, ':h')
+    endwhile
+    call gutentags#throw("Can't figure out what tag file to use for: " . a:path)
+endfunction
+
+" Generate a path for a given filename in the cache directory.
+function! gutentags#get_cachefile(root_dir, filename) abort
+    let l:tag_path = gutentags#stripslash(a:root_dir) . '/' . a:filename
+    if g:gutentags_cache_dir != ""
+        " Put the tag file in the cache dir instead of inside the
+        " projet root.
+        let l:tag_path = g:gutentags_cache_dir . '/' .
+                    \tr(l:tag_path, '\/:', '---')
+        let l:tag_path = substitute(l:tag_path, '/\-', '/', '')
+    endif
+    let l:tag_path = gutentags#normalizepath(l:tag_path)
+    return l:tag_path
+endfunction
+
+" Setup gutentags for the current buffer.
+function! gutentags#setup_gutentags() abort
+    if exists('b:gutentags_files') && !g:gutentags_debug
+        " This buffer already has gutentags support.
+        return
+    endif
+
+    " Try and find what tags file we should manage.
+    call gutentags#trace("Scanning buffer '" . bufname('%') . "' for gutentags setup...")
+    try
+        let b:gutentags_root = gutentags#get_project_root(expand('%:h'))
+        let b:gutentags_files = {}
+        for module in g:gutentags_modules
+            call call("gutentags#".module."#init", [b:gutentags_root])
+        endfor
+    catch /^gutentags\:/
+        call gutentags#trace("Can't figure out what tag file to use... no gutentags support.")
+        return
+    endtry
+
+    " We know what tags file to manage! Now set things up.
+    call gutentags#trace("Setting gutentags for buffer '" . bufname('%'))
+
+    " Autocommands for updating the tags on save.
+    let l:bn = bufnr('%')
+    execute 'augroup gutentags_buffer_' . l:bn
+    execute '  autocmd!'
+    execute '  autocmd BufWritePost <buffer=' . l:bn . '> call s:write_triggered_update_tags()'
+    execute 'augroup end'
+
+    " Miscellaneous commands.
+    command! -buffer -bang GutentagsUpdate :call s:manual_update_tags(<bang>0)
+
+    " Add these tags files to the known tags files.
+    for module in keys(b:gutentags_files)
+        let l:tagfile = b:gutentags_files[module]
+        let l:found = index(s:known_files, l:tagfile)
+        if l:found < 0
+            call add(s:known_files, l:tagfile)
+
+            " Generate this new file depending on settings and stuff.
+            if g:gutentags_generate_on_missing && !filereadable(l:tagfile)
+                call gutentags#trace("Generating missing tags file: " . l:tagfile)
+                call s:update_tags(module, 1, 0)
+            elseif g:gutentags_generate_on_new
+                call gutentags#trace("Generating tags file: " . l:tagfile)
+                call s:update_tags(module, 1, 0)
+            endif
+        endif
+    endfor
+endfunction
+
+" }}}
+
+"  Tags File Management {{{
+
+" List of queued-up jobs, and in-progress jobs, per module.
+let s:update_queue = {}
+let s:maybe_in_progress = {}
+for module in g:gutentags_modules
+    let s:update_queue[module] = []
+    let s:maybe_in_progress[module] = {}
+endfor
+
+" Make a given file known as being currently generated or updated.
+function! gutentags#add_progress(module, file) abort
+    let s:maybe_in_progress[a:module][a:file] = localtime()
+endfunction
+
+" Get how to execute an external command depending on debug settings.
+function! gutentags#get_execute_cmd() abort
+    if has('win32')
+        let l:cmd = '!start '
+        if g:gutentags_background_update
+            let l:cmd .= '/b '
+        endif
+        return l:cmd
+    else
+        return '!'
+    endif
+endfunction
+
+" Get the suffix for how to execute an external command.
+function! gutentags#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(module, bang) abort
+    for module in g:gutentags_modules
+        call s:update_tags(module, a:bang, 0)
+    endfor
+endfunction
+
+" (Re)Generate the tags file for a buffer that just go saved.
+function! s:write_triggered_update_tags() abort
+    if g:gutentags_enabled && g:gutentags_generate_on_write
+        for module in g:gutentags_modules
+            call s:update_tags(module, 0, 1)
+        endfor
+    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.
+"   1: always generate (overwrite) the tags file.
+"
+" queue_mode:
+"   0: if an update is already in progress, report it and abort.
+"   1: if an update is already in progress, queue another one.
+"
+" An additional argument specifies where to write the tags file. If nothing
+" is specified, it will go to the gutentags-defined file.
+function! s:update_tags(module, write_mode, queue_mode, ...) abort
+    " Figure out where to save.
+    if a:0 == 1
+        let l:tags_file = a:1
+        let l:proj_dir = fnamemodify(a:1, ':h')
+    else
+        let l:tags_file = b:gutentags_files[a:module]
+        let l:proj_dir = b:gutentags_root
+    endif
+
+    " Check that there's not already an update in progress.
+    let l:lock_file = l:tags_file . '.lock'
+    if filereadable(l:lock_file)
+        if a:queue_mode == 1
+            let l:idx = index(s:update_queue[a:module], l:tags_file)
+            if l:idx < 0
+                call add(s:update_queue[a:module], l:tags_file)
+            endif
+            call gutentags#trace("Tag file '" . l:tags_file . 
+                        \"' is already being updated. Queuing it up...")
+            call gutentags#trace("")
+        else
+            echom "gutentags: The tags file is already being updated, " .
+                        \"please try again later."
+            echom ""
+        endif
+        return
+    endif
+
+    " Switch to the project root to make the command line smaller, and make
+    " it possible to get the relative path of the filename to parse if we're
+    " doing an incremental update.
+    let l:prev_cwd = getcwd()
+    execute "chdir " . fnameescape(l:proj_dir)
+    try
+        call call("gutentags#".a:module."#generate",
+                    \[l:proj_dir, l:tags_file, a:write_mode])
+    finally
+        " Restore the current directory...
+        execute "chdir " . fnameescape(l:prev_cwd)
+    endtry
+endfunction
+
+" }}}
+
+" Manual Tagfile Generation {{{
+
+function! s:generate_tags(bang, ...) abort
+    call s:update_tags(1, 0, a:1)
+endfunction
+
+command! -bang -nargs=1 -complete=file GutentagsGenerate :call s:generate_tags(<bang>0, <f-args>)
+
+" }}}
+
+" Utility Functions {{{
+
+function! gutentags#rescan(...)
+    if exists('b:gutentags_files')
+        unlet b:gutentags_files
+    endif
+    if a:0 && a:1
+        let l:trace_backup = g:gutentags_trace
+        let l:gutentags_trace = 1
+    endif
+    call s:setup_gutentags()
+    if a:0 && a:1
+        let g:gutentags_trace = l:trace_backup
+    endif
+endfunction
+
+function! gutentags#toggletrace(...)
+    let g:gutentags_trace = !g:gutentags_trace
+    if a:0 > 0
+        let g:gutentags_trace = a:1
+    endif
+    if g:gutentags_trace
+        echom "gutentags: Tracing is enabled."
+    else
+        echom "gutentags: Tracing is disabled."
+    endif
+    echom ""
+endfunction
+
+function! gutentags#fake(...)
+    let g:gutentags_fake = !g:gutentags_fake
+    if a:0 > 0
+        let g:gutentags_fake = a:1
+    endif
+    if g:gutentags_fake
+        echom "gutentags: Now faking gutentags."
+    else
+        echom "gutentags: Now running gutentags for real."
+    endif
+    echom ""
+endfunction
+
+function! gutentags#inprogress()
+    echom "gutentags: generations in progress:"
+    for mip in keys(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! gutentags#statusline(...) abort
+    if !exists('b:gutentags_files')
+        " This buffer doesn't have gutentags.
+        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:modules_in_progress = []
+    for module in keys(b:gutentags_files)
+        let l:abs_tag_file = fnamemodify(b:gutentags_files[module], ':p')
+        let l:progress_queue = s:maybe_in_progress[module]
+        let l:timestamp = get(l:progress_queue, l:abs_tag_file)
+        if l:timestamp == 0
+            return ''
+        endif
+        " It's maybe generating! Check if the lock file is still there... but
+        " don't do it too soon after the script was originally launched, because
+        " there can be a race condition where we get here just before the script
+        " had a chance to write the lock file.
+        if (localtime() - l:timestamp) > 1 &&
+                    \!filereadable(l:abs_tag_file . '.lock')
+            call remove(l:progress_queue, l:abs_tag_file)
+            return ''
+        endif
+        call add(l:modules_in_progress, module)
+    endfor
+
+    " 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...)
+    if len(g:gutentags_modules) > 1
+        let l:gen_msg .= '['.join(l:modules_in_progress, ',').']'
+    endif
+    return l:gen_msg
+endfunction
+
+" }}}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/autoload/gutentags/ctags.vim	Fri Feb 20 14:13:51 2015 -0800
@@ -0,0 +1,104 @@
+" Ctags module for Gutentags
+
+" Global Options {{{
+
+if !exists('g:gutentags_ctags_executable')
+    let g:gutentags_executable = 'ctags'
+endif
+
+if !exists('g:gutentags_ctags_options_file')
+    let g:gutentags_ctags_options_file = ''
+endif
+
+if !exists('g:gutentags_tagfile')
+    let g:gutentags_tagfile = 'tags'
+endif
+
+if !exists('g:gutentags_auto_set_tags')
+    let g:gutentags_auto_set_tags = 1
+endif
+
+" }}}
+
+" Gutentags Module Interface {{{
+
+let s:runner_exe = gutentags#get_plat_file('update_tags')
+
+function! gutentags#ctags#init(project_root) abort
+    " Figure out the path to the tags file.
+    let b:gutentags_files['ctags'] = gutentags#get_cachefile(
+                \a:project_root, g:gutentags_tagfile)
+
+    " Set the tags file for Vim to use.
+    if g:gutentags_auto_set_tags
+        execute 'setlocal tags^=' . fnameescape(b:gutentags_files['ctags'])
+    endif
+endfunction
+
+function! gutentags#ctags#generate(proj_dir, tags_file, write_mode) abort
+    " Get to the tags file directory because ctags is finicky about
+    " these things.
+    let l:prev_cwd = getcwd()
+    let l:work_dir = fnamemodify(a:tags_file, ':h')
+    execute "chdir " . fnameescape(l:work_dir)
+
+    try
+        " Build the command line.
+        let l:cmd = gutentags#get_execute_cmd() . s:runner_exe
+        let l:cmd .= ' -e "' . g:gutentags_executable . '"'
+        let l:cmd .= ' -t "' . a:tags_file . '"'
+        let l:cmd .= ' -p "' . a:proj_dir . '"'
+        if a:write_mode == 0 && filereadable(a:tags_file)
+            let l:full_path = expand('%:p')
+            let l:cmd .= ' -s "' . l:full_path . '"'
+        endif
+        for ign in split(&wildignore, ',')
+            let l:cmd .= ' -x ' . '"' . ign . '"'
+        endfor
+        for exc in g:gutentags_exclude
+            let l:cmd .= ' -x ' . '"' . exc . '"'
+        endfor
+        if g:gutentags_pause_after_update
+            let l:cmd .= ' -c'
+        endif
+        if len(g:gutentags_options_file)
+            let l:cmd .= ' -o "' . g:gutentags_options_file . '"'
+        endif
+        if g:gutentags_trace
+            if has('win32')
+                let l:cmd .= ' -l "' . a:tags_file . '.log"'
+            else
+                let l:cmd .= ' > "' . a:tags_file . '.log" 2>&1'
+            endif
+        else
+            if !has('win32')
+                let l:cmd .= ' > /dev/null 2>&1'
+            endif
+        endif
+        let l:cmd .= gutentags#get_execute_cmd_suffix()
+
+        call gutentags#trace("Running: " . l:cmd)
+        call gutentags#trace("In:      " . getcwd())
+        if !g:gutentags_fake
+            " Run the background process.
+            if !g:gutentags_trace
+                silent execute l:cmd
+            else
+                execute l:cmd
+            endif
+
+            " Flag this tags file as being in progress
+            let l:full_tags_file = fnamemodify(a:tags_file, ':p')
+            call gutentags#add_progress('ctags', l:full_tags_file)
+        else
+            call gutentags#trace("(fake... not actually running)")
+        endif
+        call gutentags#trace("")
+    finally
+        " Restore the previous working directory.
+        execute "chdir " . fnameescape(l:prev_cwd)
+    endtry
+endfunction
+
+" }}}
+
--- a/plugin/gutentags.vim	Sat Jan 10 20:45:49 2015 -0800
+++ b/plugin/gutentags.vim	Fri Feb 20 14:13:51 2015 -0800
@@ -36,12 +36,8 @@
     let g:gutentags_enabled = 1
 endif
 
-if !exists('g:gutentags_executable')
-    let g:gutentags_executable = 'ctags'
-endif
-
-if !exists('g:gutentags_tagfile')
-    let g:gutentags_tagfile = 'tags'
+if !exists('g:gutentags_modules')
+    let g:gutentags_modules = ['ctags']
 endif
 
 if !exists('g:gutentags_project_root')
@@ -49,10 +45,6 @@
 endif
 let g:gutentags_project_root += ['.git', '.hg', '.svn', '.bzr', '_darcs']
 
-if !exists('g:gutentags_options_file')
-    let g:gutentags_options_file = ''
-endif
-
 if !exists('g:gutentags_exclude')
     let g:gutentags_exclude = []
 endif
@@ -69,10 +61,6 @@
     let g:gutentags_generate_on_write = 1
 endif
 
-if !exists('g:gutentags_auto_set_tags')
-    let g:gutentags_auto_set_tags = 1
-endif
-
 if !exists('g:gutentags_cache_dir')
     let g:gutentags_cache_dir = ''
 else
@@ -83,314 +71,37 @@
     call mkdir(g:gutentags_cache_dir, 'p')
 endif
 
-" }}}
-
-" Utilities {{{
-
-" Throw an exception message.
-function! s:throw(message)
-    let v:errmsg = "gutentags: " . a:message
-    throw v:errmsg
-endfunction
-
-" Prints a message if debug tracing is enabled.
-function! s:trace(message, ...)
-   if g:gutentags_trace || (a:0 && a:1)
-       let l:message = "gutentags: " . a:message
-       echom l:message
-   endif
-endfunction
-
-" Strips the ending slash in a path.
-function! s:stripslash(path)
-    return fnamemodify(a:path, ':s?[/\\]$??')
-endfunction
-
-" Normalizes the slashes in a path.
-function! s:normalizepath(path)
-    if exists('+shellslash') && &shellslash
-        return substitute(a:path, '\v/', '\\', 'g')
-    elseif has('win32')
-        return substitute(a:path, '\v/', '\\', 'g')
-    else
-        return a:path
-    endif
-endfunction
-
-" Shell-slashes the path (opposite of `normalizepath`).
-function! s:shellslash(path)
-  if exists('+shellslash') && !&shellslash
-    return substitute(a:path, '\v\\', '/', 'g')
-  else
-    return a:path
-  endif
-endfunction
+if has('win32')
+    let g:gutentags_plat_dir = expand('<sfile>:h:h:p') . "\\plat\\win32\\"
+    let g:gutentags_script_ext = '.cmd'
+else
+    let g:gutentags_plat_dir = expand('<sfile>:h:h:p') . '/plat/unix/'
+    let g:gutentags_script_ext = '.sh'
+endif
 
 " }}}
 
 " Gutentags Setup {{{
 
-let s:known_tagfiles = []
-
-" Finds the first directory with a project marker by walking up from the given
-" file path.
-function! s:get_project_root(path) abort
-    let l:path = s:stripslash(a:path)
-    let l:previous_path = ""
-    let l:markers = g:gutentags_project_root[:]
-    if exists('g:ctrlp_root_markers')
-        let l:markers += g:ctrlp_root_markers
-    endif
-    while l:path != l:previous_path
-        for root in g:gutentags_project_root
-            if getftype(l:path . '/' . root) != ""
-                let l:proj_dir = simplify(fnamemodify(l:path, ':p'))
-                return s:stripslash(l:proj_dir)
-            endif
-        endfor
-        let l:previous_path = l:path
-        let l:path = fnamemodify(l:path, ':h')
-    endwhile
-    call s:throw("Can't figure out what tag file to use for: " . a:path)
-endfunction
-
-" Get the tag filename for a given project root.
-function! s:get_tagfile(root_dir) abort
-    let l:tag_path = s:stripslash(a:root_dir) . '/' . g:gutentags_tagfile
-    if g:gutentags_cache_dir != ""
-        " Put the tag file in the cache dir instead of inside the
-        " projet root.
-        let l:tag_path = g:gutentags_cache_dir . '/' .
-                    \tr(l:tag_path, '\/:', '---')
-        let l:tag_path = substitute(l:tag_path, '/\-', '/', '')
-    endif
-    let l:tag_path = s:normalizepath(l:tag_path)
-    return l:tag_path
-endfunction
-
-" Setup gutentags for the current buffer.
-function! s:setup_gutentags() abort
-    if exists('b:gutentags_file') && !g:gutentags_debug
-        " This buffer already has gutentags support.
-        return
-    endif
-
-    " Try and find what tags file we should manage.
-    call s:trace("Scanning buffer '" . bufname('%') . "' for gutentags setup...")
-    try
-        let b:gutentags_root = s:get_project_root(expand('%:h'))
-        let b:gutentags_file = s:get_tagfile(b:gutentags_root)
-    catch /^gutentags\:/
-        call s:trace("Can't figure out what tag file to use... no gutentags support.")
-        return
-    endtry
-
-    " We know what tags file to manage! Now set things up.
-    call s:trace("Setting gutentags for buffer '" . bufname('%') . "' with tagfile: " . b:gutentags_file)
-
-    " Set the tags file for Vim to use.
-    if g:gutentags_auto_set_tags
-        execute 'setlocal tags^=' . fnameescape(b:gutentags_file)
-    endif
-
-    " Autocommands for updating the tags on save.
-    let l:bn = bufnr('%')
-    execute 'augroup gutentags_buffer_' . l:bn
-    execute '  autocmd!'
-    execute '  autocmd BufWritePost <buffer=' . l:bn . '> call s:write_triggered_update_tags()'
-    execute 'augroup end'
-
-    " Miscellaneous commands.
-    command! -buffer -bang GutentagsUpdate :call s:manual_update_tags(<bang>0)
-
-    " Add this tags file to the known tags files if it wasn't there already.
-    let l:found = index(s:known_tagfiles, b:gutentags_file)
-    if l:found < 0
-        call add(s:known_tagfiles, b:gutentags_file)
-
-        " Generate this new file depending on settings and stuff.
-        if g:gutentags_generate_on_missing && !filereadable(b:gutentags_file)
-            call s:trace("Generating missing tags file: " . b:gutentags_file)
-            call s:update_tags(1, 0)
-        elseif g:gutentags_generate_on_new
-            call s:trace("Generating tags file: " . b:gutentags_file)
-            call s:update_tags(1, 0)
-        endif
-    endif
-endfunction
-
 augroup gutentags_detect
     autocmd!
-    autocmd BufNewFile,BufReadPost *  call s:setup_gutentags()
-    autocmd VimEnter               *  if expand('<amatch>')==''|call s:setup_gutentags()|endif
+    autocmd BufNewFile,BufReadPost *  call gutentags#setup_gutentags()
+    autocmd VimEnter               *  if expand('<amatch>')==''|call gutentags#setup_gutentags()|endif
 augroup end
 
 " }}}
 
-"  Tags File Management {{{
-
-let s:runner_exe = expand('<sfile>:h:h') . '/plat/unix/update_tags.sh'
-if has('win32')
-    let s:runner_exe = expand('<sfile>:h:h') . '\plat\win32\update_tags.cmd'
-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
-    if has('win32')
-        let l:cmd = '!start '
-        if g:gutentags_background_update
-            let l:cmd .= '/b '
-        endif
-        return l:cmd
-    else
-        return '!'
-    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:gutentags_enabled && g:gutentags_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.
-"   1: always generate (overwrite) the tags file.
-"
-" queue_mode:
-"   0: if an update is already in progress, report it and abort.
-"   1: if an update is already in progress, queue another one.
-"
-" An additional argument specifies where to write the tags file. If nothing
-" is specified, it will go to the gutentags-defined file.
-function! s:update_tags(write_mode, queue_mode, ...) abort
-    " Figure out where to save.
-    if a:0 == 1
-        let l:tags_file = a:1
-        let l:proj_dir = fnamemodify(a:1, ':h')
-    else
-        let l:tags_file = b:gutentags_file
-        let l:proj_dir = b:gutentags_root
-    endif
-
-    " Check that there's not already an update in progress.
-    let l:lock_file = l:tags_file . '.lock'
-    if filereadable(l:lock_file)
-        if a:queue_mode == 1
-            let l:idx = index(s:update_queue, l:tags_file)
-            if l:idx < 0
-                call add(s:update_queue, l:tags_file)
-            endif
-            call s:trace("Tag file '" . l:tags_file . "' is already being updated. Queuing it up...")
-            call s:trace("")
-        else
-            echom "gutentags: The tags file is already being updated, please try again later."
-            echom ""
-        endif
-        return
-    endif
-
-    " Switch to the project root to make the command line smaller, and make
-    " it possible to get the relative path of the filename to parse if we're
-    " doing an incremental update.
-    let l:prev_cwd = getcwd()
-    let l:work_dir = fnamemodify(l:tags_file, ':h')
-    execute "chdir " . l:work_dir
-
-    try
-        " Build the command line.
-        let l:cmd = s:get_execute_cmd() . s:runner_exe
-        let l:cmd .= ' -e "' . g:gutentags_executable . '"'
-        let l:cmd .= ' -t "' . l:tags_file . '"'
-        let l:cmd .= ' -p "' . l:proj_dir . '"'
-        if a:write_mode == 0 && filereadable(l:tags_file)
-            let l:full_path = expand('%:p')
-            let l:cmd .= ' -s "' . l:full_path . '"'
-        endif
-        for ign in split(&wildignore, ',')
-            let l:cmd .= ' -x ' . '"' . ign . '"'
-        endfor
-        for exc in g:gutentags_exclude
-            let l:cmd .= ' -x ' . '"' . exc . '"'
-        endfor
-        if g:gutentags_pause_after_update
-            let l:cmd .= ' -c'
-        endif
-        if len(g:gutentags_options_file)
-            let l:cmd .= ' -o "' . g:gutentags_options_file . '"'
-        endif
-        if g:gutentags_trace
-            if has('win32')
-                let l:cmd .= ' -l "' . l:tags_file . '.log"'
-            else
-                let l:cmd .= ' > "' . l:tags_file . '.log" 2>&1'
-            endif
-        else
-            if !has('win32')
-                let l:cmd .= ' > /dev/null 2>&1'
-            endif
-        endif
-        let l:cmd .= s:get_execute_cmd_suffix()
-
-        call s:trace("Running: " . l:cmd)
-        call s:trace("In:      " . l:work_dir)
-        if !g:gutentags_fake
-            " Run the background process.
-            if !g:gutentags_trace
-                silent execute l:cmd
-            else
-                execute l:cmd
-            endif
-
-            " Flag this tags file as being in progress
-            let l:full_tags_file = fnamemodify(l:tags_file, ':p')
-            let s:maybe_in_progress[l:full_tags_file] = localtime()
-        else
-            call s:trace("(fake... not actually running)")
-        endif
-        call s:trace("")
-    finally
-        " Restore the current directory...
-        execute "chdir " . fnameescape(l:prev_cwd)
-    endtry
-endfunction
-
-" }}}
-
-" Manual Tagfile Generation {{{
-
-function! s:generate_tags(bang, ...) abort
-    call s:update_tags(1, 0, a:1)
-endfunction
-
-command! -bang -nargs=1 -complete=file GutentagsGenerate :call s:generate_tags(<bang>0, <f-args>)
-
-" }}}
-
 " Toggles and Miscellaneous Commands {{{
 
+function! s:delete_lock_files() abort
+    for tagfile in values(b:gutentags_files)
+        silent call delete(tagfile.'.lock')
+    endfor
+endfunction
+
 command! GutentagsToggleEnabled :let g:gutentags_enabled=!g:gutentags_enabled
 command! GutentagsToggleTrace   :call gutentags#trace()
-command! GutentagsUnlock        :call delete(b:gutentags_file . '.lock')
+command! GutentagsUnlock        :call s:delete_lock_files()
 
 if g:gutentags_debug
     command! GutentagsToggleFake    :call gutentags#fake()
@@ -398,107 +109,3 @@
 
 " }}}
 
-" Autoload Functions {{{
-
-function! gutentags#rescan(...)
-    if exists('b:gutentags_file')
-        unlet b:gutentags_file
-    endif
-    if a:0 && a:1
-        let l:trace_backup = g:gutentags_trace
-        let l:gutentags_trace = 1
-    endif
-    call s:setup_gutentags()
-    if a:0 && a:1
-        let g:gutentags_trace = l:trace_backup
-    endif
-endfunction
-
-function! gutentags#trace(...)
-    let g:gutentags_trace = !g:gutentags_trace
-    if a:0 > 0
-        let g:gutentags_trace = a:1
-    endif
-    if g:gutentags_trace
-        echom "gutentags: Tracing is enabled."
-    else
-        echom "gutentags: Tracing is disabled."
-    endif
-    echom ""
-endfunction
-
-function! gutentags#fake(...)
-    let g:gutentags_fake = !g:gutentags_fake
-    if a:0 > 0
-        let g:gutentags_fake = a:1
-    endif
-    if g:gutentags_fake
-        echom "gutentags: Now faking gutentags."
-    else
-        echom "gutentags: Now running gutentags for real."
-    endif
-    echom ""
-endfunction
-
-function! gutentags#inprogress()
-    echom "gutentags: generations in progress:"
-    for mip in keys(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! gutentags#statusline(...) abort
-    if !exists('b:gutentags_file')
-        " This buffer doesn't have gutentags.
-        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:gutentags_file, ':p')
-    let l:timestamp = get(s:maybe_in_progress, l:abs_tag_file)
-    if l:timestamp == 0
-        return ''
-    endif
-    " It's maybe generating! Check if the lock file is still there... but
-    " don't do it too soon after the script was originally launched, because
-    " there can be a race condition where we get here just before the script
-    " had a chance to write the lock file.
-    if (localtime() - l:timestamp) > 1 &&
-                \!filereadable(l:abs_tag_file . '.lock')
-        call remove(s:maybe_in_progress, l:abs_tag_file)
-        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
-
-" }}}
-