diff options
Diffstat (limited to 'autoload/xolox/easytags')
-rw-r--r-- | autoload/xolox/easytags/filetypes.vim | 122 | ||||
-rw-r--r-- | autoload/xolox/easytags/update.vim | 268 | ||||
-rw-r--r-- | autoload/xolox/easytags/utils.vim | 20 |
3 files changed, 410 insertions, 0 deletions
diff --git a/autoload/xolox/easytags/filetypes.vim b/autoload/xolox/easytags/filetypes.vim new file mode 100644 index 0000000..d655ef7 --- /dev/null +++ b/autoload/xolox/easytags/filetypes.vim @@ -0,0 +1,122 @@ +" Vim script +" Author: Peter Odding <peter@peterodding.com> +" Last Change: June 20, 2014 +" URL: http://peterodding.com/code/vim/easytags/ + +" This submodule of the vim-easytags plug-in translates between back and forth +" between Vim file types and Exuberant Ctags languages. This is complicated by +" a couple of things: +" +" - Vim allows file types to be combined like `filetype=c.doxygen'. +" +" - Some file types need to be canonicalized, for example the `htmldjango' +" Vim file type should be treated as the `html' Exuberant Ctags language. + +" Whether we've run Exuberant Ctags to discover the supported file types. +let s:discovered_filetypes = 0 + +" List of supported Vim file types. +let s:supported_filetypes = [] + +" Mapping of Exuberant Ctags languages to Vim file types and vice versa. +let g:xolox#easytags#filetypes#ctags_to_vim = {} +let g:xolox#easytags#filetypes#vim_to_ctags = {} + +" Mapping of Vim file types to canonical file types. +let s:canonical_filetypes = {} + +" Mapping of canonical Vim file types to their groups. +let s:filetype_groups = {} + +function! xolox#easytags#filetypes#add_group(...) " {{{1 + " Define a group of Vim file types whose tags should be stored together. + let canonical_filetype = tolower(a:1) + let s:filetype_groups[canonical_filetype] = a:000[1:] + for ft in s:filetype_groups[canonical_filetype] + let s:canonical_filetypes[tolower(ft)] = canonical_filetype + endfor +endfunction + +function! xolox#easytags#filetypes#add_mapping(vim_filetype, ctags_language) " {{{1 + " Map an Exuberant Ctags language to a Vim file type and vice versa. + let vim_filetype = tolower(a:vim_filetype) + let ctags_language = tolower(a:ctags_language) + let g:xolox#easytags#filetypes#ctags_to_vim[ctags_language] = vim_filetype + let g:xolox#easytags#filetypes#vim_to_ctags[vim_filetype] = ctags_language +endfunction + +function! xolox#easytags#filetypes#to_vim(ctags_language) " {{{1 + " Translate an Exuberant Ctags language to a Vim file type. + let ctags_language = tolower(a:ctags_language) + return get(g:xolox#easytags#filetypes#ctags_to_vim, ctags_language, ctags_language) +endfunction + +function! xolox#easytags#filetypes#to_ctags(vim_filetype) " {{{1 + " Translate a Vim file type to an Exuberant Ctags language. + let vim_filetype = tolower(a:vim_filetype) + return get(g:xolox#easytags#filetypes#vim_to_ctags, vim_filetype, vim_filetype) +endfunction + +function! xolox#easytags#filetypes#canonicalize(vim_filetype_value) " {{{1 + " Select a canonical, supported Vim file type given a value of &filetype. + call s:discover_supported_filetypes() + " Split the possibly combined Vim file type into individual file types. + for filetype in split(tolower(a:vim_filetype_value), '\.') + " Canonicalize the Vim file type. + let filetype = get(s:canonical_filetypes, filetype, filetype) + if index(s:supported_filetypes, filetype) >= 0 + return filetype + endif + endfor + return '' +endfunction + +function! xolox#easytags#filetypes#find_ctags_aliases(canonical_vim_filetype) " {{{1 + " Find Exuberant Ctags languages that correspond to a canonical, supported Vim file type. + if has_key(s:filetype_groups, a:canonical_vim_filetype) + let filetypes = copy(s:filetype_groups[a:canonical_vim_filetype]) + return map(filetypes, 'xolox#easytags#filetypes#to_ctags(v:val)') + else + return [xolox#easytags#filetypes#to_ctags(a:canonical_vim_filetype)] + endif +endfunction + +function! s:discover_supported_filetypes() " {{{1 + " Initialize predefined groups & mappings and discover supported file types. + if !s:discovered_filetypes + " Discover the file types supported by Exuberant Ctags? + if !empty(g:easytags_cmd) + let starttime = xolox#misc#timer#start() + let command = g:easytags_cmd . ' --list-languages' + for line in xolox#misc#os#exec({'command': command})['stdout'] + if line =~ '\[disabled\]$' + " Ignore languages that have been explicitly disabled using `--languages=-Vim'. + continue + elseif line =~ '^\w\S*$' + call add(s:supported_filetypes, xolox#easytags#filetypes#to_vim(xolox#misc#str#trim(line))) + elseif line =~ '\S' + call xolox#misc#msg#warn("easytags.vim %s: Failed to parse line of output from ctags --list-languages: %s", g:xolox#easytags#version, string(line)) + endif + endfor + let msg = "easytags.vim %s: Retrieved %i supported languages in %s." + call xolox#misc#timer#stop(msg, g:xolox#easytags#version, len(s:supported_filetypes), starttime) + endif + " Add file types supported by language specific programs. + call extend(s:supported_filetypes, keys(xolox#misc#option#get('easytags_languages', {}))) + " Don't run s:discover_supported_filetypes() more than once. + let s:discovered_filetypes = 1 + endif +endfunction + +" }}}1 + +" Define the default file type groups. +call xolox#easytags#filetypes#add_group('c', 'cpp', 'objc', 'objcpp') +call xolox#easytags#filetypes#add_group('html', 'htmldjango') + +" Define the default file type mappings. +call xolox#easytags#filetypes#add_mapping('cpp', 'c++') +call xolox#easytags#filetypes#add_mapping('cs', 'c#') +call xolox#easytags#filetypes#add_mapping(exists('g:filetype_asp') ? g:filetype_asp : 'aspvbs', 'asp') + +" vim: ts=2 sw=2 et diff --git a/autoload/xolox/easytags/update.vim b/autoload/xolox/easytags/update.vim new file mode 100644 index 0000000..eca86c3 --- /dev/null +++ b/autoload/xolox/easytags/update.vim @@ -0,0 +1,268 @@ +" Vim script +" Author: Peter Odding <peter@peterodding.com> +" Last Change: June 22, 2014 +" URL: http://peterodding.com/code/vim/easytags/ + +" This Vim auto-load script contains the parts of vim-easytags that are used +" to update tags files. The vim-easytags plug-in can run this code in one of +" two ways: +" +" - Synchronously inside your main Vim process, blocking your editing session +" during the tags file update (not very nice as your tags files get larger +" and updating them becomes slower). +" +" - Asynchronously in a separate Vim process to update a tags file in the +" background without blocking your editing session (this provides a much +" nicer user experience). +" +" This code is kept separate from the rest of the plug-in to force me to use +" simple form of communication (a Vim dictionary with all of the state +" required to update tags files) which in the future can be used to implement +" an alternative update mechanism in a faster scripting language (for example +" I could translate the Vim dictionary to JSON and feed it to Python). + +function! xolox#easytags#update#with_vim(params) " {{{1 + let counters = {} + let starttime = xolox#misc#timer#start() + call xolox#misc#msg#debug("easytags.vim %s: Executing %s.", g:xolox#easytags#version, a:params['command']) + let lines = xolox#misc#os#exec({'command': a:params['command']})['stdout'] + let entries = xolox#easytags#update#parse_entries(lines) + let counters['num_updated'] = len(entries) + let directory = get(a:params, 'directory', '') + let cache = s:create_cache() + if !empty(directory) + let counters['num_filtered'] = s:save_by_filetype(a:params['filter_tags'], [], entries, cache, directory) + else + let counters['num_filtered'] = s:filter_merge_tags(a:params['filter_tags'], a:params['tagsfile'], entries, cache) + endif + let counters['elapsed_time'] = xolox#misc#timer#convert(starttime) + return counters +endfunction + +function! xolox#easytags#update#convert_by_filetype(undo) " {{{1 + try + if empty(g:easytags_by_filetype) + throw "Please set g:easytags_by_filetype before running :TagsByFileType!" + endif + let global_tagsfile = expand(g:easytags_file) + let disabled_tagsfile = global_tagsfile . '.disabled' + if !a:undo + let [headers, entries] = xolox#easytags#update#read_tagsfile(global_tagsfile) + call s:save_by_filetype(0, headers, entries) + call rename(global_tagsfile, disabled_tagsfile) + let msg = "easytags.vim %s: Finished copying tags from %s to %s! Note that your old tags file has been renamed to %s instead of deleting it, should you want to restore it." + call xolox#misc#msg#info(msg, g:xolox#easytags#version, g:easytags_file, g:easytags_by_filetype, disabled_tagsfile) + else + let headers = [] + let all_entries = [] + for tagsfile in split(glob(g:easytags_by_filetype . '/*'), '\n') + let [headers, entries] = xolox#easytags#update#read_tagsfile(tagsfile) + call extend(all_entries, entries) + endfor + call xolox#easytags#update#write_tagsfile(global_tagsfile, headers, all_entries) + call xolox#misc#msg#info("easytags.vim %s: Finished copying tags from %s to %s!", g:xolox#easytags#version, g:easytags_by_filetype, g:easytags_file) + endif + catch + call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint) + endtry +endfunction + +function! s:filter_merge_tags(filter_tags, tagsfile, output, cache) " {{{1 + let [headers, entries] = xolox#easytags#update#read_tagsfile(a:tagsfile) + let tagged_files = s:find_tagged_files(a:output, a:cache) + if !empty(tagged_files) + call filter(entries, '!has_key(tagged_files, a:cache.canonicalize(v:val[1]))') + endif + " Filter tags for non-existing files? + let count_before_filter = len(entries) + if a:filter_tags + call filter(entries, 'a:cache.exists(v:val[1])') + endif + let num_filtered = count_before_filter - len(entries) + " Merge the old and new tags. + call extend(entries, a:output) + " Now we're ready to save the tags file. + if !xolox#easytags#update#write_tagsfile(a:tagsfile, headers, entries) + let msg = "Failed to write filtered tags file %s!" + throw printf(msg, fnamemodify(a:tagsfile, ':~')) + endif + return num_filtered +endfunction + +function! s:find_tagged_files(entries, cache) " {{{1 + let tagged_files = {} + for entry in a:entries + let filename = a:cache.canonicalize(entry[1]) + if filename != '' + if !has_key(tagged_files, filename) + let tagged_files[filename] = 1 + endif + endif + endfor + return tagged_files +endfunction + +function! s:save_by_filetype(filter_tags, headers, entries, cache, directory) " {{{1 + let filetypes = {} + let num_invalid = 0 + let num_filtered = 0 + for entry in a:entries + let ctags_ft = matchstr(entry[4], '^language:\zs\S\+$') + if empty(ctags_ft) + " TODO This triggers on entries where the pattern contains tabs. The interesting thing is that Vim reads these entries fine... Fix it in xolox#easytags#update#read_tagsfile()? + let num_invalid += 1 + if &vbs >= 1 + call xolox#misc#msg#debug("easytags.vim %s: Skipping tag without 'language:' field: %s", + \ g:xolox#easytags#version, string(entry)) + endif + else + let vim_ft = xolox#easytags#filetypes#to_vim(ctags_ft) + if !has_key(filetypes, vim_ft) + let filetypes[vim_ft] = [] + endif + call add(filetypes[vim_ft], entry) + endif + endfor + if num_invalid > 0 + call xolox#misc#msg#warn("easytags.vim %s: Skipped %i lines without 'language:' tag!", g:xolox#easytags#version, num_invalid) + endif + let directory = xolox#misc#path#absolute(a:directory) + for vim_ft in keys(filetypes) + let tagsfile = xolox#misc#path#merge(directory, vim_ft) + let existing = filereadable(tagsfile) + call xolox#misc#msg#debug("easytags.vim %s: Writing %s tags to %s tags file %s.", + \ g:xolox#easytags#version, len(filetypes[vim_ft]), + \ existing ? "existing" : "new", tagsfile) + if !existing + call xolox#easytags#update#write_tagsfile(tagsfile, a:headers, filetypes[vim_ft]) + else + let num_filtered += s:filter_merge_tags(a:filter_tags, tagsfile, filetypes[vim_ft], a:cache) + endif + endfor + return num_filtered +endfunction + +function! xolox#easytags#update#read_tagsfile(tagsfile) " {{{1 + " I'm not sure whether this is by design or an implementation detail but + " it's possible for the "!_TAG_FILE_SORTED" header to appear after one or + " more tags and Vim will apparently still use the header! For this reason + " the xolox#easytags#update#write_tagsfile() function should also recognize it, + " otherwise Vim might complain with "E432: Tags file not sorted". + let headers = [] + let entries = [] + let num_invalid = 0 + for line in readfile(a:tagsfile) + if line =~# '^!_TAG_' + call add(headers, line) + else + let entry = xolox#easytags#update#parse_entry(line) + if !empty(entry) + call add(entries, entry) + else + let num_invalid += 1 + endif + endif + endfor + if num_invalid > 0 + call xolox#misc#msg#warn("easytags.vim %s: Ignored %i invalid line(s) in %s!", g:xolox#easytags#version, num_invalid, a:tagsfile) + endif + return [headers, entries] +endfunction + +function! xolox#easytags#update#parse_entry(line) " {{{1 + let fields = split(a:line, '\t') + return len(fields) >= 3 ? fields : [] +endfunction + +function! xolox#easytags#update#parse_entries(lines) " {{{1 + call map(a:lines, 'xolox#easytags#update#parse_entry(v:val)') + return filter(a:lines, '!empty(v:val)') +endfunction + +function! xolox#easytags#update#write_tagsfile(tagsfile, headers, entries) " {{{1 + " This function always sorts the tags file but understands "foldcase". + let sort_order = 0 + let sort_header_present = 0 + let sort_header_pattern = '^!_TAG_FILE_SORTED\t\zs\d' + " Discover the sort order defined in the tags file headers. + let i = 0 + for line in a:headers + let match = matchstr(line, sort_header_pattern) + if !empty(match) + let sort_header_present = 1 + let sort_order = match + 0 + if sort_order == 0 + let sort_order = 2 + let a:headers[i] = substitute(line, sort_header_pattern, '2', '') + endif + endif + endfor + if !sort_header_present + " If no sorting is defined in the tags file headers we default to + " "foldcase" sorting and add the header. + let sort_order = 2 + call add(a:headers, "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/") + endif + call xolox#easytags#update#join_entries(a:entries) + if sort_order == 1 + call sort(a:entries) + else + call sort(a:entries, function('xolox#easytags#update#foldcase_compare')) + endif + let lines = [] + if xolox#misc#os#is_win() + " Exuberant Ctags on Windows requires \r\n but Vim's writefile() doesn't add them! + for line in a:headers + call add(lines, line . "\r") + endfor + for line in a:entries + call add(lines, line . "\r") + endfor + else + call extend(lines, a:headers) + call extend(lines, a:entries) + endif + let tempname = a:tagsfile . '.easytags.tmp' + return writefile(lines, tempname) == 0 && rename(tempname, a:tagsfile) == 0 +endfunction + +function! s:enumerate(list) + let items = [] + let index = 0 + for item in a:list + call add(items, [index, item]) + let index += 1 + endfor + return items +endfunction + +function! xolox#easytags#update#join_entry(value) " {{{1 + return type(a:value) == type([]) ? join(a:value, "\t") : a:value +endfunction + +function! xolox#easytags#update#join_entries(values) " {{{1 + call map(a:values, 'xolox#easytags#update#join_entry(v:val)') + return filter(a:values, '!empty(v:val)') +endfunction + +function! xolox#easytags#update#foldcase_compare(a, b) " {{{1 + let a = toupper(a:a) + let b = toupper(a:b) + return a == b ? 0 : a ># b ? 1 : -1 +endfunction + +function! s:create_cache() " {{{1 + let cache = {'canonicalize_cache': {}, 'exists_cache': {}} + function cache.canonicalize(pathname) dict + if !has_key(self, a:pathname) + let self[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname) + endif + return self[a:pathname] + endfunction + function cache.exists(pathname) dict + if !has_key(self, a:pathname) + let self[a:pathname] = filereadable(a:pathname) + endif + endfunction + return cache +endfunction diff --git a/autoload/xolox/easytags/utils.vim b/autoload/xolox/easytags/utils.vim new file mode 100644 index 0000000..a88c7a8 --- /dev/null +++ b/autoload/xolox/easytags/utils.vim @@ -0,0 +1,20 @@ +" Vim script +" Author: Peter Odding <peter@peterodding.com> +" Last Change: June 20, 2014 +" URL: http://peterodding.com/code/vim/easytags/ + +" Utility functions for vim-easytags. + +function! xolox#easytags#utils#canonicalize(pathname) + if !empty(a:pathname) + return xolox#misc#path#absolute(xolox#easytags#utils#resolve(a:pathname)) + endif + return a:pathname +endfunction + +function! xolox#easytags#utils#resolve(pathname) + if !empty(a:pathname) && xolox#misc#option#get('easytags_resolve_links', 0) + return resolve(a:pathname) + endif + return a:pathname +endfunction |