diff --git a/autoload/taggatron.vim b/autoload/taggatron.vim index b503085..a125d83 100644 --- a/autoload/taggatron.vim +++ b/autoload/taggatron.vim @@ -2,70 +2,117 @@ if exists("g:loaded_taggatron") || &cp finish endif - let g:loaded_taggatron= 1 -let s:taggatron_cmd_entry = {"cmd":"ctags-exuberant","args":"",'filesappend':'**'} +" Initialise script default values +let s:taggatron_enabled = 1 +let s:tagcommands = {} +let s:tagcommands_entry = { + \ "cmd": "ctags-exuberant", + \ "args": "", + \ "filesappend": "**" + \ } + +"" +" Check command options and initialise tag creation. +" +" Ensure that tags for the current file do need to be created, validate a set +" of current command line options, and create tags based on them. +" +" @param bool forceCreate A switch indicating if we want to update tags for +" the current file only (0), or (re)create tags for all file under the path. +" +function! taggatron#CheckCommandList(forceCreate) + " Exit early if taggatron is disabled + if taggatron#get('taggatron_enabled') == 0 + call taggatron#debug('Tag file generation is disabled') + return + endif -function! taggatron#CreateTags(cmdset,forceCreate) - call taggatron#debug("Creating tags for file type ".&filetype) - call taggatron#debug(a:cmdset) - call taggatron#debug(s:taggatron_cmd_entry) + " Validate command options for the current file type + call taggatron#debug('Checking for tag command for this file type') + let l:cmdset = get(taggatron#get('tagcommands'), &filetype) - " Define local support variables - let l:cset = {} - let l:eset = a:cmdset - let l:cwd = fnamemodify(getcwd(), ':p') + " Do nothing if tag command is missing + if l:cmdset is 0 + call taggatron#debug('No tag command for filetype ' . &filetype) + return + endif - " Initialise l:cset variable - call extend(l:cset,s:taggatron_cmd_entry) - call extend(l:cset,l:eset) + " Identify project root directory (aka files) + let l:cmdset['files'] = has_key(l:cmdset, 'files') + \ ? fnamemodify(l:cmdset['files'], ':p') + \ : fnamemodify(getcwd(), ':p') + call taggatron#debug('Project root directory: ' . l:cmdset['files']) - " Detect missing tagfile - if !has_key(l:cset,'tagfile') - call taggatron#error("Missing tag file destination from tag commands for file type ".&filetype) + " Do nothing if the file is not inside the project directory + if expand("%:p") !~ '^' . l:cmdset['files'] + call taggatron#debug('Not creating tags: file is not inside project root') return endif - " Identify files to be scanned - if !has_key(l:cset,'files') - let l:cset['files'] = l:cwd - if has_key(l:cset,'filesappend') - let l:cset['files'] = l:cset['files'].l:cset['filesappend'] - endif + " Exit with an error if tag file argument is missing + if !has_key(l:cmdset, 'tagfile') + call taggatron#error('Missing tag file for file type ' . &filetype) + return endif - " Identify the value for the ctag's --language switch - if !has_key(l:cset,"lang") - let l:cset['lang'] = &filetype + " Sanitize tagfile path + let l:cmdset['tagfile'] = fnamemodify(l:cmdset['tagfile'], ':p') + + " Create tag file and ensure that it is in use by the editor + call taggatron#CreateTags(l:cmdset, a:forceCreate) + call taggatron#SetTags(l:cmdset['tagfile']) +endfunction + +"" +" Create tags as per command options +" +" This function is used to update tags for the current file via UpdateTags() +" or to create a new tag file for tags collected from all files matching +" a current/configured language under a specific path. +" +" @param dictionary cmdset A set of command options +" +function! taggatron#CreateTags(cmdset, forceCreate) + call taggatron#debug('Creating tags for file type ' . &filetype) + call taggatron#debug('Argument: ' . string(a:cmdset)) + call taggatron#debug('Default: ' . string(s:tagcommands_entry)) + + " Initialise local support variables + let l:cmdset = {} + + " Build up a set of command line options + call extend(l:cmdset, s:tagcommands_entry) + call extend(l:cmdset, a:cmdset) + + " Updated tag file and exit early + if filereadable(l:cmdset['tagfile']) && a:forceCreate == 0 + call taggatron#debug('Updating tag file ' . l:cmdset['tagfile']) + call taggatron#UpdateTags(l:cmdset['cmd'], l:cmdset['files'], l:cmdset['tagfile']) + return endif - " Generate ctags command - let l:cmdstr = l:cset['cmd'] . " " . l:cset["args"] . " --languages=" . l:cset['lang'] - - " Run ctags to either (re)create or update tag file - if !filereadable(l:cset['tagfile']) || a:forceCreate == 1 - let l:cmdstr = l:cmdstr ." -f ".l:cset['tagfile'] . " " .l:cset['files'] - call taggatron#debug("Executing command: ".l:cmdstr) - call system(l:cmdstr) - else - call taggatron#debug("Updating tag file ".l:cset['tagfile']) - call taggatron#UpdateTags(l:cset['cmd'],l:cwd,l:cset['tagfile']) + " Append path suffix on top of the existing 'files' path + if has_key(l:cmdset, 'filesappend') + let l:cmdset['files'] = l:cmdset['files'] . l:cmdset['filesappend'] endif - " Ensure that generated tags are picked up by the editor - let l:tagfile = fnamemodify(l:cwd.l:cset['tagfile'], ':p') - call taggatron#SetTags(l:tagfile) -endfunction + " Auto-generate --languages command line argument + let l:cmdset['args'] .= ' --languages=' + \ . (has_key(l:cmdset, 'lang') ? l:cmdset['lang'] : &filetype) -function! taggatron#error(str) - echohl Error | echo a:str | echohl None -endfunction + " Auto-generate -f command line argument + let l:cmdset['args'] .= ' -f ' . l:cmdset['tagfile'] -function! taggatron#debug(str) - if g:taggatron_verbose == 1 - echo a:str - endif + " Create a completed command line string from all available arguments + let l:cmdstr = l:cmdset['cmd'] + \ . ' ' . l:cmdset['args'] + \ . ' ' . l:cmdset['files'] + + " Execute tag creation command by passing it over to the system + call taggatron#debug('Executing command: ' . l:cmdstr) + call system(l:cmdstr) endfunction """""""""""""""""" diff --git a/plugin/taggatron.vim b/plugin/taggatron.vim index 694f151..2431dc4 100644 --- a/plugin/taggatron.vim +++ b/plugin/taggatron.vim @@ -1,31 +1,28 @@ -if !exists("g:tagcommands") - let g:tagcommands = {} -endif -if !exists("g:tagdefaults") - let g:tagdefaults = "" -endif -if !exists("g:taggatron_verbose") - let g:taggatron_verbose = 0 -endif -if !exists("g:taggatron_enabled") - let g:taggatron_enabled = 1 -endif - -" Include all default tags -if len(g:tagdefaults) > 0 - call taggatron#debug("Adding default tags: ".g:tagdefaults) - exec "setlocal tags+=".g:tagdefaults -endif - -autocmd BufWritePost * call taggatron#CheckCommandList(0) -command! TagUpdate call taggatron#CheckCommandList(1) -command! -nargs=1 SetTags call taggatron#SetTags() +" Initialise script default values +let s:taggatron_verbose = 0 +let s:tagdefaults = '' + +" Function Declarations +" ===================== +" +" This section is used minimize memory foot print of the idle plugin and to +" speed up loading times. All functions required to initialise the plugin are +" declared below, delaying the load of `autoload/taggatron.vim` file until the +" first time the plugin is used. +"" +" Add a file to the list of local tags +" +" This function is used to add user supplied file to the list of local tags. +" Before being added, the filename is converted to the absolute path to file +" and is only added if that file is not already on the list. +" +" @param list|string files A file or a list of files to be added +" function! taggatron#SetTags(files) " Define local support variables let l:files = type(a:files) == 1 ? [a:files] : a:files let l:tagfiles = [] - let l:cwd = fnamemodify(getcwd(), ':p') " Fail if l:files is not a list if type(l:files) != 3 @@ -40,14 +37,14 @@ function! taggatron#SetTags(files) " Create a list of all tag files currently loaded (absolute path) for l:tagfile in tagfiles() - call add(l:tagfiles, fnamemodify(l:cwd.l:tagfile, ':p')) + call add(l:tagfiles, fnamemodify(l:tagfile, ':p')) endfor " Process all tag files one by one for l:file in l:files " Skip non-existent and unreadable file if !filereadable(l:file) - call taggatron#debug("Skipping non-existent or unreadable tag file: ".l:file) + call taggatron#debug("Skipping unreadable tag file: " . l:file) continue endif @@ -56,29 +53,75 @@ function! taggatron#SetTags(files) " Only add current file to tags if it hasn't been already found if index(l:tagfiles, l:file) == -1 - call taggatron#debug("Adding tag file: ".l:file) - exec "setlocal tags+=".l:file + call taggatron#debug("Adding tag file:" . l:file) + exec "setlocal tags+=" . l:file endif endfor endfunction -function! taggatron#CheckCommandList(forceCreate) - if g:taggatron_enabled != 1 - call taggatron#debug("Tag file generation disabled (taggatron_enabled: " . g:taggatron_enabled . ")") - return +"" +" Fetch a scoped value of an option +" +" Determine a value of an option based on user configuration or pre-configured +" defaults. A user can configure an option by defining it as a buffer variable +" or as a global (buffer vars override globals). Default value can be provided +" by defining a script variable for the whole file or a function local (local +" vars override script vars). When all else fails, falls back the supplied +" default value, if one is supplied. +" +" @param string option Scope-less name of the option +" @param mixed a:1 An option default value for the option +" +function! taggatron#get(option, ...) + for l:scope in ['b', 'g', 'l', 's'] + if exists(l:scope . ':'. a:option) + return eval(l:scope . ':'. a:option) + endif + endfor + + if a:0 > 0 + return a:1 endif - let l:cwd = getcwd() - call taggatron#debug("Current directory: ".l:cwd) - if expand("%:p:h") =~ l:cwd . ".*" - call taggatron#debug("Checking for tag command for this file type") - let l:cmdset = get(g:tagcommands,&filetype) - if l:cmdset is 0 - call taggatron#debug("No tag command for filetype " . &filetype) - else - call taggatron#CreateTags(l:cmdset,a:forceCreate) - endif - else - call taggatron#debug("Not creating tags: file is not in current directory") + call taggatron#error('Invalid or undefined option: ' . a:option) +endfunction + +"" +" Show user an error message +" +" Pre-format supplied message as an Error and display it to the user. All +" messages are saved to message-history and are accessible via `:messages`. +" +" @param string message A message to be displayed to the user +" +function! taggatron#error(message) + echohl Error | echomsg a:message | echohl None +endfunction + +"" +" Show user a debug message +" +" Echo supplied message to the user if verbose mode is enabled. All messages +" are saved to message-history and can be reviewed with :messages command. +" +" @param string message A message to be displayed to the user +" +function! taggatron#debug(message) + " Do nothing if verbose mode is disabled + if taggatron#get('taggatron_verbose') == 0 + return endif + + echomsg a:message endfunction + +" Executable code +" =============== + +" Initialise taggatron auto-commands +autocmd! BufNew,BufRead * call taggatron#SetTags(taggatron#get('tagdefaults', [])) +autocmd! BufWritePost * call taggatron#CheckCommandList(0) + +" Initialise taggatron commands +command! TagUpdate call taggatron#CheckCommandList(1) +command! -nargs=1 SetTags call taggatron#SetTags()