aboutsummaryrefslogtreecommitdiffstats
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/xolox/easytags.vim923
-rw-r--r--autoload/xolox/misc/README.md24
-rw-r--r--autoload/xolox/misc/buffer.vim37
-rw-r--r--autoload/xolox/misc/complete.vim18
-rw-r--r--autoload/xolox/misc/escape.vim46
-rw-r--r--autoload/xolox/misc/list.vim49
-rw-r--r--autoload/xolox/misc/msg.vim83
-rw-r--r--autoload/xolox/misc/open.vim70
-rw-r--r--autoload/xolox/misc/option.vim84
-rw-r--r--autoload/xolox/misc/os.vim12
-rw-r--r--autoload/xolox/misc/path.vim194
-rw-r--r--autoload/xolox/misc/str.vim12
-rw-r--r--autoload/xolox/misc/timer.vim85
13 files changed, 1637 insertions, 0 deletions
diff --git a/autoload/xolox/easytags.vim b/autoload/xolox/easytags.vim
new file mode 100644
index 0000000..02d8578
--- /dev/null
+++ b/autoload/xolox/easytags.vim
@@ -0,0 +1,923 @@
+" Vim script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: October 29, 2011
+" URL: http://peterodding.com/code/vim/easytags/
+
+let g:xolox#easytags#version = '2.7.2'
+
+" Public interface through (automatic) commands. {{{1
+
+function! xolox#easytags#register(global) " {{{2
+ " Parse the &tags option and get a list of all tags files *including
+ " non-existing files* (this is why we can't just call tagfiles()).
+ let tagfiles = xolox#misc#option#split_tags(&tags)
+ let expanded = map(copy(tagfiles), 'resolve(expand(v:val))')
+ " Add the filename to the &tags option when the user hasn't done so already.
+ let tagsfile = a:global ? g:easytags_file : xolox#easytags#get_tagsfile()
+ if index(expanded, xolox#misc#path#absolute(tagsfile)) == -1
+ " This is a real mess because of bugs in Vim?! :let &tags = '...' doesn't
+ " work on UNIX and Windows, :set tags=... doesn't work on Windows. What I
+ " mean with "doesn't work" is that tagfiles() == [] after the :let/:set
+ " command even though the tags file exists! One easy way to confirm that
+ " this is a bug in Vim is to type :set tags= then press <Tab> followed by
+ " <CR>. Now you entered the exact same value that the code below also did
+ " but suddenly Vim sees the tags file and tagfiles() != [] :-S
+ call add(tagfiles, tagsfile)
+ let value = xolox#misc#option#join_tags(tagfiles)
+ let cmd = (a:global ? 'set' : 'setl') . ' tags=' . escape(value, '\ ')
+ if xolox#misc#os#is_win() && v:version < 703
+ " TODO How to clear the expression from Vim's status line?
+ call feedkeys(":" . cmd . "|let &ro=&ro\<CR>", 'n')
+ else
+ execute cmd
+ endif
+ endif
+endfunction
+
+function! xolox#easytags#autoload(event) " {{{2
+ try
+ if a:event =~? 'cursorhold'
+ " Only for the CursorHold automatic command: check for unreasonable
+ " &updatetime values. The minimum value 4000 is kind of arbitrary
+ " (apart from being Vim's default) so I made it configurable:
+ let updatetime_min = xolox#misc#option#get('easytags_updatetime_min', 4000)
+ if &updatetime < updatetime_min
+ " Other plug-ins may lower &updatetime in certain contexts, e.g.
+ " insert mode in the case of the neocomplcache plug-in. The following
+ " option (disabled by default unless neocomplcache is loaded) silences
+ " the warning and makes the easytags plug-in skip the update and
+ " highlight. When the &updatetime is restored to a reasonable value
+ " the plug-in resumes.
+ if xolox#misc#option#get('easytags_updatetime_autodisable', exists('g:loaded_neocomplcache'))
+ return
+ else
+ call xolox#misc#msg#warn("easytags.vim %s: I'm being executed every %i milliseconds! Please :set updatetime=%i. To find where 'updatetime' was changed execute ':verb set ut?'", g:xolox#easytags#version, &updatetime, updatetime_min)
+ endif
+ endif
+ endif
+ let do_update = xolox#misc#option#get('easytags_auto_update', 1)
+ let do_highlight = xolox#misc#option#get('easytags_auto_highlight', 1) && &eventignore !~? '\<syntax\>'
+ " Don't execute this function for unsupported file types (doesn't load
+ " the list of file types if updates and highlighting are both disabled).
+ if (do_update || do_highlight) && index(xolox#easytags#supported_filetypes(), &ft) >= 0
+ " Update entries for current file in tags file?
+ if do_update
+ let pathname = s:resolve(expand('%:p'))
+ if pathname != ''
+ let tags_outdated = getftime(pathname) > getftime(xolox#easytags#get_tagsfile())
+ if tags_outdated || !xolox#easytags#file_has_tags(pathname)
+ call xolox#easytags#update(1, 0, [])
+ endif
+ endif
+ endif
+ " Apply highlighting of tags to current buffer?
+ if do_highlight
+ if !exists('b:easytags_last_highlighted')
+ call xolox#easytags#highlight()
+ else
+ for tagfile in tagfiles()
+ if getftime(tagfile) > b:easytags_last_highlighted
+ call xolox#easytags#highlight()
+ break
+ endif
+ endfor
+ endif
+ let b:easytags_last_highlighted = localtime()
+ endif
+ endif
+ catch
+ call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
+ endtry
+endfunction
+
+function! xolox#easytags#update(silent, filter_tags, filenames) " {{{2
+ try
+ let context = s:create_context()
+ let have_args = !empty(a:filenames)
+ let starttime = xolox#misc#timer#start()
+ let cfile = s:check_cfile(a:silent, a:filter_tags, have_args)
+ let tagsfile = xolox#easytags#get_tagsfile()
+ let firstrun = !filereadable(tagsfile)
+ let cmdline = s:prep_cmdline(cfile, tagsfile, firstrun, a:filenames, context)
+ let output = s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline)
+ if !firstrun
+ if have_args && !empty(g:easytags_by_filetype)
+ " TODO Get the headers from somewhere?!
+ call s:save_by_filetype(a:filter_tags, [], output, context)
+ else
+ let num_filtered = s:filter_merge_tags(a:filter_tags, tagsfile, output, context)
+ endif
+ if cfile != ''
+ let msg = "easytags.vim %s: Updated tags for %s in %s."
+ call xolox#misc#timer#stop(msg, g:xolox#easytags#version, expand('%:p:~'), starttime)
+ elseif have_args
+ let msg = "easytags.vim %s: Updated tags in %s."
+ call xolox#misc#timer#stop(msg, g:xolox#easytags#version, starttime)
+ else
+ let msg = "easytags.vim %s: Filtered %i invalid tags in %s."
+ call xolox#misc#timer#stop(msg, g:xolox#easytags#version, num_filtered, starttime)
+ endif
+ endif
+ " When :UpdateTags was executed manually we'll refresh the dynamic
+ " syntax highlighting so that new tags are immediately visible.
+ if !a:silent
+ HighlightTags
+ endif
+ return 1
+ catch
+ call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
+ endtry
+endfunction
+
+function! s:check_cfile(silent, filter_tags, have_args) " {{{3
+ if a:have_args
+ return ''
+ endif
+ let silent = a:silent || a:filter_tags
+ if xolox#misc#option#get('easytags_autorecurse', 0)
+ let cdir = s:resolve(expand('%:p:h'))
+ if !isdirectory(cdir)
+ if silent | return '' | endif
+ throw "The directory of the current file doesn't exist yet!"
+ endif
+ return cdir
+ endif
+ let cfile = s:resolve(expand('%:p'))
+ if cfile == '' || !filereadable(cfile)
+ if silent | return '' | endif
+ throw "You'll need to save your file before using :UpdateTags!"
+ elseif g:easytags_ignored_filetypes != '' && &ft =~ g:easytags_ignored_filetypes
+ if silent | return '' | endif
+ throw "The " . string(&ft) . " file type is explicitly ignored."
+ elseif index(xolox#easytags#supported_filetypes(), &ft) == -1
+ if silent | return '' | endif
+ throw "Exuberant Ctags doesn't support the " . string(&ft) . " file type!"
+ endif
+ return cfile
+endfunction
+
+function! s:prep_cmdline(cfile, tagsfile, firstrun, arguments, context) " {{{3
+ let program = xolox#misc#option#get('easytags_cmd')
+ let cmdline = [program, '--fields=+l', '--c-kinds=+p', '--c++-kinds=+p']
+ if a:firstrun
+ call add(cmdline, shellescape('-f' . a:tagsfile))
+ call add(cmdline, '--sort=' . (&ic ? 'foldcase' : 'yes'))
+ else
+ call add(cmdline, '--sort=no')
+ call add(cmdline, '-f-')
+ endif
+ if xolox#misc#option#get('easytags_include_members', 0)
+ call add(cmdline, '--extra=+q')
+ endif
+ let have_args = 0
+ if a:cfile != ''
+ if xolox#misc#option#get('easytags_autorecurse', 0)
+ call add(cmdline, '-R')
+ call add(cmdline, shellescape(a:cfile))
+ else
+ " TODO Should --language-force distinguish between C and C++?
+ " TODO --language-force doesn't make sense for JavaScript tags in HTML files?
+ let filetype = xolox#easytags#to_ctags_ft(&filetype)
+ call add(cmdline, shellescape('--language-force=' . filetype))
+ call add(cmdline, shellescape(a:cfile))
+ endif
+ let have_args = 1
+ else
+ for arg in a:arguments
+ if arg =~ '^-'
+ call add(cmdline, arg)
+ let have_args = 1
+ else
+ let matches = split(expand(arg), "\n")
+ if !empty(matches)
+ call map(matches, 'shellescape(s:canonicalize(v:val, a:context))')
+ call extend(cmdline, matches)
+ let have_args = 1
+ endif
+ endif
+ endfor
+ endif
+ " No need to run Exuberant Ctags without any filename arguments!
+ return have_args ? join(cmdline) : ''
+endfunction
+
+function! s:run_ctags(starttime, cfile, tagsfile, firstrun, cmdline) " {{{3
+ let lines = []
+ if a:cmdline != ''
+ call xolox#misc#msg#debug("easytags.vim %s: Executing %s.", g:xolox#easytags#version, a:cmdline)
+ try
+ let lines = xolox#shell#execute(a:cmdline, 1)
+ catch /^Vim\%((\a\+)\)\=:E117/
+ " Ignore missing shell.vim plug-in.
+ let output = system(a:cmdline)
+ if v:shell_error
+ let msg = "Failed to update tags file %s: %s!"
+ throw printf(msg, fnamemodify(a:tagsfile, ':~'), strtrans(output))
+ endif
+ let lines = split(output, "\n")
+ endtry
+ if a:firstrun
+ if a:cfile != ''
+ call xolox#misc#timer#stop("easytags.vim %s: Created tags for %s in %s.", g:xolox#easytags#version, expand('%:p:~'), a:starttime)
+ else
+ call xolox#misc#timer#stop("easytags.vim %s: Created tags in %s.", g:xolox#easytags#version, a:starttime)
+ endif
+ return []
+ endif
+ endif
+ return xolox#easytags#parse_entries(lines)
+endfunction
+
+function! s:filter_merge_tags(filter_tags, tagsfile, output, context) " {{{3
+ let [headers, entries] = xolox#easytags#read_tagsfile(a:tagsfile)
+ let filters = []
+ " Filter old tags that are to be replaced with the tags in {output}.
+ let tagged_files = s:find_tagged_files(a:output, a:context)
+ if !empty(tagged_files)
+ call add(filters, '!has_key(tagged_files, s:canonicalize(v:val[1], a:context))')
+ endif
+ " Filter tags for non-existing files?
+ if a:filter_tags
+ call add(filters, 'filereadable(v:val[1])')
+ endif
+ let num_old_entries = len(entries)
+ if !empty(filters)
+ " Apply the filters.
+ call filter(entries, join(filters, ' && '))
+ endif
+ let num_filtered = num_old_entries - len(entries)
+ " Merge old/new tags and write tags file.
+ call extend(entries, a:output)
+ if !xolox#easytags#write_tagsfile(a:tagsfile, headers, entries)
+ let msg = "Failed to write filtered tags file %s!"
+ throw printf(msg, fnamemodify(a:tagsfile, ':~'))
+ endif
+ " We've already read the tags file, might as well cache the tagged files :-)
+ let fname = s:canonicalize(a:tagsfile, a:context)
+ call s:cache_tagged_files_in(fname, getftime(fname), entries, a:context)
+ return num_filtered
+endfunction
+
+function! s:find_tagged_files(entries, context) " {{{3
+ let tagged_files = {}
+ for entry in a:entries
+ let filename = s:canonicalize(entry[1], a:context)
+ if filename != ''
+ if !has_key(tagged_files, filename)
+ let tagged_files[filename] = 1
+ endif
+ endif
+ endfor
+ return tagged_files
+endfunction
+
+function! xolox#easytags#highlight() " {{{2
+ try
+ " Treat C++ and Objective-C as plain C.
+ let filetype = get(s:canonical_aliases, &ft, &ft)
+ let tagkinds = get(s:tagkinds, filetype, [])
+ if exists('g:syntax_on') && !empty(tagkinds) && !exists('b:easytags_nohl')
+ let starttime = xolox#misc#timer#start()
+ let used_python = 0
+ for tagkind in tagkinds
+ let hlgroup_tagged = tagkind.hlgroup . 'Tag'
+ " Define style on first run, clear highlighting on later runs.
+ if !hlexists(hlgroup_tagged)
+ execute 'highlight def link' hlgroup_tagged tagkind.hlgroup
+ else
+ execute 'syntax clear' hlgroup_tagged
+ endif
+ " Try to perform the highlighting using the fast Python script.
+ " TODO The tags files are read multiple times by the Python script
+ " within one run of xolox#easytags#highlight()
+ if s:highlight_with_python(hlgroup_tagged, tagkind)
+ let used_python = 1
+ else
+ " Fall back to the slow and naive Vim script implementation.
+ if !exists('taglist')
+ " Get the list of tags when we need it and remember the results.
+ if !has_key(s:aliases, filetype)
+ let ctags_filetype = xolox#easytags#to_ctags_ft(filetype)
+ let taglist = filter(taglist('.'), "get(v:val, 'language', '') ==? ctags_filetype")
+ else
+ let aliases = s:aliases[&ft]
+ let taglist = filter(taglist('.'), "has_key(aliases, tolower(get(v:val, 'language', '')))")
+ endif
+ endif
+ " Filter a copy of the list of tags to the relevant kinds.
+ if has_key(tagkind, 'tagkinds')
+ let filter = 'v:val.kind =~ tagkind.tagkinds'
+ else
+ let filter = tagkind.vim_filter
+ endif
+ let matches = filter(copy(taglist), filter)
+ if matches != []
+ " Convert matched tags to :syntax command and execute it.
+ call map(matches, 'xolox#misc#escape#pattern(get(v:val, "name"))')
+ let pattern = tagkind.pattern_prefix . '\%(' . join(xolox#misc#list#unique(matches), '\|') . '\)' . tagkind.pattern_suffix
+ let template = 'syntax match %s /%s/ containedin=ALLBUT,%s'
+ let command = printf(template, hlgroup_tagged, escape(pattern, '/'), xolox#misc#option#get('easytags_ignored_syntax_groups'))
+ call xolox#misc#msg#debug("easytags.vim %s: Executing command '%s'.", g:xolox#easytags#version, command)
+ try
+ execute command
+ catch /^Vim\%((\a\+)\)\=:E339/
+ let msg = "easytags.vim %s: Failed to highlight %i %s tags because pattern is too big! (%i KB)"
+ call xolox#misc#msg#warn(msg, g:xolox#easytags#version, len(matches), tagkind.hlgroup, len(pattern) / 1024)
+ endtry
+ endif
+ endif
+ endfor
+ redraw
+ let bufname = expand('%:p:~')
+ if bufname == ''
+ let bufname = 'unnamed buffer #' . bufnr('%')
+ endif
+ let msg = "easytags.vim %s: Highlighted tags in %s in %s%s."
+ call xolox#misc#timer#stop(msg, g:xolox#easytags#version, bufname, starttime, used_python ? " (using Python)" : "")
+ return 1
+ endif
+ catch
+ call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
+ endtry
+endfunction
+
+function! xolox#easytags#by_filetype(undo) " {{{2
+ try
+ if empty(g:easytags_by_filetype)
+ throw "Please set g:easytags_by_filetype before running :TagsByFileType!"
+ endif
+ let context = s:create_context()
+ let global_tagsfile = expand(g:easytags_file)
+ let disabled_tagsfile = global_tagsfile . '.disabled'
+ if !a:undo
+ let [headers, entries] = xolox#easytags#read_tagsfile(global_tagsfile)
+ call s:save_by_filetype(0, headers, entries, context)
+ 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#read_tagsfile(tagsfile)
+ call extend(all_entries, entries)
+ endfor
+ call xolox#easytags#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:save_by_filetype(filter_tags, headers, entries, context)
+ let filetypes = {}
+ for entry in a:entries
+ let ctags_ft = matchstr(entry[2], '\tlanguage:\zs\S\+')
+ if !empty(ctags_ft)
+ let vim_ft = xolox#easytags#to_vim_ft(ctags_ft)
+ if !has_key(filetypes, vim_ft)
+ let filetypes[vim_ft] = []
+ endif
+ call add(filetypes[vim_ft], entry)
+ endif
+ endfor
+ let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
+ for vim_ft in keys(filetypes)
+ let tagsfile = xolox#misc#path#merge(directory, vim_ft)
+ if !filereadable(tagsfile)
+ call xolox#easytags#write_tagsfile(tagsfile, a:headers, filetypes[vim_ft])
+ else
+ call s:filter_merge_tags(a:filter_tags, tagsfile, filetypes[vim_ft], a:context)
+ endif
+ endfor
+endfunction
+
+" Public supporting functions (might be useful to others). {{{1
+
+function! xolox#easytags#supported_filetypes() " {{{2
+ if !exists('s:supported_filetypes')
+ let starttime = xolox#misc#timer#start()
+ let command = g:easytags_cmd . ' --list-languages'
+ try
+ let listing = xolox#shell#execute(command, 1)
+ catch /^Vim\%((\a\+)\)\=:E117/
+ " Ignore missing shell.vim plug-in.
+ let listing = split(system(command), "\n")
+ if v:shell_error
+ let msg = "Failed to get supported languages! (output: %s)"
+ throw printf(msg, strtrans(join(listing, "\n")))
+ endif
+ endtry
+ let s:supported_filetypes = map(copy(listing), 's:check_filetype(listing, v:val)')
+ 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
+ return s:supported_filetypes
+endfunction
+
+function! s:check_filetype(listing, cline)
+ if a:cline !~ '^\w\S*$'
+ let msg = "Failed to get supported languages! (output: %s)"
+ throw printf(msg, strtrans(join(a:listing, "\n")))
+ endif
+ return xolox#easytags#to_vim_ft(a:cline)
+endfunction
+
+function! xolox#easytags#read_tagsfile(tagsfile) " {{{2
+ " 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#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#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#parse_entry(line) " {{{2
+ let fields = split(a:line, '\t')
+ return len(fields) >= 3 ? fields : []
+endfunction
+
+function! xolox#easytags#parse_entries(lines) " {{{2
+ call map(a:lines, 'xolox#easytags#parse_entry(v:val)')
+ return filter(a:lines, '!empty(v:val)')
+endfunction
+
+function! xolox#easytags#write_tagsfile(tagsfile, headers, entries) " {{{2
+ " This function always sorts the tags file but understands "foldcase".
+ let sort_order = 1
+ for line in a:headers
+ if match(line, '^!_TAG_FILE_SORTED\t2') == 0
+ let sort_order = 2
+ endif
+ endfor
+ call map(a:entries, 's:join_entry(v:val)')
+ if sort_order == 1
+ call sort(a:entries)
+ else
+ call sort(a:entries, 1)
+ 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:join_entry(value)
+ return type(a:value) == type([]) ? join(a:value, "\t") : a:value
+endfunction
+
+function! xolox#easytags#file_has_tags(filename) " {{{2
+ " Check whether the given source file occurs in one of the tags files known
+ " to Vim. This function might not always give the right answer because of
+ " caching, but for the intended purpose that's no problem: When editing an
+ " existing file which has no tags defined the plug-in will run Exuberant
+ " Ctags to update the tags, *unless the file has already been tagged*.
+ call s:cache_tagged_files(s:create_context())
+ return has_key(s:tagged_files, s:resolve(a:filename))
+endfunction
+
+if !exists('s:tagged_files')
+ let s:tagged_files = {}
+ let s:known_tagfiles = {}
+endif
+
+function! s:cache_tagged_files(context) " {{{3
+ if empty(s:tagged_files)
+ " Initialize the cache of tagged files on first use. After initialization
+ " we'll only update the cache when we're reading a tags file from disk for
+ " other purposes anyway (so the cache doesn't introduce too much overhead).
+ let starttime = xolox#misc#timer#start()
+ for tagsfile in tagfiles()
+ if !filereadable(tagsfile)
+ call xolox#misc#msg#warn("easytags.vim %s: Skipping unreadable tags file %s!", g:xolox#easytags#version, tagsfile)
+ else
+ let fname = s:canonicalize(tagsfile, a:context)
+ let ftime = getftime(fname)
+ if get(s:known_tagfiles, fname, 0) != ftime
+ let [headers, entries] = xolox#easytags#read_tagsfile(fname)
+ call s:cache_tagged_files_in(fname, ftime, entries, a:context)
+ endif
+ endif
+ endfor
+ call xolox#misc#timer#stop("easytags.vim %s: Initialized cache of tagged files in %s.", g:xolox#easytags#version, starttime)
+ endif
+endfunction
+
+function! s:cache_tagged_files_in(fname, ftime, entries, context) " {{{3
+ for entry in a:entries
+ let filename = s:canonicalize(entry[1], a:context)
+ if filename != ''
+ let s:tagged_files[filename] = 1
+ endif
+ endfor
+ let s:known_tagfiles[a:fname] = a:ftime
+endfunction
+
+function! xolox#easytags#get_tagsfile() " {{{2
+ let tagsfile = ''
+ " Look for a suitable project specific tags file?
+ let dynamic_files = xolox#misc#option#get('easytags_dynamic_files', 0)
+ if dynamic_files == 1
+ let tagsfile = get(tagfiles(), 0, '')
+ elseif dynamic_files == 2
+ let tagsfile = xolox#misc#option#eval_tags(&tags, 1)
+ endif
+ " Check if a file type specific tags file is useful?
+ if empty(tagsfile) && !empty(g:easytags_by_filetype) && index(xolox#easytags#supported_filetypes(), &ft) >= 0
+ let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
+ let tagsfile = xolox#misc#path#merge(directory, &filetype)
+ endif
+ " Default to the global tags file?
+ if empty(tagsfile)
+ let tagsfile = expand(xolox#misc#option#get('easytags_file'))
+ endif
+ " If the tags file exists, make sure it is writable!
+ if filereadable(tagsfile) && filewritable(tagsfile) != 1
+ let message = "The tags file %s isn't writable!"
+ throw printf(message, fnamemodify(tagsfile, ':~'))
+ endif
+ return tagsfile
+endfunction
+
+" Public API for definition of file type specific dynamic syntax highlighting. {{{1
+
+function! xolox#easytags#define_tagkind(object) " {{{2
+ if !has_key(a:object, 'pattern_prefix')
+ let a:object.pattern_prefix = '\C\<'
+ endif
+ if !has_key(a:object, 'pattern_suffix')
+ let a:object.pattern_suffix = '\>'
+ endif
+ if !has_key(s:tagkinds, a:object.filetype)
+ let s:tagkinds[a:object.filetype] = []
+ endif
+ call add(s:tagkinds[a:object.filetype], a:object)
+endfunction
+
+function! xolox#easytags#map_filetypes(vim_ft, ctags_ft) " {{{2
+ call add(s:vim_filetypes, a:vim_ft)
+ call add(s:ctags_filetypes, a:ctags_ft)
+endfunction
+
+function! xolox#easytags#alias_filetypes(...) " {{{2
+ " TODO Simplify alias handling, this much complexity really isn't needed!
+ for type in a:000
+ let s:canonical_aliases[type] = a:1
+ if !has_key(s:aliases, type)
+ let s:aliases[type] = {}
+ endif
+ endfor
+ for i in range(a:0)
+ for j in range(a:0)
+ let vimft1 = a:000[i]
+ let ctagsft1 = xolox#easytags#to_ctags_ft(vimft1)
+ let vimft2 = a:000[j]
+ let ctagsft2 = xolox#easytags#to_ctags_ft(vimft2)
+ if !has_key(s:aliases[vimft1], ctagsft2)
+ let s:aliases[vimft1][ctagsft2] = 1
+ endif
+ if !has_key(s:aliases[vimft2], ctagsft1)
+ let s:aliases[vimft2][ctagsft1] = 1
+ endif
+ endfor
+ endfor
+endfunction
+
+function! xolox#easytags#to_vim_ft(ctags_ft) " {{{2
+ let type = tolower(a:ctags_ft)
+ let index = index(s:ctags_filetypes, type)
+ return index >= 0 ? s:vim_filetypes[index] : type
+endfunction
+
+function! xolox#easytags#to_ctags_ft(vim_ft) " {{{2
+ let type = tolower(a:vim_ft)
+ let index = index(s:vim_filetypes, type)
+ return index >= 0 ? s:ctags_filetypes[index] : type
+endfunction
+
+" Miscellaneous script-local functions. {{{1
+
+function! s:create_context() " {{{2
+ return {'cache': {}}
+endfunction
+
+function! s:resolve(filename) " {{{2
+ if xolox#misc#option#get('easytags_resolve_links', 0)
+ return resolve(a:filename)
+ else
+ return a:filename
+ endif
+endfunction
+
+function! s:canonicalize(filename, context) " {{{2
+ if a:filename != ''
+ if has_key(a:context.cache, a:filename)
+ return a:context.cache[a:filename]
+ else
+ let canonical = s:resolve(fnamemodify(a:filename, ':p'))
+ let a:context.cache[a:filename] = canonical
+ return canonical
+ endif
+ endif
+ return ''
+endfunction
+
+function! s:python_available() " {{{2
+ if !exists('s:is_python_available')
+ try
+ execute 'pyfile' fnameescape(g:easytags_python_script)
+ redir => output
+ silent python easytags_ping()
+ redir END
+ let s:is_python_available = (output =~ 'it works!')
+ catch
+ let s:is_python_available = 0
+ endtry
+ endif
+ return s:is_python_available
+endfunction
+
+function! s:highlight_with_python(syntax_group, tagkind) " {{{2
+ if xolox#misc#option#get('easytags_python_enabled', 1) && s:python_available()
+ " Gather arguments for Python function.
+ let context = {}
+ let context['tagsfiles'] = tagfiles()
+ let context['syntaxgroup'] = a:syntax_group
+ let context['filetype'] = xolox#easytags#to_ctags_ft(&ft)
+ let context['tagkinds'] = get(a:tagkind, 'tagkinds', '')
+ let context['prefix'] = get(a:tagkind, 'pattern_prefix', '')
+ let context['suffix'] = get(a:tagkind, 'pattern_suffix', '')
+ let context['filters'] = get(a:tagkind, 'python_filter', {})
+ let context['ignoresyntax'] = xolox#misc#option#get('easytags_ignored_syntax_groups')
+ " Call the Python function and intercept the output.
+ try
+ redir => commands
+ python import vim
+ silent python print easytags_gensyncmd(**vim.eval('context'))
+ redir END
+ execute commands
+ return 1
+ catch
+ redir END
+ " If the Python script raised an error, don't run it again.
+ let g:easytags_python_enabled = 0
+ endtry
+ endif
+ return 0
+endfunction
+
+" Built-in file type & tag kind definitions. {{{1
+
+" Don't bother redefining everything below when this script is sourced again.
+if exists('s:tagkinds')
+ finish
+endif
+
+let s:tagkinds = {}
+
+" Define the built-in Vim <=> Ctags file-type mappings.
+let s:vim_filetypes = []
+let s:ctags_filetypes = []
+call xolox#easytags#map_filetypes('cpp', 'c++')
+call xolox#easytags#map_filetypes('cs', 'c#')
+call xolox#easytags#map_filetypes(exists('g:filetype_asp') ? g:filetype_asp : 'aspvbs', 'asp')
+
+" Define the Vim file-types that are aliased by default.
+let s:aliases = {}
+let s:canonical_aliases = {}
+call xolox#easytags#alias_filetypes('c', 'cpp', 'objc', 'objcpp')
+
+" Enable line continuation.
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Lua. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'lua',
+ \ 'hlgroup': 'luaFunc',
+ \ 'tagkinds': 'f'})
+
+" C. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'c',
+ \ 'hlgroup': 'cType',
+ \ 'tagkinds': '[cgstu]'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'c',
+ \ 'hlgroup': 'cEnum',
+ \ 'tagkinds': 'e'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'c',
+ \ 'hlgroup': 'cPreProc',
+ \ 'tagkinds': 'd'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'c',
+ \ 'hlgroup': 'cFunction',
+ \ 'tagkinds': '[fp]'})
+
+highlight def link cEnum Identifier
+highlight def link cFunction Function
+
+if xolox#misc#option#get('easytags_include_members', 0)
+ call xolox#easytags#define_tagkind({
+ \ 'filetype': 'c',
+ \ 'hlgroup': 'cMember',
+ \ 'tagkinds': 'm'})
+ highlight def link cMember Identifier
+endif
+
+" PHP. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'php',
+ \ 'hlgroup': 'phpFunctions',
+ \ 'tagkinds': 'f',
+ \ 'pattern_suffix': '(\@='})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'php',
+ \ 'hlgroup': 'phpClasses',
+ \ 'tagkinds': 'c'})
+
+" Vim script. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'vim',
+ \ 'hlgroup': 'vimAutoGroup',
+ \ 'tagkinds': 'a'})
+
+highlight def link vimAutoGroup vimAutoEvent
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'vim',
+ \ 'hlgroup': 'vimCommand',
+ \ 'tagkinds': 'c',
+ \ 'pattern_prefix': '\(\(^\|\s\):\?\)\@<=',
+ \ 'pattern_suffix': '\(!\?\(\s\|$\)\)\@='})
+
+" Exuberant Ctags doesn't mark script local functions in Vim scripts as
+" "static". When your tags file contains search patterns this plug-in can use
+" those search patterns to check which Vim script functions are defined
+" globally and which script local.
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'vim',
+ \ 'hlgroup': 'vimFuncName',
+ \ 'vim_filter': 'v:val.kind ==# "f" && get(v:val, "cmd", "") !~? ''<sid>\w\|\<s:\w''',
+ \ 'python_filter': { 'kind': 'f', 'nomatch': '(?i)(<sid>\w|\bs:\w)' },
+ \ 'pattern_prefix': '\C\%(\<s:\|<[sS][iI][dD]>\)\@<!\<'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'vim',
+ \ 'hlgroup': 'vimScriptFuncName',
+ \ 'vim_filter': 'v:val.kind ==# "f" && get(v:val, "cmd", "") =~? ''<sid>\w\|\<s:\w''',
+ \ 'python_filter': { 'kind': 'f', 'match': '(?i)(<sid>\w|\bs:\w)' },
+ \ 'pattern_prefix': '\C\%(\<s:\|<[sS][iI][dD]>\)'})
+
+highlight def link vimScriptFuncName vimFuncName
+
+" Python. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'python',
+ \ 'hlgroup': 'pythonFunction',
+ \ 'tagkinds': 'f',
+ \ 'pattern_prefix': '\%(\<def\s\+\)\@<!\<'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'python',
+ \ 'hlgroup': 'pythonMethod',
+ \ 'tagkinds': 'm',
+ \ 'pattern_prefix': '\.\@<='})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'python',
+ \ 'hlgroup': 'pythonClass',
+ \ 'tagkinds': 'c'})
+
+highlight def link pythonMethodTag pythonFunction
+highlight def link pythonClassTag pythonFunction
+
+" Java. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'java',
+ \ 'hlgroup': 'javaClass',
+ \ 'tagkinds': 'c'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'java',
+ \ 'hlgroup': 'javaMethod',
+ \ 'tagkinds': 'm'})
+
+highlight def link javaClass Identifier
+highlight def link javaMethod Function
+
+" C#. {{{2
+
+" TODO C# name spaces, interface names, enumeration member names, structure names?
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'cs',
+ \ 'hlgroup': 'csClassOrStruct',
+ \ 'tagkinds': 'c'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'cs',
+ \ 'hlgroup': 'csMethod',
+ \ 'tagkinds': '[ms]'})
+
+highlight def link csClassOrStruct Identifier
+highlight def link csMethod Function
+
+" Ruby. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'ruby',
+ \ 'hlgroup': 'rubyModuleName',
+ \ 'tagkinds': 'm'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'ruby',
+ \ 'hlgroup': 'rubyClassName',
+ \ 'tagkinds': 'c'})
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'ruby',
+ \ 'hlgroup': 'rubyMethodName',
+ \ 'tagkinds': '[fF]'})
+
+highlight def link rubyModuleName Type
+highlight def link rubyClassName Type
+highlight def link rubyMethodName Function
+
+" Awk. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'awk',
+ \ 'hlgroup': 'awkFunctionTag',
+ \ 'tagkinds': 'f'})
+
+highlight def link awkFunctionTag Function
+
+" Shell. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'sh',
+ \ 'hlgroup': 'shFunctionTag',
+ \ 'tagkinds': 'f',
+ \ 'pattern_suffix': '\(\s*()\)\@!'})
+
+highlight def link shFunctionTag Operator
+
+" Tcl. {{{2
+
+call xolox#easytags#define_tagkind({
+ \ 'filetype': 'tcl',
+ \ 'hlgroup': 'tclCommandTag',
+ \ 'tagkinds': 'p'})
+
+highlight def link tclCommandTag Operator
+
+" }}}
+
+" Restore "cpoptions".
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/README.md b/autoload/xolox/misc/README.md
new file mode 100644
index 0000000..9111126
--- /dev/null
+++ b/autoload/xolox/misc/README.md
@@ -0,0 +1,24 @@
+# Miscellaneous auto-load Vim scripts
+
+The git repository at <http://github.com/xolox/vim-misc> contains Vim scripts that are used by most of the [Vim plug-ins I've written] [plugins] yet don't really belong with any single one. I include this repository as a subdirectory of my plug-in repositories using the following commands:
+
+ $ git remote add -f vim-misc https://github.com/xolox/vim-misc.git
+ $ git merge -s ours --no-commit vim-misc/master
+ $ git read-tree --prefix=autoload/xolox/misc/ -u vim-misc/master
+ $ git commit -m "Merge vim-misc repository as subdirectory"
+
+To update a plug-in repository to the latest versions of the miscellaneous auto-load scripts I execute the following command:
+
+ $ git pull -s subtree vim-misc master
+
+## Contact
+
+If you have questions, bug reports, suggestions, etc. the author can be contacted at <peter@peterodding.com>. The latest version is available at <http://peterodding.com/code/vim/misc> and <http://github.com/xolox/vim-misc>.
+
+## License
+
+This software is licensed under the [MIT license](http://en.wikipedia.org/wiki/MIT_License).
+© 2011 Peter Odding &lt;<peter@peterodding.com>&gt;.
+
+
+[plugins]: http://peterodding.com/code/vim/
diff --git a/autoload/xolox/misc/buffer.vim b/autoload/xolox/misc/buffer.vim
new file mode 100644
index 0000000..e4472e6
--- /dev/null
+++ b/autoload/xolox/misc/buffer.vim
@@ -0,0 +1,37 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: September 4, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+function! xolox#misc#buffer#is_empty()
+ " Check if the current buffer is an empty, unchanged buffer which can be reused.
+ return !&modified && expand('%') == '' && line('$') <= 1 && getline(1) == ''
+endfunction
+
+function! xolox#misc#buffer#prepare(bufname)
+ let bufname = '[' . a:bufname . ']'
+ let buffers = tabpagebuflist()
+ call map(buffers, 'fnamemodify(bufname(v:val), ":t:r")')
+ let idx = index(buffers, bufname)
+ if idx >= 0
+ execute (idx + 1) . 'wincmd w'
+ elseif !(xolox#misc#buffer#is_empty() || expand('%:t') == bufname)
+ vsplit
+ endif
+ silent execute 'edit' fnameescape(bufname)
+ lcd " clear working directory
+ setlocal buftype=nofile bufhidden=hide noswapfile
+ let &l:statusline = bufname
+ call xolox#misc#buffer#unlock()
+ silent %delete
+endfunction
+
+function! xolox#misc#buffer#lock()
+ " Lock a special buffer so it can no longer be edited.
+ setlocal readonly nomodifiable nomodified
+endfunction
+
+function! xolox#misc#buffer#unlock()
+ " Unlock a special buffer so that its content can be updated.
+ setlocal noreadonly modifiable
+endfunction
diff --git a/autoload/xolox/misc/complete.vim b/autoload/xolox/misc/complete.vim
new file mode 100644
index 0000000..2ada676
--- /dev/null
+++ b/autoload/xolox/misc/complete.vim
@@ -0,0 +1,18 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: March 15, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+" Keyword completion from the current buffer for user defined commands.
+
+function! xolox#misc#complete#keywords(arglead, cmdline, cursorpos)
+ let words = {}
+ for line in getline(1, '$')
+ for word in split(line, '\W\+')
+ let words[word] = 1
+ endfor
+ endfor
+ return sort(keys(filter(words, 'v:key =~# a:arglead')))
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/escape.vim b/autoload/xolox/misc/escape.vim
new file mode 100644
index 0000000..1dd1838
--- /dev/null
+++ b/autoload/xolox/misc/escape.vim
@@ -0,0 +1,46 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: November 21, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+" Convert a string into a :substitute pattern that matches the string literally.
+
+function! xolox#misc#escape#pattern(string)
+ if type(a:string) == type('')
+ let string = escape(a:string, '^$.*\~[]')
+ return substitute(string, '\n', '\\n', 'g')
+ endif
+ return ''
+endfunction
+
+" Convert a string into a :substitute replacement that inserts the string literally.
+
+function! xolox#misc#escape#substitute(string)
+ if type(a:string) == type('')
+ let string = escape(a:string, '\&~%')
+ return substitute(string, '\n', '\\r', 'g')
+ endif
+ return ''
+endfunction
+
+" Convert a string into a quoted command line argument. I was going to add a
+" long rant here about &shellslash, but really, it won't make any difference.
+" Let's just suffice to say that I have yet to encounter a single person out
+" there who uses this option for its intended purpose (running a UNIX-style
+" shell on Windows).
+
+function! xolox#misc#escape#shell(string)
+ if xolox#misc#os#is_win()
+ try
+ let ssl_save = &shellslash
+ set noshellslash
+ return shellescape(a:string)
+ finally
+ let &shellslash = ssl_save
+ endtry
+ else
+ return shellescape(a:string)
+ endif
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/list.vim b/autoload/xolox/misc/list.vim
new file mode 100644
index 0000000..ee243d4
--- /dev/null
+++ b/autoload/xolox/misc/list.vim
@@ -0,0 +1,49 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: August 31, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+" Remove duplicate values from {list} in-place (preserves order).
+
+function! xolox#misc#list#unique(list)
+ call reverse(a:list)
+ call filter(a:list, 'count(a:list, v:val) == 1')
+ return reverse(a:list)
+endfunction
+
+" Binary insertion (more efficient than calling sort() after each insertion).
+
+function! xolox#misc#list#binsert(list, value, ...)
+ let idx = s:binsert_r(a:list, 0, len(a:list), a:value, exists('a:1') && a:1)
+ return insert(a:list, a:value, idx)
+endfunction
+
+function! s:binsert_r(list, low, high, value, ignorecase)
+ let mid = a:low + (a:high - a:low) / 2
+ if a:low == a:high
+ return a:low
+ elseif a:ignorecase ? a:value >? a:list[mid] : a:value > a:list[mid]
+ return s:binsert_r(a:list, mid + 1, a:high, a:value, a:ignorecase)
+ elseif a:ignorecase ? a:value <? a:list[mid] : a:value < a:list[mid]
+ return s:binsert_r(a:list, a:low, mid, a:value, a:ignorecase)
+ else
+ return mid
+ endif
+endfunction
+
+if 0
+ " Tests for xolox#misc#list#binsert().
+ let s:list = ['a', 'B', 'e']
+ function! s:test(value, expected)
+ call xolox#misc#list#binsert(s:list, a:value, 1)
+ if s:list != a:expected
+ call xolox#misc#msg#warn("list.vim: Test failed! Expected %s, got %s",
+ \ string(a:expected), string(s:list))
+ endif
+ endfunction
+ call s:test('c', ['a', 'B', 'c', 'e'])
+ call s:test('D', ['a', 'B', 'c', 'D', 'e'])
+ call s:test('f', ['a', 'B', 'c', 'D', 'e', 'f'])
+endif
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/msg.vim b/autoload/xolox/misc/msg.vim
new file mode 100644
index 0000000..9ba2b7c
--- /dev/null
+++ b/autoload/xolox/misc/msg.vim
@@ -0,0 +1,83 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: March 15, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+if !exists('g:xolox_message_buffer')
+ " For when I lose my :messages history :-\
+ let g:xolox_message_buffer = 100
+endif
+
+if !exists('g:xolox_messages')
+ let g:xolox_messages = []
+endif
+
+" Show a formatted informational message to the user.
+
+function! xolox#misc#msg#info(...)
+ call s:show_message('title', a:000)
+endfunction
+
+" Show a formatted warning message to the user.
+
+function! xolox#misc#msg#warn(...)
+ call s:show_message('warningmsg', a:000)
+endfunction
+
+" Show a formatted debugging message to the user?
+
+function! xolox#misc#msg#debug(...)
+ if &vbs >= 1
+ call s:show_message('question', a:000)
+ endif
+endfunction
+
+" The implementation of info() and warn().
+
+function! s:show_message(hlgroup, args)
+ let nargs = len(a:args)
+ if nargs == 1
+ let message = a:args[0]
+ elseif nargs >= 2
+ let message = call('printf', a:args)
+ endif
+ if exists('message')
+ try
+ " Temporarily disable Vim's |hit-enter| prompt and mode display.
+ if !exists('s:more_save')
+ let s:more_save = &more
+ let s:ruler_save = &ruler
+ let s:smd_save = &showmode
+ endif
+ set nomore noshowmode
+ if winnr('$') == 1 | set noruler | endif
+ augroup PluginXoloxHideMode
+ autocmd! CursorHold,CursorHoldI * call s:clear_message()
+ augroup END
+ execute 'echohl' a:hlgroup
+ " Redraw to avoid |hit-enter| prompt.
+ redraw | echomsg message
+ if g:xolox_message_buffer > 0
+ call add(g:xolox_messages, message)
+ if len(g:xolox_messages) > g:xolox_message_buffer
+ call remove(g:xolox_messages, 0)
+ endif
+ endif
+ finally
+ " Always clear message highlighting, even when interrupted by Ctrl-C.
+ echohl none
+ endtry
+ endif
+endfunction
+
+function! s:clear_message()
+ echo ''
+ let &more = s:more_save
+ let &showmode = s:smd_save
+ let &ruler = s:ruler_save
+ unlet s:more_save s:ruler_save s:smd_save
+ autocmd! PluginXoloxHideMode
+ augroup! PluginXoloxHideMode
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/open.vim b/autoload/xolox/misc/open.vim
new file mode 100644
index 0000000..1fb24e0
--- /dev/null
+++ b/autoload/xolox/misc/open.vim
@@ -0,0 +1,70 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: November 21, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+if !exists('s:version')
+ let s:version = '1.1'
+ let s:enoimpl = "open.vim %s: %s() hasn't been implemented for your platform! If you have suggestions, please contact peter@peterodding.com."
+ let s:handlers = ['gnome-open', 'kde-open', 'exo-open', 'xdg-open']
+endif
+
+function! xolox#misc#open#file(path, ...)
+ if xolox#misc#os#is_win()
+ try
+ call xolox#shell#open_with_windows_shell(a:path)
+ catch /^Vim\%((\a\+)\)\=:E117/
+ let command = '!start CMD /C START "" %s'
+ silent execute printf(command, xolox#misc#escape#shell(a:path))
+ endtry
+ return
+ elseif has('macunix')
+ let cmd = 'open ' . shellescape(a:path) . ' 2>&1'
+ call s:handle_error(cmd, system(cmd))
+ return
+ else
+ for handler in s:handlers + a:000
+ if executable(handler)
+ call xolox#misc#msg#debug("open.vim %s: Using '%s' to open '%s'.", s:version, handler, a:path)
+ let cmd = shellescape(handler) . ' ' . shellescape(a:path) . ' 2>&1'
+ call s:handle_error(cmd, system(cmd))
+ return
+ endif
+ endfor
+ endif
+ throw printf(s:enoimpl, s:script, 'xolox#misc#open#file')
+endfunction
+
+function! xolox#misc#open#url(url)
+ let url = a:url
+ if url !~ '^\w\+://'
+ if url !~ '@'
+ let url = 'http://' . url
+ elseif url !~ '^mailto:'
+ let url = 'mailto:' . url
+ endif
+ endif
+ if has('unix') && !has('gui_running') && $DISPLAY == ''
+ for browser in ['lynx', 'links', 'w3m']
+ if executable(browser)
+ execute '!' . browser fnameescape(url)
+ call s:handle_error(browser . ' ' . url, '')
+ return
+ endif
+ endfor
+ endif
+ call xolox#misc#open#file(url, 'firefox', 'google-chrome')
+endfunction
+
+function! s:handle_error(cmd, output)
+ if v:shell_error
+ let message = "open.vim %s: Failed to execute program! (command line: %s%s)"
+ let output = strtrans(xolox#misc#str#trim(a:output))
+ if output != ''
+ let output = ", output: " . string(output)
+ endif
+ throw printf(message, s:version, a:cmd, output)
+ endif
+endfunction
+
+" vim: et ts=2 sw=2 fdm=marker
diff --git a/autoload/xolox/misc/option.vim b/autoload/xolox/misc/option.vim
new file mode 100644
index 0000000..efaf1bc
--- /dev/null
+++ b/autoload/xolox/misc/option.vim
@@ -0,0 +1,84 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: August 31, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+function! xolox#misc#option#get(name, ...)
+ if exists('b:' . a:name)
+ " Buffer local variable.
+ return eval('b:' . a:name)
+ elseif exists('g:' . a:name)
+ " Global variable.
+ return eval('g:' . a:name)
+ elseif exists('a:1')
+ " Default value.
+ return a:1
+ endif
+endfunction
+
+" Functions to parse multi-valued Vim options like &tags and &runtimepath.
+
+function! xolox#misc#option#split(value)
+ let values = split(a:value, '[^\\]\zs,')
+ return map(values, 's:unescape(v:val)')
+endfunction
+
+function! s:unescape(s)
+ return substitute(a:s, '\\\([\\,]\)', '\1', 'g')
+endfunction
+
+function! xolox#misc#option#join(values)
+ let values = copy(a:values)
+ call map(values, 's:escape(v:val)')
+ return join(values, ',')
+endfunction
+
+function! s:escape(s)
+ return escape(a:s, ',\')
+endfunction
+
+function! xolox#misc#option#split_tags(value)
+ let values = split(a:value, '[^\\]\zs,')
+ return map(values, 's:unescape_tags(v:val)')
+endfunction
+
+function! s:unescape_tags(s)
+ return substitute(a:s, '\\\([\\, ]\)', '\1', 'g')
+endfunction
+
+function! xolox#misc#option#join_tags(values)
+ let values = copy(a:values)
+ call map(values, 's:escape_tags(v:val)')
+ return join(values, ',')
+endfunction
+
+function! s:escape_tags(s)
+ return escape(a:s, ', ')
+endfunction
+
+function! xolox#misc#option#eval_tags(value, ...)
+ let pathnames = []
+ let first_only = exists('a:1') && a:1
+ for pattern in xolox#misc#option#split_tags(a:value)
+ " Make buffer relative pathnames absolute.
+ if pattern =~ '^\./'
+ let directory = xolox#misc#escape#substitute(expand('%:p:h'))
+ let pattern = substitute(pattern, '^.\ze/', directory, '')
+ endif
+ " Make working directory relative pathnames absolute.
+ if xolox#misc#path#is_relative(pattern)
+ let pattern = xolox#misc#path#merge(getcwd(), pattern)
+ endif
+ " Ignore the trailing `;' for recursive upwards searching because we
+ " always want the most specific pathname available.
+ let pattern = substitute(pattern, ';$', '', '')
+ " Expand the pattern.
+ call extend(pathnames, split(expand(pattern), "\n"))
+ if first_only && !empty(pathnames)
+ return pathnames[0]
+ endif
+ endfor
+ return firstonly ? '' : pathnames
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/os.vim b/autoload/xolox/misc/os.vim
new file mode 100644
index 0000000..0852e2d
--- /dev/null
+++ b/autoload/xolox/misc/os.vim
@@ -0,0 +1,12 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: March 15, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+" Check whether Vim is running on Microsoft Windows.
+
+function! xolox#misc#os#is_win()
+ return has('win16') || has('win32') || has('win64')
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/path.vim b/autoload/xolox/misc/path.vim
new file mode 100644
index 0000000..efb6340
--- /dev/null
+++ b/autoload/xolox/misc/path.vim
@@ -0,0 +1,194 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: September 26, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+let s:windows_compatible = has('win32') || has('win64')
+
+function! xolox#misc#path#which(...)
+ let extensions = s:windows_compatible ? split($PATHEXT, ';') : ['']
+ let matches = []
+ let checked = {}
+ for directory in split($PATH, s:windows_compatible ? ';' : ':')
+ let directory = xolox#misc#path#absolute(directory)
+ if !has_key(checked, directory)
+ if isdirectory(directory)
+ for program in a:000
+ for extension in extensions
+ let path = xolox#misc#path#merge(directory, program . extension)
+ if executable(path)
+ call add(matches, path)
+ endif
+ endfor
+ endfor
+ endif
+ let checked[directory] = 1
+ endif
+ endfor
+ return matches
+endfunction
+
+" Split a pathname into a list of path components.
+
+function! xolox#misc#path#split(path)
+ if type(a:path) == type('')
+ if s:windows_compatible
+ return split(a:path, '[\/]\+')
+ else
+ let absolute = (a:path =~ '^/')
+ let segments = split(a:path, '/\+')
+ return absolute ? insert(segments, '/') : segments
+ endif
+ endif
+ return []
+endfunction
+
+" Join a list of path components into a pathname.
+
+function! xolox#misc#path#join(parts)
+ if type(a:parts) == type([])
+ if !s:windows_compatible && a:parts[0] == '/'
+ return join(a:parts, '/')[1 : -1]
+ else
+ return join(a:parts, '/')
+ endif
+ endif
+ return ''
+endfunction
+
+" Canonicalize and resolve a pathname.
+
+function! xolox#misc#path#absolute(path)
+ if type(a:path) == type('')
+ let path = fnamemodify(a:path, ':p')
+ " resolve() doesn't work when there's a trailing path separator.
+ if path =~ '/$'
+ let stripped_slash = 1
+ let path = substitute(path, '/$', '', '')
+ endif
+ let path = resolve(path)
+ " Restore the path separator after calling resolve().
+ if exists('stripped_slash') && path !~ '/$'
+ let path .= '/'
+ endif
+ return path
+ endif
+ return ''
+endfunction
+
+" Make an absolute pathname relative.
+
+function! xolox#misc#path#relative(path, base)
+ let path = xolox#misc#path#split(a:path)
+ let base = xolox#misc#path#split(a:base)
+ while path != [] && base != [] && path[0] == base[0]
+ call remove(path, 0)
+ call remove(base, 0)
+ endwhile
+ let distance = repeat(['..'], len(base))
+ return xolox#misc#path#join(distance + path)
+endfunction
+
+" Join a directory and filename into a single pathname.
+
+function! xolox#misc#path#merge(parent, child, ...)
+ " TODO Use isabs()!
+ if type(a:parent) == type('') && type(a:child) == type('')
+ if s:windows_compatible
+ let parent = substitute(a:parent, '[\\/]\+$', '', '')
+ let child = substitute(a:child, '^[\\/]\+', '', '')
+ return parent . '\' . child
+ else
+ let parent = substitute(a:parent, '/\+$', '', '')
+ let child = substitute(a:child, '^/\+', '', '')
+ return parent . '/' . child
+ endif
+ endif
+ return ''
+endfunction
+
+" Find the common prefix of path components in a list of pathnames.
+
+function! xolox#misc#path#commonprefix(paths)
+ let common = xolox#misc#path#split(a:paths[0])
+ for path in a:paths
+ let index = 0
+ for segment in xolox#misc#path#split(path)
+ if len(common) <= index
+ break
+ elseif common[index] != segment
+ call remove(common, index, -1)
+ break
+ endif
+ let index += 1
+ endfor
+ endfor
+ return xolox#misc#path#join(common)
+endfunction
+
+" Encode a pathname so it can be used as a filename.
+
+function! xolox#misc#path#encode(path)
+ let mask = s:windows_compatible ? '[*|\\/:"<>?%]' : '[\\/%]'
+ return substitute(a:path, mask, '\=printf("%%%x", char2nr(submatch(0)))', 'g')
+endfunction
+
+" Decode a pathname previously encoded with xolox#misc#path#encode().
+
+function! xolox#misc#path#decode(encoded_path)
+ return substitute(a:encoded_path, '%\(\x\x\?\)', '\=nr2char("0x" . submatch(1))', 'g')
+endfunction
+
+" Check whether two pathnames point to the same file.
+
+if s:windows_compatible
+ function! xolox#misc#path#equals(a, b)
+ return a:a ==? a:b || xolox#misc#path#absolute(a:a) ==? xolox#misc#path#absolute(a:b)
+ endfunction
+else
+ function! xolox#misc#path#equals(a, b)
+ return a:a ==# a:b || xolox#misc#path#absolute(a:a) ==# xolox#misc#path#absolute(a:b)
+ endfunction
+endif
+
+" Check whether a path is relative.
+
+function! xolox#misc#path#is_relative(path)
+ if a:path =~ '^\w\+://'
+ return 0
+ elseif s:windows_compatible
+ return a:path !~ '^\(\w:\|[\\/]\)'
+ else
+ return a:path !~ '^/'
+ endif
+endfunction
+
+" Create a temporary directory and return the path.
+
+function! xolox#misc#path#tempdir()
+ if !exists('s:tempdir_counter')
+ let s:tempdir_counter = 1
+ endif
+ if exists('*mkdir')
+ if s:windows_compatible
+ let template = $TMP . '\vim_tempdir_'
+ elseif filewritable('/tmp') == 2
+ let template = '/tmp/vim_tempdir_'
+ endif
+ endif
+ if !exists('template')
+ throw "xolox#misc#path#tempdir() hasn't been implemented on your platform!"
+ endif
+ while 1
+ let directory = template . s:tempdir_counter
+ try
+ call mkdir(directory, '', 0700)
+ return directory
+ catch /\<E739\>/
+ " Keep looking for a non-existing directory.
+ endtry
+ let s:tempdir_counter += 1
+ endwhile
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/str.vim b/autoload/xolox/misc/str.vim
new file mode 100644
index 0000000..74a05aa
--- /dev/null
+++ b/autoload/xolox/misc/str.vim
@@ -0,0 +1,12 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: June 14, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+" Trim whitespace from start and end of string.
+
+function! xolox#misc#str#trim(s)
+ return substitute(a:s, '^\_s*\(.\{-}\)\_s*$', '\1', '')
+endfunction
+
+" vim: ts=2 sw=2 et
diff --git a/autoload/xolox/misc/timer.vim b/autoload/xolox/misc/timer.vim
new file mode 100644
index 0000000..151972d
--- /dev/null
+++ b/autoload/xolox/misc/timer.vim
@@ -0,0 +1,85 @@
+" Vim auto-load script
+" Author: Peter Odding <peter@peterodding.com>
+" Last Change: March 15, 2011
+" URL: http://peterodding.com/code/vim/misc/
+
+if !exists('g:timer_enabled')
+ let g:timer_enabled = 0
+endif
+
+if !exists('g:timer_verbosity')
+ let g:timer_verbosity = 1
+endif
+
+let s:has_reltime = has('reltime')
+
+" Start a timer.
+
+function! xolox#misc#timer#start()
+ if g:timer_enabled || &verbose >= g:timer_verbosity
+ return s:has_reltime ? reltime() : [localtime()]
+ endif
+ return []
+endfunction
+
+" Stop a timer and print the elapsed time (only if the user is interested).
+
+function! xolox#misc#timer#stop(...)
+ if (g:timer_enabled || &verbose >= g:timer_verbosity)
+ call call('xolox#misc#msg#info', map(copy(a:000), 's:convert_value(v:val)'))
+ endif
+endfunction
+
+function! s:convert_value(value)
+ if type(a:value) != type([])
+ return a:value
+ elseif !empty(a:value)
+ if s:has_reltime
+ let ts = xolox#misc#str#trim(reltimestr(reltime(a:value)))
+ else
+ let ts = localtime() - a:value[0]
+ endif
+ return xolox#misc#timer#format_timespan(ts)
+ else
+ return '?'
+ endif
+endfunction
+
+" Format number of seconds as human friendly description.
+
+let s:units = [['day', 60 * 60 * 24], ['hour', 60 * 60], ['minute', 60], ['second', 1]]
+
+function! xolox#misc#timer#format_timespan(ts)
+
+ " Convert timespan to integer.
+ let seconds = a:ts + 0
+
+ " Fast common case with extra precision from reltime().
+ if seconds < 5
+ let extract = matchstr(a:ts, '^\d\+\(\.0*[1-9][1-9]\?\)\?')
+ if extract =~ '[123456789]'
+ return extract . ' second' . (extract != '1' ? 's' : '')
+ endif
+ endif
+
+ " Generic but slow code.
+ let result = []
+ for [name, size] in s:units
+ if seconds >= size
+ let counter = seconds / size
+ let seconds = seconds % size
+ let suffix = counter != 1 ? 's' : ''
+ call add(result, printf('%i %s%s', counter, name, suffix))
+ endif
+ endfor
+
+ " Format the resulting text?
+ if len(result) == 1
+ return result[0]
+ else
+ return join(result[0:-2], ', ') . ' and ' . result[-1]
+ endif
+
+endfunction
+
+" vim: ts=2 sw=2 et