Move easytags to pathogen
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -22,3 +22,6 @@
|
||||
[submodule ".vim/bundle/vim-ispc"]
|
||||
path = .vim/bundle/vim-ispc
|
||||
url = https://github.com/jez/vim-ispc
|
||||
[submodule ".vim/bundle/vim-easytags"]
|
||||
path = .vim/bundle/vim-easytags
|
||||
url = git@github.com:xolox/vim-easytags.git
|
||||
|
||||
@@ -1,910 +0,0 @@
|
||||
" Vim script
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: March 15, 2015
|
||||
" URL: http://peterodding.com/code/vim/easytags/
|
||||
|
||||
let g:xolox#easytags#version = '3.10'
|
||||
let g:xolox#easytags#default_pattern_prefix = '\C\<'
|
||||
let g:xolox#easytags#default_pattern_suffix = '\>'
|
||||
|
||||
if !exists('s:timers_initialized')
|
||||
let g:xolox#easytags#update_timer = xolox#misc#timer#resumable()
|
||||
let g:xolox#easytags#highlight_timer = xolox#misc#timer#resumable()
|
||||
let g:xolox#easytags#syntax_match_timer = xolox#misc#timer#resumable()
|
||||
let g:xolox#easytags#syntax_keyword_timer = xolox#misc#timer#resumable()
|
||||
let g:xolox#easytags#syntax_filter_stage_1_timer = xolox#misc#timer#resumable()
|
||||
let g:xolox#easytags#syntax_filter_stage_2_timer = xolox#misc#timer#resumable()
|
||||
let s:timers_initialized = 1
|
||||
endif
|
||||
|
||||
" Plug-in initialization. {{{1
|
||||
|
||||
function! xolox#easytags#initialize(min_version) " {{{2
|
||||
" Check that the location of Exuberant Ctags has been configured or that the
|
||||
" correct version of the program exists in one of its default locations.
|
||||
if exists('g:easytags_cmd') && xolox#easytags#check_ctags_compatible(g:easytags_cmd, a:min_version)
|
||||
return 1
|
||||
endif
|
||||
if xolox#misc#os#is_win()
|
||||
" FIXME The code below that searches the $PATH is not used on Windows at
|
||||
" the moment because xolox#misc#path#which() generally produces absolute
|
||||
" paths and on Windows these absolute paths tend to contain spaces which
|
||||
" makes xolox#shell#execute_with_dll() fail. I've tried quoting the
|
||||
" program name with double quotes but it fails just the same (it works
|
||||
" with system() though). Anyway the problem of having multiple conflicting
|
||||
" versions of Exuberant Ctags installed is not that relevant to Windows
|
||||
" since it doesn't have a package management system. I still want to fix
|
||||
" xolox#shell#execute_with_dll() though.
|
||||
if xolox#easytags#check_ctags_compatible('ctags', a:min_version)
|
||||
let g:easytags_cmd = 'ctags'
|
||||
return 1
|
||||
endif
|
||||
else
|
||||
" Exuberant Ctags can be installed under several names:
|
||||
" - On Ubuntu Linux, Exuberant Ctags is installed as `ctags-exuberant'
|
||||
" (and possibly `ctags' but that one can't be trusted :-)
|
||||
" - On Debian Linux, Exuberant Ctags is installed as `exuberant-ctags'.
|
||||
" - On Free-BSD, Exuberant Ctags is installed as `exctags'.
|
||||
" IIUC on Mac OS X the program /usr/bin/ctags is installed by default but
|
||||
" unusable and when the user installs Exuberant Ctags in an alternative
|
||||
" location, it doesn't come before /usr/bin/ctags in the search path. To
|
||||
" solve this problem in a general way and to save every Mac user out there
|
||||
" some frustration the plug-in will search the path and consider every
|
||||
" possible location, meaning that as long as Exuberant Ctags is installed
|
||||
" in the $PATH the plug-in should find it automatically.
|
||||
for program in xolox#misc#path#which('exuberant-ctags', 'ctags-exuberant', 'ctags', 'exctags')
|
||||
if xolox#easytags#check_ctags_compatible(program, a:min_version)
|
||||
let g:easytags_cmd = program
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#check_ctags_compatible(name, min_version) " {{{2
|
||||
" Not every executable out there named `ctags' is in fact Exuberant Ctags.
|
||||
" This function makes sure it is because the easytags plug-in requires the
|
||||
" --list-languages option (and more).
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Checking if Exuberant Ctags is installed as '%s'.", g:xolox#easytags#version, a:name)
|
||||
" Make sure the given program is executable.
|
||||
if !executable(a:name)
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Program '%s' is not executable!", g:xolox#easytags#version, a:name)
|
||||
return 0
|
||||
endif
|
||||
" Make sure the command exits without reporting an error.
|
||||
let command = a:name . ' --version'
|
||||
let result = xolox#misc#os#exec({'command': command, 'check': 0})
|
||||
if result['exit_code'] != 0
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Command '%s' returned nonzero exit code %i!", g:xolox#easytags#version, a:name, result['exit_code'])
|
||||
else
|
||||
" Extract the version number from the output.
|
||||
let pattern = 'Exuberant Ctags \zs\(\d\+\(\.\d\+\)*\|Development\)'
|
||||
let g:easytags_ctags_version = matchstr(get(result['stdout'], 0, ''), pattern)
|
||||
" Deal with development builds.
|
||||
if g:easytags_ctags_version == 'Development'
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Assuming development build is compatible ..", g:xolox#easytags#version, a:name)
|
||||
return 1
|
||||
endif
|
||||
" Make sure the version is compatible.
|
||||
if xolox#misc#version#at_least(a:min_version, g:easytags_ctags_version)
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Version is compatible! :-)", g:xolox#easytags#version)
|
||||
return 1
|
||||
else
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Version is not compatible! :-(", g:xolox#easytags#version)
|
||||
endif
|
||||
endif
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Standard output of command: %s", g:xolox#easytags#version, string(result['stdout']))
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Standard error of command: %s", g:xolox#easytags#version, string(result['stderr']))
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
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_file_type_specific_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
|
||||
|
||||
" Public interface through (automatic) commands. {{{1
|
||||
|
||||
function! xolox#easytags#autoload(event) " {{{2
|
||||
try
|
||||
let session_loading = xolox#easytags#session_is_loading() && a:event == 'BufReadPost'
|
||||
let do_update = xolox#misc#option#get('easytags_auto_update', 1) && !session_loading
|
||||
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) && !empty(xolox#easytags#filetypes#canonicalize(&filetype))
|
||||
" Update entries for current file in tags file?
|
||||
if do_update
|
||||
let buffer_read = (a:event =~? 'BufReadPost')
|
||||
let buffer_written = (a:event =~? 'BufWritePost')
|
||||
if buffer_written || (buffer_read && xolox#misc#option#get('easytags_always_enabled', 0))
|
||||
call xolox#easytags#update(1, 0, [])
|
||||
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
|
||||
let async = xolox#misc#option#get('easytags_async', 0)
|
||||
call g:xolox#easytags#update_timer.start()
|
||||
try
|
||||
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 command_line = s:prep_cmdline(cfile, a:filenames)
|
||||
if empty(command_line)
|
||||
return 0
|
||||
endif
|
||||
" Pack all of the information required to update the tags in
|
||||
" a Vim dictionary which is easy to serialize to a string.
|
||||
let params = {}
|
||||
let params['command'] = command_line
|
||||
let params['ctags_version'] = g:easytags_ctags_version
|
||||
let params['default_filetype'] = xolox#easytags#filetypes#canonicalize(&filetype)
|
||||
let params['filter_tags'] = a:filter_tags || async
|
||||
let params['have_args'] = have_args
|
||||
let dynamic_tagsfile = xolox#easytags#get_dynamic_tagsfile()
|
||||
if !empty(dynamic_tagsfile)
|
||||
let params['tagsfile'] = dynamic_tagsfile
|
||||
elseif !empty(g:easytags_by_filetype)
|
||||
let params['directory'] = xolox#misc#path#absolute(g:easytags_by_filetype)
|
||||
let params['filetypes'] = g:xolox#easytags#filetypes#ctags_to_vim
|
||||
else
|
||||
let params['tagsfile'] = xolox#easytags#get_global_tagsfile()
|
||||
endif
|
||||
if async
|
||||
call xolox#misc#async#call({'function': 'xolox#easytags#update#with_vim', 'arguments': [params], 'callback': 'xolox#easytags#async_callback'})
|
||||
else
|
||||
call s:report_results(xolox#easytags#update#with_vim(params), 0)
|
||||
" When :UpdateTags was executed manually we'll refresh the dynamic
|
||||
" syntax highlighting so that new tags are immediately visible.
|
||||
if !a:silent && xolox#misc#option#get('easytags_auto_highlight', 1)
|
||||
HighlightTags
|
||||
endif
|
||||
endif
|
||||
return 1
|
||||
catch
|
||||
call xolox#misc#msg#warn("easytags.vim %s: %s (at %s)", g:xolox#easytags#version, v:exception, v:throwpoint)
|
||||
finally
|
||||
call g:xolox#easytags#update_timer.stop()
|
||||
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 = xolox#easytags#utils#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 = xolox#easytags#utils#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 empty(xolox#easytags#filetypes#canonicalize(&ft))
|
||||
if silent | return '' | endif
|
||||
throw "Exuberant Ctags doesn't support the " . string(&ft) . " file type!"
|
||||
endif
|
||||
return cfile
|
||||
endfunction
|
||||
|
||||
function! s:prep_cmdline(cfile, arguments) " {{{3
|
||||
let vim_file_type = xolox#easytags#filetypes#canonicalize(&filetype)
|
||||
let custom_languages = xolox#misc#option#get('easytags_languages', {})
|
||||
let language = get(custom_languages, vim_file_type, {})
|
||||
if empty(language)
|
||||
let cmdline = [xolox#easytags#ctags_command()]
|
||||
call add(cmdline, '--fields=+l')
|
||||
call add(cmdline, '--c-kinds=+p')
|
||||
call add(cmdline, '--c++-kinds=+p')
|
||||
call add(cmdline, '--sort=no')
|
||||
call add(cmdline, '-f-')
|
||||
if xolox#misc#option#get('easytags_include_members', 0)
|
||||
call add(cmdline, '--extra=+q')
|
||||
endif
|
||||
else
|
||||
let program = get(language, 'cmd', xolox#easytags#ctags_command())
|
||||
if empty(program)
|
||||
call xolox#misc#msg#warn("easytags.vim %s: No 'cmd' defined for language '%s', and also no global default!", g:xolox#easytags#version, vim_file_type)
|
||||
return ''
|
||||
endif
|
||||
let cmdline = [program] + get(language, 'args', [])
|
||||
call add(cmdline, xolox#misc#escape#shell(get(language, 'stdout_opt', '-f-')))
|
||||
endif
|
||||
let have_args = 0
|
||||
if a:cfile != ''
|
||||
if xolox#misc#option#get('easytags_autorecurse', 0)
|
||||
call add(cmdline, empty(language) ? '-R' : xolox#misc#escape#shell(get(language, 'recurse_flag', '-R')))
|
||||
call add(cmdline, xolox#misc#escape#shell(a:cfile))
|
||||
else
|
||||
if empty(language)
|
||||
" 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#filetypes#to_ctags(vim_file_type)
|
||||
call add(cmdline, xolox#misc#escape#shell('--language-force=' . filetype))
|
||||
endif
|
||||
call add(cmdline, xolox#misc#escape#shell(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, 'xolox#misc#escape#shell(xolox#easytags#utils#canonicalize(v:val))')
|
||||
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! xolox#easytags#highlight() " {{{2
|
||||
" TODO This is a mess; Re-implement Python version in Vim script, benchmark, remove Python version.
|
||||
try
|
||||
call g:xolox#easytags#highlight_timer.start()
|
||||
let filetype = xolox#easytags#filetypes#canonicalize(&filetype)
|
||||
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.
|
||||
let ctags_filetypes = xolox#easytags#filetypes#find_ctags_aliases(filetype)
|
||||
let filetypes_pattern = printf('^\(%s\)$', join(map(ctags_filetypes, 'xolox#misc#escape#pattern(v:val)'), '\|'))
|
||||
call g:xolox#easytags#syntax_filter_stage_1_timer.start()
|
||||
let taglist = filter(taglist('.'), "get(v:val, 'language', '') =~? filetypes_pattern")
|
||||
call g:xolox#easytags#syntax_filter_stage_1_timer.stop()
|
||||
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
|
||||
call g:xolox#easytags#syntax_filter_stage_2_timer.start()
|
||||
let matches = filter(copy(taglist), filter)
|
||||
call g:xolox#easytags#syntax_filter_stage_2_timer.stop()
|
||||
if matches != []
|
||||
" Convert matched tags to :syntax commands and execute them.
|
||||
let use_keywords_when = xolox#misc#option#get('easytags_syntax_keyword', 'auto')
|
||||
let has_default_pattern_prefix = (tagkind.pattern_prefix == g:xolox#easytags#default_pattern_prefix)
|
||||
let has_default_pattern_suffix = (tagkind.pattern_suffix == g:xolox#easytags#default_pattern_suffix)
|
||||
let has_non_default_patterns = !(has_default_pattern_prefix && has_default_pattern_suffix)
|
||||
if use_keywords_when == 'always' || (use_keywords_when == 'auto' && !has_non_default_patterns)
|
||||
" Vim's ":syntax keyword" command doesn't use the regular
|
||||
" expression engine and the resulting syntax highlighting is
|
||||
" therefor much faster. Because of this we use the syntax
|
||||
" keyword command when 1) we can do so without sacrificing
|
||||
" accuracy or 2) the user explicitly chose to sacrifice
|
||||
" accuracy in order to make the highlighting faster.
|
||||
call g:xolox#easytags#syntax_keyword_timer.start()
|
||||
let keywords = {}
|
||||
for tag in matches
|
||||
if s:is_keyword_compatible(tag)
|
||||
let keywords[tag.name] = 1
|
||||
endif
|
||||
endfor
|
||||
if !empty(keywords)
|
||||
let template = 'syntax keyword %s %s containedin=ALLBUT,%s'
|
||||
let command = printf(template, hlgroup_tagged, join(keys(keywords)), xolox#easytags#syntax_groups_to_ignore())
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Executing command '%s'.", g:xolox#easytags#version, command)
|
||||
execute command
|
||||
" Remove the tags that we just highlighted from the list of
|
||||
" tags that still need to be highlighted.
|
||||
call filter(matches, "!s:is_keyword_compatible(v:val)")
|
||||
endif
|
||||
call g:xolox#easytags#syntax_keyword_timer.stop()
|
||||
endif
|
||||
if !empty(matches)
|
||||
call g:xolox#easytags#syntax_match_timer.start()
|
||||
let matches = xolox#misc#list#unique(map(matches, 'xolox#misc#escape#pattern(get(v:val, "name"))'))
|
||||
let pattern = tagkind.pattern_prefix . '\%(' . join(matches, '\|') . '\)' . tagkind.pattern_suffix
|
||||
let template = 'syntax match %s /%s/ containedin=ALLBUT,%s'
|
||||
let command = printf(template, hlgroup_tagged, escape(pattern, '/'), xolox#easytags#syntax_groups_to_ignore())
|
||||
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
|
||||
call g:xolox#easytags#syntax_match_timer.stop()
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
" Avoid flashing each highlighted buffer in front of the user when
|
||||
" loading a session.
|
||||
if !xolox#easytags#session_is_loading()
|
||||
redraw
|
||||
endif
|
||||
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)
|
||||
finally
|
||||
call g:xolox#easytags#highlight_timer.stop()
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:is_keyword_compatible(tag)
|
||||
let name = get(a:tag, 'name', '')
|
||||
if !empty(name)
|
||||
" Make sure the tag contains only `keyword characters' (included in the
|
||||
" &iskeyword option) and is not longer than 80 characters (these
|
||||
" limitations are documented under :help :syn-keyword).
|
||||
if name =~ '^\k\+$' && len(name) <= 80
|
||||
" Make sure the tag doesn't conflict with one of the named options
|
||||
" accepted by the `:syntax keyword' command (using these named options
|
||||
" improperly, e.g. without a mandatory argument, will raise an error).
|
||||
return !has_key(s:invalid_keywords, name)
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
" These are documented under :help E395, except for "contains" which is not
|
||||
" documented as being forbidden but when used definitely triggers an error.
|
||||
let s:invalid_keywords = {
|
||||
\ 'cchar': 1,
|
||||
\ 'conceal': 1,
|
||||
\ 'contained': 1,
|
||||
\ 'containedin': 1,
|
||||
\ 'contains': 1,
|
||||
\ 'nextgroup': 1,
|
||||
\ 'skipempty': 1,
|
||||
\ 'skipnl': 1,
|
||||
\ 'skipwhite': 1,
|
||||
\ 'transparent': 1,
|
||||
\ }
|
||||
|
||||
" Public supporting functions (might be useful to others). {{{1
|
||||
|
||||
function! xolox#easytags#ctags_command() " {{{2
|
||||
let program = xolox#misc#option#get('easytags_cmd')
|
||||
if !empty(program)
|
||||
let options = xolox#misc#option#get('easytags_opts')
|
||||
if !empty(options)
|
||||
let command_line = [program]
|
||||
call extend(command_line, map(copy(options), 'xolox#misc#escape#shell(expand(v:val))'))
|
||||
let program = join(command_line)
|
||||
endif
|
||||
return program
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#get_tagsfile() " {{{2
|
||||
" Get the absolute pathname of the tags file to use. This function
|
||||
" automatically selects the best choice from the following options (in
|
||||
" descending order of preference):
|
||||
"
|
||||
" 1. Dynamic tags files (see `xolox#easytags#get_dynamic_tagsfile()`).
|
||||
" 2. File type specific tags files (see `xolox#easytags#get_file_type_specific_tagsfile()`).
|
||||
" 3. The global tags file (see `xolox#easytags#get_global_tagsfile()`).
|
||||
"
|
||||
" Returns the absolute pathname of the selected tags file.
|
||||
"
|
||||
" This function is no longer used by the vim-easytags plug-in itself because
|
||||
" the vim-easytags plug-in needs to differentiate between the different
|
||||
" types of tags files in every place where it deals with tags files. Because
|
||||
" this is an externally callable function it is unclear to me if other code
|
||||
" depends on it, this is the reason why I haven't removed it yet.
|
||||
let tagsfile = xolox#easytags#get_dynamic_tagsfile()
|
||||
if empty(tagsfile)
|
||||
let tagsfile = xolox#easytags#get_file_type_specific_tagsfile()
|
||||
endif
|
||||
if empty(tagsfile)
|
||||
let tagsfile = xolox#easytags#get_global_tagsfile()
|
||||
endif
|
||||
return tagsfile
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#get_dynamic_tagsfile() " {{{2
|
||||
" Get the pathname of the dynamic tags file to use. If the user configured
|
||||
" dynamic tags files this function returns the pathname of the applicable
|
||||
" dynamic tags file (which may not exist yet), otherwise it returns an empty
|
||||
" string.
|
||||
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)
|
||||
let directory = fnamemodify(tagsfile, ':h')
|
||||
if filewritable(directory) != 2
|
||||
" If the directory of the dynamic tags file is not writable, we fall
|
||||
" back to another type of tags file.
|
||||
call xolox#misc#msg#warn("easytags.vim %s: Dynamic tags files enabled but %s not writable so falling back.", g:xolox#easytags#version, directory)
|
||||
let tagsfile = ''
|
||||
endif
|
||||
endif
|
||||
if !empty(tagsfile)
|
||||
return s:select_tags_file(tagsfile, 'dynamic')
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#get_file_type_specific_tagsfile() " {{{2
|
||||
" Get the pathname of the file type specific tags file to use. If the user
|
||||
" configured file type specific tags files this function returns the
|
||||
" pathname of the applicable file type specific tags file (which may not
|
||||
" exist yet), otherwise it returns an empty string.
|
||||
let vim_file_type = xolox#easytags#filetypes#canonicalize(&filetype)
|
||||
if !empty(g:easytags_by_filetype) && !empty(vim_file_type)
|
||||
let directory = xolox#misc#path#absolute(g:easytags_by_filetype)
|
||||
let tagsfile = xolox#misc#path#merge(directory, vim_file_type)
|
||||
if !empty(tagsfile)
|
||||
return s:select_tags_file(tagsfile, 'file type specific')
|
||||
endif
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#get_global_tagsfile() " {{{2
|
||||
" Get the pathname of the global tags file. Returns the absolute pathname of
|
||||
" the global tags file.
|
||||
let tagsfile = xolox#misc#option#get('easytags_file')
|
||||
return s:select_tags_file(expand(tagsfile), 'global')
|
||||
endfunction
|
||||
|
||||
function! s:select_tags_file(tagsfile, kind) " {{{2
|
||||
" If the selected tags file exists, make sure its writable. Also provide the
|
||||
" user with feedback about the tags file selection process.
|
||||
if filereadable(a:tagsfile) && filewritable(a:tagsfile) != 1
|
||||
let message = "The %s tags file %s isn't writable!"
|
||||
throw printf(message, a:kind, fnamemodify(a:tagsfile, ':~'))
|
||||
endif
|
||||
" Provide the user with feedback about the tags file selection process.
|
||||
call xolox#misc#msg#debug("easytags.vim %s: Selected %s tags file %s.", g:xolox#easytags#version, a:kind, a:tagsfile)
|
||||
" Canonicalize the tags file's pathname.
|
||||
return xolox#misc#path#absolute(a:tagsfile)
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#syntax_groups_to_ignore() " {{{2
|
||||
" Get a string matching the syntax groups where dynamic highlighting should
|
||||
" *not* apply. This is complicated by the fact that Vim has a tendency to do
|
||||
" this:
|
||||
"
|
||||
" Vim(syntax):E409: Unknown group name: doxygen.*
|
||||
"
|
||||
" This happens when a group wildcard doesn't match *anything*. Why does Vim
|
||||
" always have to make everything so complicated? :-(
|
||||
let groups = ['.*String.*', '.*Comment.*']
|
||||
for group_name in ['cIncluded', 'cCppOut2', 'cCppInElse2', 'cCppOutIf2', 'pythonDocTest', 'pythonDocTest2']
|
||||
if hlexists(group_name)
|
||||
call add(groups, group_name)
|
||||
endif
|
||||
endfor
|
||||
" Doxygen is an "add-on syntax script", it's usually used in combination:
|
||||
" :set syntax=c.doxygen
|
||||
" It gets special treatment because it defines a dozen or so groups :-)
|
||||
if hlexists('doxygenComment')
|
||||
call add(groups, 'doxygen.*')
|
||||
endif
|
||||
return join(groups, ',')
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#async_callback(response) " {{{2
|
||||
if has_key(a:response, 'result')
|
||||
call s:report_results(a:response['result'], 1)
|
||||
else
|
||||
call xolox#misc#msg#warn("easytags.vim %s: Asynchronous tags file update failed! (%s at %s)", g:xolox#easytags#version, a:response['exception'], a:response['throwpoint'])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#session_is_loading() " {{{2
|
||||
return exists('g:SessionLoad')
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#disable_automatic_updates() " {{{2
|
||||
let s:easytags_auto_update_save = xolox#misc#option#get('easytags_auto_update', 1)
|
||||
let g:easytags_auto_update = 0
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#restore_automatic_updates() " {{{2
|
||||
if exists('s:easytags_auto_update_save')
|
||||
let g:easytags_auto_update = s:easytags_auto_update_save
|
||||
unlet s:easytags_auto_update_save
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#easytags#why_so_slow() " {{{2
|
||||
let message = [printf("easytags.vim %s: Timings since you started Vim:", g:xolox#easytags#version)]
|
||||
call add(message, printf(" - %s seconds updating tags", g:xolox#easytags#update_timer.format()))
|
||||
call add(message, printf(" - %s seconds highlighting tags", g:xolox#easytags#highlight_timer.format()))
|
||||
call add(message, printf(" - %s seconds highlighting tags using ':syntax match')", g:xolox#easytags#syntax_match_timer.format()))
|
||||
call add(message, printf(" - %s seconds highlighting tags using ':syntax keyword')", g:xolox#easytags#syntax_keyword_timer.format()))
|
||||
call add(message, printf(" - %s seconds filtering tags for highlighting (stage 1)", g:xolox#easytags#syntax_filter_stage_1_timer.format()))
|
||||
call add(message, printf(" - %s seconds filtering tags for highlighting (stage 2)", g:xolox#easytags#syntax_filter_stage_2_timer.format()))
|
||||
echo join(message, "\n")
|
||||
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 = g:xolox#easytags#default_pattern_prefix
|
||||
endif
|
||||
if !has_key(a:object, 'pattern_suffix')
|
||||
let a:object.pattern_suffix = g:xolox#easytags#default_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
|
||||
|
||||
" Miscellaneous script-local functions. {{{1
|
||||
|
||||
function! s:report_results(response, async) " {{{2
|
||||
if !xolox#misc#option#get('easytags_suppress_report', 0)
|
||||
let actions = []
|
||||
if a:response['num_updated'] > 0
|
||||
call add(actions, printf('updated %i tags', a:response['num_updated']))
|
||||
endif
|
||||
if a:response['num_filtered'] > 0
|
||||
call add(actions, printf('filtered %i invalid tags', a:response['num_filtered']))
|
||||
endif
|
||||
if !empty(actions)
|
||||
let function = a:async ? 'xolox#misc#msg#debug' : 'xolox#misc#msg#info'
|
||||
let actions_string = xolox#misc#str#ucfirst(join(actions, ' and '))
|
||||
let command_type = a:async ? 'asynchronously' : 'synchronously'
|
||||
call call(function, ["easytags.vim %s: %s in %s (%s).", g:xolox#easytags#version, actions_string, a:response['elapsed_time'], command_type])
|
||||
endif
|
||||
endif
|
||||
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
|
||||
" TODO This doesn't support file type groups!
|
||||
let context['filetype'] = xolox#easytags#filetypes#to_ctags(xolox#easytags#filetypes#canonicalize(&filetype))
|
||||
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#easytags#syntax_groups_to_ignore()
|
||||
" 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 = {}
|
||||
|
||||
" 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 and C++. {{{2
|
||||
"
|
||||
" C and C++ are both treated as C++, for details refer
|
||||
" to https://github.com/xolox/vim-easytags/issues/91.
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'cpp',
|
||||
\ 'hlgroup': 'cType',
|
||||
\ 'tagkinds': '[cgstu]'})
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'cpp',
|
||||
\ 'hlgroup': 'cEnum',
|
||||
\ 'tagkinds': 'e'})
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'cpp',
|
||||
\ 'hlgroup': 'cPreProc',
|
||||
\ 'tagkinds': 'd'})
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'cpp',
|
||||
\ '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': 'cpp',
|
||||
\ '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': 'javaInterface',
|
||||
\ 'tagkinds': 'i'})
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'java',
|
||||
\ 'hlgroup': 'javaMethod',
|
||||
\ 'tagkinds': 'm'})
|
||||
|
||||
highlight def link javaClass Identifier
|
||||
highlight def link javaMethod Function
|
||||
highlight def link javaInterface Identifier
|
||||
|
||||
" 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': '\(\w\|\s*()\)\@!'})
|
||||
|
||||
highlight def link shFunctionTag Operator
|
||||
|
||||
" Tcl. {{{2
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'tcl',
|
||||
\ 'hlgroup': 'tclCommandTag',
|
||||
\ 'tagkinds': 'p'})
|
||||
|
||||
highlight def link tclCommandTag Operator
|
||||
|
||||
" Perl. {{{2
|
||||
|
||||
call xolox#easytags#define_tagkind({
|
||||
\ 'filetype': 'perl',
|
||||
\ 'hlgroup': 'perlFunctionTag',
|
||||
\ 'tagkinds': '[s]',
|
||||
\ 'pattern_prefix': '\%(\<sub\s\+\)\@<!\%(>\|\s\|&\|^\)\@<=\<'})
|
||||
|
||||
highlight def link perlFunctionTag Operator
|
||||
|
||||
" }}}
|
||||
|
||||
" Restore "cpoptions".
|
||||
let &cpo = s:cpo_save
|
||||
unlet s:cpo_save
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,139 +0,0 @@
|
||||
" Vim script
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: November 13, 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 other_filetypes = map(a:000[1:], 'tolower(v:val)')
|
||||
let s:filetype_groups[canonical_filetype] = other_filetypes
|
||||
for ft in other_filetypes
|
||||
let s:canonical_filetypes[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 = [a:canonical_vim_filetype]
|
||||
call extend(filetypes, 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?
|
||||
let command_line = xolox#easytags#ctags_command()
|
||||
if !empty(command_line)
|
||||
let starttime = xolox#misc#timer#start()
|
||||
let command_line .= ' --list-languages'
|
||||
for line in xolox#misc#os#exec({'command': command_line})['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. It's important that C normalizes to C++
|
||||
" because of the following points:
|
||||
"
|
||||
" - Vim and Exuberant Ctags consistently treat *.h files as C++. I guess this
|
||||
" is because A) the filename extension is ambiguous and B) C++ is a
|
||||
" superset of C so the mapping makes sense.
|
||||
"
|
||||
" - Because of the above point, when you use file type specific tags files
|
||||
" and you're editing C source code you'll be missing everything defined in
|
||||
" your *.h files. Depending on your programming style those tags might be
|
||||
" redundant or they might not be.
|
||||
"
|
||||
" To solve this dilemma the vim-easytags plug-in groups the C and C++ file
|
||||
" types together and tells Exuberant Ctags to treat it all as C++ because C++
|
||||
" is a superset of C.
|
||||
call xolox#easytags#filetypes#add_group('cpp', 'c')
|
||||
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
|
||||
@@ -1,288 +0,0 @@
|
||||
" Vim script
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: August 8, 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
|
||||
if filereadable(a:tagsfile)
|
||||
let lines = readfile(a:tagsfile)
|
||||
else
|
||||
let lines = []
|
||||
endif
|
||||
for line in lines
|
||||
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
|
||||
" Make sure the directory exists.
|
||||
let directory = fnamemodify(a:tagsfile, ':h')
|
||||
if !isdirectory(directory)
|
||||
call mkdir(directory, 'p')
|
||||
endif
|
||||
" Write the new contents to a temporary file and atomically rename the
|
||||
" temporary file into place while preserving the file's permissions.
|
||||
return xolox#misc#perm#update(a:tagsfile, lines)
|
||||
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
|
||||
let cache = self['canonicalize_cache']
|
||||
if !empty(a:pathname)
|
||||
if !has_key(cache, a:pathname)
|
||||
let cache[a:pathname] = xolox#easytags#utils#canonicalize(a:pathname)
|
||||
endif
|
||||
return cache[a:pathname]
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
function cache.exists(pathname) dict
|
||||
let cache = self['exists_cache']
|
||||
if !empty(a:pathname)
|
||||
if !has_key(cache, a:pathname)
|
||||
let cache[a:pathname] = filereadable(a:pathname)
|
||||
endif
|
||||
return cache[a:pathname]
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
return cache
|
||||
endfunction
|
||||
@@ -1,20 +0,0 @@
|
||||
" 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
|
||||
@@ -1,7 +0,0 @@
|
||||
" The version of my miscellaneous scripts.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: March 15, 2015
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
let g:xolox#misc#version = '1.17.2'
|
||||
@@ -1,261 +0,0 @@
|
||||
" Asynchronous Vim script evaluation.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: September 17, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" The `xolox#misc#async#call()` function builds on top of `xolox#misc#os#exec()`
|
||||
" to support asynchronous evaluation of Vim scripts. The first (and for now
|
||||
" only) use case is my [vim-easytags][] plug-in which has a bunch of
|
||||
" conflicting requirements:
|
||||
"
|
||||
" 1. I want the [vim-easytags][] plug-in to be as portable as possible.
|
||||
" Ideally everything is implemented in Vim script because that's the only
|
||||
" thing I can rely on to be available for all potential users of the
|
||||
" plug-in!
|
||||
"
|
||||
" 2. Because of point one I've been forced to implement tags file reading,
|
||||
" parsing, (fold case) sorting and writing in Vim script. This is fine for
|
||||
" small tags files but once they grow to a couple of megabytes it becomes
|
||||
" annoying because Vim is unresponsive during tags file updates (key
|
||||
" presses are fortunately buffered due to Vim's input model but that
|
||||
" doesn't make it a nice user experience :-).
|
||||
"
|
||||
" 3. I could (and did in the past) come up with all sorts of hacks to speed
|
||||
" things up without switching away from Vim script, but none of them are
|
||||
" going to solve the fundamental problem that Vim's unresponsive hiccups
|
||||
" become longer as tags files grow larger.
|
||||
"
|
||||
" By now it should be clear where this is heading: _Why not handle tags file
|
||||
" updates in a Vim process that runs in the background without blocking the
|
||||
" Vim process that the user is interacting with?_ It turns out that there are
|
||||
" quite a few details to take care of, but with those out of the way, it might
|
||||
" just work! I'm actually hoping to make asynchronous updates the default mode
|
||||
" in [vim-easytags][]. This means I need this functionality to be as
|
||||
" portable and robust as possible.
|
||||
"
|
||||
" **Status:** This code has seen little testing so I wouldn't trust it too
|
||||
" much just yet. On the other hand, as I said, my intention is to make this
|
||||
" functionality as portable and robust as possible. You be the judge :-).
|
||||
"
|
||||
" [vim-easytags]: http://peterodding.com/code/vim/easytags/
|
||||
|
||||
if !exists('g:xolox#misc#async#counter')
|
||||
" Increasing integer number used to match asynchronous responses to the
|
||||
" requests that generated them.
|
||||
let g:xolox#misc#async#counter = 1
|
||||
endif
|
||||
|
||||
if !exists('g:xolox#misc#async#requests')
|
||||
" Queue of asynchronous requests that haven't received a response yet.
|
||||
let g:xolox#misc#async#requests = {}
|
||||
endif
|
||||
|
||||
function! xolox#misc#async#call(options) " {{{1
|
||||
" Call a Vim script function asynchronously by starting a hidden Vim process
|
||||
" in the background. Once the function returns the hidden Vim process
|
||||
" terminates itself. This function takes a single argument which is a
|
||||
" dictionary with the following key/value pairs:
|
||||
"
|
||||
" - **function** (required): The name of the Vim function to call inside
|
||||
" the child process (a string). I suggest using an [autoload][] function
|
||||
" for this, see below.
|
||||
"
|
||||
" - **arguments** (optional): A list of arguments to pass to the function.
|
||||
" This list is serialized to a string using [string()][] and deserialized
|
||||
" using [eval()][].
|
||||
"
|
||||
" - **callback** (optional): The name of a Vim function to call in the
|
||||
" parent process when the child process has completed (a string).
|
||||
"
|
||||
" - **clientserver** (optional): If this is true (1) the child process will
|
||||
" notify the parent process when it has finished (the default is true).
|
||||
" This works using Vim's client/server support which is not always
|
||||
" available. As a fall back Vim's [CursorHold][] automatic command is
|
||||
" also supported (although the effect is not quite as instantaneous :-).
|
||||
"
|
||||
" This functionality is experimental and non trivial to use, so consider
|
||||
" yourself warned :-).
|
||||
"
|
||||
" **Limitations**
|
||||
"
|
||||
" I'm making this functionality available in [vim-misc][] because I think it
|
||||
" can be useful to other plug-ins, however if you are going to use it you
|
||||
" should be aware of the following limitations:
|
||||
"
|
||||
" - Because of the use of multiple processes this functionality is only
|
||||
" suitable for 'heavy' tasks.
|
||||
"
|
||||
" - The function arguments are serialized to a string which is passed to
|
||||
" the hidden Vim process as a command line argument, so the amount of
|
||||
" data you can pass will be limited by your operating environment.
|
||||
"
|
||||
" - The hidden Vim process is explicitly isolated from the user in several
|
||||
" ways (see below for more details). This is to make sure that the hidden
|
||||
" Vim processes are fast and don't clobber the user's editing sessions in
|
||||
" any way.
|
||||
"
|
||||
" **Changes to how Vim normally works**
|
||||
"
|
||||
" You have to be aware that the hidden Vim process is initialized in a
|
||||
" specific way that is very different from your regular Vim editing
|
||||
" sessions:
|
||||
"
|
||||
" - Your [vimrc][] file is ignored using the `-u NONE` command line option.
|
||||
"
|
||||
" - Your [gvimrc][] file (if you even knew it existed ;-) is ignored using
|
||||
" the `-U NONE` command line option.
|
||||
"
|
||||
" - Plug-in loading is skipped using the `--noplugin` command line option.
|
||||
"
|
||||
" - Swap files (see [swap-file][]) are disabled using the `-n` command line
|
||||
" option. This makes sure asynchronous Vim processes don't disturb the
|
||||
" user's editing session.
|
||||
"
|
||||
" - Your [viminfo][] file is ignored using the `-i NONE` command line
|
||||
" option. Just like with swap files this makes sure asynchronous Vim
|
||||
" processes don't disturb the user's editing session.
|
||||
"
|
||||
" - No-compatible mode is enabled using the `-N` command line option
|
||||
" (usually the existence of your vimrc script would have achieved the
|
||||
" same effect but since we disable loading of your vimrc we need to spell
|
||||
" things out for Vim).
|
||||
"
|
||||
" **Use an auto-load function**
|
||||
"
|
||||
" The function you want to call is identified by its name which has to be
|
||||
" defined, but I just explained above that all regular initialization is
|
||||
" disabled for asynchronous Vim processes, so what gives? The answer is to
|
||||
" use an [autoload][] function. This should work fine because the
|
||||
" asynchronous Vim process 'inherits' the value of the ['runtimepath'][]
|
||||
" option from your editing session.
|
||||
"
|
||||
" ['runtimepath']: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath'
|
||||
" [autoload]: http://vimdoc.sourceforge.net/htmldoc/eval.html#autoload
|
||||
" [CursorHold]: http://vimdoc.sourceforge.net/htmldoc/autocmd.html#CursorHold
|
||||
" [eval()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#eval()
|
||||
" [gvimrc]: http://vimdoc.sourceforge.net/htmldoc/gui.html#gvimrc
|
||||
" [string()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#string()
|
||||
" [swap-file]: http://vimdoc.sourceforge.net/htmldoc/recover.html#swap-file
|
||||
" [vim-misc]: http://peterodding.com/code/vim/misc/
|
||||
" [viminfo]: http://vimdoc.sourceforge.net/htmldoc/starting.html#viminfo
|
||||
" [vimrc]: http://vimdoc.sourceforge.net/htmldoc/starting.html#vimrc
|
||||
let unique_number = g:xolox#misc#async#counter
|
||||
let g:xolox#misc#async#counter += 1
|
||||
let request = {'function': a:options['function']}
|
||||
let request['arguments'] = get(a:options, 'arguments', [])
|
||||
let request['starttime'] = xolox#misc#timer#start()
|
||||
let request['number'] = unique_number
|
||||
let callback = get(a:options, 'callback')
|
||||
if !empty(callback)
|
||||
let request['callback'] = callback
|
||||
endif
|
||||
if get(a:options, 'clientserver', 1) && !empty(v:servername)
|
||||
let request['servername'] = v:servername
|
||||
else
|
||||
let temporary_file = tempname()
|
||||
let request['temporary_file'] = temporary_file
|
||||
endif
|
||||
let vim_command = printf('let &rtp = %s | call xolox#misc#async#inside_child(%s)', string(&rtp), string(request))
|
||||
call xolox#misc#msg#debug("vim-misc %s: Generated asynchronous Vim command #%i: %s", g:xolox#misc#version, unique_number, vim_command)
|
||||
let quoted_program = xolox#misc#escape#shell(xolox#misc#os#find_vim('vim'))
|
||||
let quoted_command = xolox#misc#escape#shell(vim_command)
|
||||
let shell_command = printf('%s -u NONE -U NONE --noplugin -n -N -i NONE --cmd %s', quoted_program, quoted_command)
|
||||
call xolox#misc#msg#debug("vim-misc %s: Generated asynchronous shell command #%i: %s", g:xolox#misc#version, unique_number, shell_command)
|
||||
call xolox#misc#os#exec({'command': shell_command, 'async': 1})
|
||||
let g:xolox#misc#async#requests[unique_number] = request
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#async#inside_child(request) " {{{1
|
||||
" Entry point inside the hidden Vim process that runs in the background.
|
||||
" Invoked indirectly by `xolox#misc#async#call()` because it runs a command
|
||||
" similar to the following:
|
||||
"
|
||||
" vim --cmd 'call xolox#misc#async#inside_child(...)'
|
||||
"
|
||||
" This function is responsible for calling the user defined function,
|
||||
" capturing exceptions and reporting the results back to the parent Vim
|
||||
" process using Vim's client/server support or a temporary file.
|
||||
try
|
||||
let response = {'number': a:request['number']}
|
||||
let starttime = xolox#misc#timer#start()
|
||||
try
|
||||
" Call the user defined function and store its result.
|
||||
let response['result'] = call(a:request['function'], a:request['arguments'])
|
||||
catch
|
||||
" Intercept errors raised by the user defined function.
|
||||
let response['exception'] = v:exception
|
||||
let response['throwpoint'] = v:throwpoint
|
||||
endtry
|
||||
" Record the elapsed time.
|
||||
let response['elapsed_time'] = xolox#misc#timer#convert(starttime)
|
||||
" Communicate the results back to the master Vim process.
|
||||
let servername = get(a:request, 'servername', '')
|
||||
if !empty(servername)
|
||||
" Actively notify the parent process using Vim's client/server support?
|
||||
call remote_expr(servername, printf('xolox#misc#async#callback_to_parent(%s)', string(response)))
|
||||
else
|
||||
" 'Passively' notify the parent process by creating the expected
|
||||
" temporary file.
|
||||
call xolox#misc#persist#save(a:request['temporary_file'], response)
|
||||
endif
|
||||
finally
|
||||
" Make sure we terminate this hidden Vim process.
|
||||
quitall!
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#async#callback_to_parent(response) " {{{1
|
||||
" When Vim was compiled with client/server support this function (in the
|
||||
" parent process) will be called by `xolox#misc#async#inside_child()` (in
|
||||
" the child process) after the user defined function has returned. This
|
||||
" enables more or less instant callbacks after running an asynchronous
|
||||
" function.
|
||||
let unique_number = a:response['number']
|
||||
let request = g:xolox#misc#async#requests[unique_number]
|
||||
call xolox#misc#timer#stop("vim-misc %s: Processing asynchronous callback #%i after %s ..", g:xolox#misc#version, unique_number, request['starttime'])
|
||||
call remove(g:xolox#misc#async#requests, unique_number)
|
||||
let callback = get(request, 'callback')
|
||||
if !empty(callback)
|
||||
call call(callback, [a:response])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#async#periodic_callback() " {{{1
|
||||
" When client/server support is not being used the vim-misc plug-in
|
||||
" improvises: It uses Vim's [CursorHold][] event to periodically check if an
|
||||
" asynchronous process has written its results to one of the expected
|
||||
" temporary files. If a response is found the temporary file is read and
|
||||
" deleted and then `xolox#misc#async#callback_to_parent()` is called to
|
||||
" process the response.
|
||||
"
|
||||
" [CursorHold]: http://vimdoc.sourceforge.net/htmldoc/autocmd.html#CursorHold
|
||||
if !empty(g:xolox#misc#async#requests)
|
||||
let num_processed = 0
|
||||
call xolox#misc#msg#debug("vim-misc %s: Checking for asynchronous responses (%i responses not yet received) ..", g:xolox#misc#version, len(g:xolox#misc#async#requests))
|
||||
for unique_number in sort(keys(g:xolox#misc#async#requests))
|
||||
let request = g:xolox#misc#async#requests[unique_number]
|
||||
let temporary_file = get(request, 'temporary_file', '')
|
||||
if !empty(temporary_file) && getfsize(temporary_file) > 0
|
||||
try
|
||||
call xolox#misc#msg#debug("vim-misc %s: Found asynchronous response by %s in %s ..", g:xolox#misc#version, request['function'], temporary_file)
|
||||
call xolox#misc#async#callback_to_parent(xolox#misc#persist#load(temporary_file))
|
||||
let num_processed += 1
|
||||
finally
|
||||
call delete(temporary_file)
|
||||
endtry
|
||||
endif
|
||||
endfor
|
||||
call xolox#misc#msg#debug("vim-misc %s: Processed %i asynchronous responses (%i responses not yet received).", g:xolox#misc#version, num_processed, len(g:xolox#misc#async#requests))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" }}}1
|
||||
|
||||
" The interval in the options below is set to one (1) although the default
|
||||
" value for &updatetime is four seconds. Because vim-misc never modifies
|
||||
" &updatetime the interval will effectively default to four seconds unless the
|
||||
" user has set &updatetime to a lower value themselves.
|
||||
call xolox#misc#cursorhold#register({'function': 'xolox#misc#async#periodic_callback', 'interval': 1})
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,80 +0,0 @@
|
||||
" Handling of special buffers
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: May 19, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" The functions defined here make it easier to deal with special Vim buffers
|
||||
" that contain text generated by a Vim plug-in. For example my [vim-notes
|
||||
" plug-in] [vim-notes] generates several such buffers:
|
||||
"
|
||||
" - [:RecentNotes] [RecentNotes] lists recently modified notes
|
||||
" - [:ShowTaggedNotes] [ShowTaggedNotes] lists notes grouped by tags
|
||||
" - etc.
|
||||
"
|
||||
" Because the text in these buffers is generated, Vim shouldn't bother with
|
||||
" swap files and it should never prompt the user whether to save changes to
|
||||
" the generated text.
|
||||
"
|
||||
" [vim-notes]: http://peterodding.com/code/vim/notes/
|
||||
" [RecentNotes]: http://peterodding.com/code/vim/notes/#recentnotes_command
|
||||
" [ShowTaggedNotes]: http://peterodding.com/code/vim/notes/#showtaggednotes_command
|
||||
|
||||
function! xolox#misc#buffer#is_empty() " {{{1
|
||||
" Checks if the current buffer is an empty, unchanged buffer which can be
|
||||
" reused. Returns 1 if an empty buffer is found, 0 otherwise.
|
||||
return !&modified && expand('%') == '' && line('$') <= 1 && getline(1) == ''
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#buffer#prepare(...) " {{{1
|
||||
" Open a special buffer, i.e. a buffer that will hold generated contents,
|
||||
" not directly edited by the user. The buffer can be customized by passing a
|
||||
" dictionary with the following key/value pairs as the first argument:
|
||||
"
|
||||
" - **name** (required): The base name of the buffer (i.e. the base name of
|
||||
" the file loaded in the buffer, even though it isn't really a file and
|
||||
" nothing is really 'loaded' :-)
|
||||
" - **path** (required): The pathname of the buffer. May be relevant if
|
||||
" [:lcd] [lcd] or ['autochdir'] [acd] is being used.
|
||||
"
|
||||
" [lcd]: http://vimdoc.sourceforge.net/htmldoc/editing.html#:lcd
|
||||
" [acd]: http://vimdoc.sourceforge.net/htmldoc/options.html#'autochdir'
|
||||
if a:0 == 1 && type(a:1) == type('')
|
||||
" Backwards compatibility with old interface.
|
||||
let options = {'name': a:1, 'path': a:1}
|
||||
elseif type(a:1) == type({})
|
||||
let options = a:1
|
||||
else
|
||||
throw "Invalid arguments"
|
||||
endif
|
||||
let winnr = 1
|
||||
let found = 0
|
||||
for bufnr in tabpagebuflist()
|
||||
if xolox#misc#path#equals(options['path'], bufname(bufnr))
|
||||
execute winnr . 'wincmd w'
|
||||
let found = 1
|
||||
break
|
||||
else
|
||||
let winnr += 1
|
||||
endif
|
||||
endfor
|
||||
if !(found || xolox#misc#buffer#is_empty())
|
||||
vsplit
|
||||
endif
|
||||
silent execute 'edit' fnameescape(options['path'])
|
||||
lcd " clear working directory
|
||||
setlocal buftype=nofile bufhidden=hide noswapfile
|
||||
let &l:statusline = '[' . options['name'] . ']'
|
||||
call xolox#misc#buffer#unlock()
|
||||
silent %delete
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#buffer#lock() " {{{1
|
||||
" Lock a special buffer so that its contents can no longer be edited.
|
||||
setlocal readonly nomodifiable nomodified
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#buffer#unlock() " {{{1
|
||||
" Unlock a special buffer so that its content can be updated.
|
||||
setlocal noreadonly modifiable
|
||||
endfunction
|
||||
@@ -1,26 +0,0 @@
|
||||
" Tab completion for user defined commands.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: July 9, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#complete#keywords(arglead, cmdline, cursorpos)
|
||||
" This function can be used to perform keyword completion for user defined
|
||||
" Vim commands based on the contents of the current buffer. Here's an
|
||||
" example of how you would use it:
|
||||
"
|
||||
" :command -nargs=* -complete=customlist,xolox#misc#complete#keywords MyCmd call s:MyCmd(<f-args>)
|
||||
let words = {}
|
||||
for line in getline(1, '$')
|
||||
for word in split(line, '\W\+')
|
||||
let words[word] = 1
|
||||
endfor
|
||||
endfor
|
||||
let arguments = [keys(filter(words, 'v:key =~# a:arglead'))]
|
||||
if &ignorecase
|
||||
call add(arguments, 1)
|
||||
endif
|
||||
return call('sort', arguments)
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,71 +0,0 @@
|
||||
" Rate limiting for Vim's CursorHold event.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 22, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" Several of my Vim plug-ins (e.g. [vim-easytags][], [vim-notes][] and
|
||||
" [vim-session][]) use Vim's [CursorHold][] and [CursorHoldI][] events to
|
||||
" perform periodic tasks when the user doesn't press any keys for a couple of
|
||||
" seconds. These events by default fire after four seconds, this is
|
||||
" configurable using Vim's ['updatetime'][] option. The problem that this
|
||||
" script solves is that there are Vim plug-ins which set the ['updatetime'][]
|
||||
" option to unreasonably low values, thereby breaking my Vim plug-ins and
|
||||
" probably a lot of other Vim plug-ins out there. When users complain about
|
||||
" this I can tell them that another Vim plug-in is to blame, but users don't
|
||||
" care for the difference, their Vim is broken! So I implemented a workaround.
|
||||
" This script enables registration of [CursorHold][] event handlers with a
|
||||
" configurable interval (expressed in seconds). The event handlers will be
|
||||
" called no more than once every interval.
|
||||
"
|
||||
" ['updatetime']: http://vimdoc.sourceforge.net/htmldoc/options.html#'updatetime'
|
||||
" [CursorHold]: http://vimdoc.sourceforge.net/htmldoc/autocmd.html#CursorHold
|
||||
" [CursorHoldI]: http://vimdoc.sourceforge.net/htmldoc/autocmd.html#CursorHoldI
|
||||
" [vim-easytags]: http://peterodding.com/code/vim/easytags/
|
||||
" [vim-notes]: http://peterodding.com/code/vim/notes/
|
||||
" [vim-session]: http://peterodding.com/code/vim/session/
|
||||
|
||||
if !exists('g:xolox#misc#cursorhold#handlers')
|
||||
let g:xolox#misc#cursorhold#handlers = []
|
||||
endif
|
||||
|
||||
function! xolox#misc#cursorhold#register(options)
|
||||
" Register a [CursorHold][] event handler with a custom interval. This
|
||||
" function takes a single argument which is a dictionary with the following
|
||||
" fields:
|
||||
"
|
||||
" - **function** (required): The name of the event handler function (a
|
||||
" string).
|
||||
"
|
||||
" - **arguments** (optional): A list of arguments to pass to the event
|
||||
" handler function (defaults to an empty list).
|
||||
"
|
||||
" - **interval** (optional): The number of seconds between calls to the
|
||||
" event handler (defaults to 4).
|
||||
call add(g:xolox#misc#cursorhold#handlers, copy(a:options))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#cursorhold#autocmd()
|
||||
" The 'top level event handler' that's called by Vim whenever the
|
||||
" [CursorHold][] or [CursorHoldI][] event fires. It iterates through the
|
||||
" event handlers registered using `xolox#misc#cursorhold#register()` and
|
||||
" calls each event handler at the appropriate interval, keeping track of
|
||||
" the time when each event handler was last run.
|
||||
for handler in g:xolox#misc#cursorhold#handlers
|
||||
let function = handler['function']
|
||||
let last_run = get(handler, 'last_run', 0)
|
||||
let interval = get(handler, 'interval', 4)
|
||||
call xolox#misc#msg#debug("vim-misc %s: Checking handler %s with interval %i and last run %i ..", g:xolox#misc#version, function, interval, last_run)
|
||||
" Rate limit in case &updatetime is set (very) low.
|
||||
let time_until_next_run = (last_run + interval) - localtime()
|
||||
if time_until_next_run > 0
|
||||
call xolox#misc#msg#debug("vim-misc %s: Rate limiting handler %s (time until next run: %i seconds).", g:xolox#misc#version, function, time_until_next_run)
|
||||
else
|
||||
call xolox#misc#msg#debug("vim-misc %s: Running handler %s ..", g:xolox#misc#version, function)
|
||||
call call(function, get(handler, 'arguments', []))
|
||||
let handler['last_run'] = localtime()
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
Binary file not shown.
@@ -1,56 +0,0 @@
|
||||
" String escaping functions.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: May 19, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#escape#pattern(string) " {{{1
|
||||
" Takes a single string argument and converts it into a [:substitute]
|
||||
" [subcmd] / [substitute()] [subfun] pattern string that matches the given
|
||||
" string literally.
|
||||
"
|
||||
" [subfun]: http://vimdoc.sourceforge.net/htmldoc/eval.html#substitute()
|
||||
" [subcmd]: http://vimdoc.sourceforge.net/htmldoc/change.html#:substitute
|
||||
if type(a:string) == type('')
|
||||
let string = escape(a:string, '^$.*\~[]')
|
||||
return substitute(string, '\n', '\\n', 'g')
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#escape#substitute(string) " {{{1
|
||||
" Takes a single string argument and converts it into a [:substitute]
|
||||
" [subcmd] / [substitute()] [subfun] replacement string that inserts the
|
||||
" given string literally.
|
||||
if type(a:string) == type('')
|
||||
let string = escape(a:string, '\&~%')
|
||||
return substitute(string, '\n', '\\r', 'g')
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#escape#shell(string) " {{{1
|
||||
" Takes a single string argument and converts it into a quoted command line
|
||||
" argument.
|
||||
"
|
||||
" I was going to add a long rant here about Vim's ['shellslash' option]
|
||||
" [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
|
||||
" Microsoft Windows).
|
||||
"
|
||||
" [shellslash]: http://vimdoc.sourceforge.net/htmldoc/options.html#'shellslash'
|
||||
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
|
||||
@@ -1,46 +0,0 @@
|
||||
" Human friendly string formatting for Vim.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 2, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#format#pluralize(count, singular, plural) " {{{1
|
||||
" Concatenate a counter (the first argument, expected to be an integer) with
|
||||
" a singular or plural label (the second and third arguments, both expected
|
||||
" to be strings).
|
||||
if a:count == 0
|
||||
return printf('no %s', a:plural)
|
||||
else
|
||||
return printf('%i %s', a:count, a:count == 1 ? a:singular : a:plural)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#format#timestamp(ts) " {{{1
|
||||
" Format a time stamp (a string containing a formatted floating point
|
||||
" number) into a human friendly format, for example 70 seconds is phrased as
|
||||
" "1 minute and 10 seconds".
|
||||
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 [['day', 60 * 60 * 24], ['hour', 60 * 60], ['minute', 60], ['second', 1]]
|
||||
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
|
||||
@@ -1,42 +0,0 @@
|
||||
" List handling functions.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 2, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#list#unique(list) " {{{1
|
||||
" Remove duplicate values from the given list in-place (preserves order).
|
||||
call reverse(a:list)
|
||||
call filter(a:list, 'count(a:list, v:val) == 1')
|
||||
return reverse(a:list)
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#list#binsert(list, value, ...) " {{{1
|
||||
" Performs in-place binary insertion, which depending on your use case can
|
||||
" be more efficient than calling Vim's [sort()] [sort] function after each
|
||||
" insertion (in cases where a single, final sort is not an option). Expects
|
||||
" three arguments:
|
||||
"
|
||||
" 1. A list
|
||||
" 2. A value to insert
|
||||
" 3. 1 (true) when case should be ignored, 0 (false) otherwise
|
||||
"
|
||||
" [sort]: http://vimdoc.sourceforge.net/htmldoc/eval.html#sort()
|
||||
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
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,129 +0,0 @@
|
||||
" Functions to interact with the user.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: March 15, 2015
|
||||
" 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
|
||||
|
||||
function! xolox#misc#msg#info(...) " {{{1
|
||||
" Show a formatted informational message to the user.
|
||||
"
|
||||
" This function has the same argument handling as Vim's [printf()] []
|
||||
" function with one notable difference: Any arguments which are not numbers
|
||||
" or strings are coerced to strings using Vim's [string()] [] function.
|
||||
"
|
||||
" In the case of `xolox#misc#msg#info()`, automatic string coercion simply
|
||||
" makes the function a bit easier to use.
|
||||
"
|
||||
" The messages emitted by this function have no highlighting. Previously
|
||||
" these messages were highlighted using the [Title group] [hl-title], but it
|
||||
" was pointed out in [pull request 16] [pr-16] that this group shouldn't be
|
||||
" used for informational messages because it is meant for titles and because
|
||||
" of this some color schemes use colors that stand out quite a bit, causing
|
||||
" the informational messages to look like errors.
|
||||
"
|
||||
" [hl-title]: http://vimdoc.sourceforge.net/htmldoc/syntax.html#hl-Title
|
||||
" [pr-16]: https://github.com/xolox/vim-misc/pull/16
|
||||
" [printf()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#printf()
|
||||
" [string()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#string()
|
||||
call s:show_message('None', a:000)
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#msg#warn(...) " {{{1
|
||||
" Show a formatted warning message to the user.
|
||||
"
|
||||
" This function has the same argument handling as the
|
||||
" `xolox#misc#msg#info()` function.
|
||||
call s:show_message('WarningMsg', a:000)
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#msg#debug(...) " {{{1
|
||||
" Show a formatted debugging message to the user, *if the user has enabled
|
||||
" increased verbosity by setting Vim's ['verbose'] [] option to one
|
||||
" (1) or higher*.
|
||||
"
|
||||
" This function has the same argument handling as the
|
||||
" `xolox#misc#msg#info()` function.
|
||||
"
|
||||
" In the case of `xolox#misc#msg#debug()`, automatic string coercion
|
||||
" provides lazy evaluation in the sense that complex data structures are
|
||||
" only converted to strings when the user has enabled increased verbosity.
|
||||
"
|
||||
" ['verbose']: http://vimdoc.sourceforge.net/htmldoc/options.html#'verbose'
|
||||
if &vbs >= 1
|
||||
call s:show_message('Question', a:000)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:show_message(hlgroup, args) " {{{1
|
||||
" The implementation of info() and warn().
|
||||
let nargs = len(a:args)
|
||||
if nargs == 1
|
||||
let message = a:args[0]
|
||||
elseif nargs >= 2
|
||||
let args = map(copy(a:args), 's:coerce_argument(v:val)')
|
||||
let message = call('printf', 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 the |hit-enter| prompt. We use :silent to avoid issues
|
||||
" like this one: https://github.com/xolox/vim-easytags/issues/69.
|
||||
silent! redraw
|
||||
for line in split(message, "\n")
|
||||
echomsg line
|
||||
endfor
|
||||
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:coerce_argument(value) " {{{1
|
||||
" Callback to coerce printf() arguments into strings.
|
||||
let value_type = type(a:value)
|
||||
if value_type != type(0) && value_type != type('')
|
||||
return string(a:value)
|
||||
else
|
||||
return a:value
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:clear_message() " {{{1
|
||||
" Callback to clear message after some time has passed.
|
||||
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
|
||||
@@ -1,100 +0,0 @@
|
||||
" Integration between Vim and its environment.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 19, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
let s:enoimpl = "vim-misc %s: %s() hasn't been implemented for your platform! If you have suggestions, please get in touch at https://github.com/xolox/vim-misc/issues"
|
||||
let s:handlers = ['gnome-open', 'kde-open', 'exo-open', 'xdg-open', 'cygstart']
|
||||
|
||||
function! xolox#misc#open#file(location, ...) " {{{1
|
||||
" Given a pathname or URL as the first argument, this opens the file with
|
||||
" the program associated with the file type. So for example a text file
|
||||
" might open in Vim, an `*.html` file would probably open in your web
|
||||
" browser and a media file would open in a media player.
|
||||
"
|
||||
" This should work on Windows, Mac OS X and most Linux distributions. If
|
||||
" this fails to find a file association, you can pass one or more external
|
||||
" commands to try as additional arguments. For example:
|
||||
"
|
||||
" :call xolox#misc#open#file('/path/to/my/file', 'firefox', 'google-chrome')
|
||||
"
|
||||
" This generally shouldn't be necessary but it might come in handy now and
|
||||
" then.
|
||||
if xolox#misc#os#is_win()
|
||||
try
|
||||
call xolox#shell#open_with_windows_shell(a:location)
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
let command = '!start CMD /C START "" %s'
|
||||
silent execute printf(command, xolox#misc#escape#shell(a:location))
|
||||
endtry
|
||||
return
|
||||
elseif xolox#misc#os#is_mac()
|
||||
call xolox#misc#msg#debug("vim-misc %s: Detected Mac OS X, using 'open' command to open %s ..", g:xolox#misc#version, string(a:location))
|
||||
let cmd = 'open ' . shellescape(a:location) . ' 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("vim-misc %s: Using '%s' to open '%s'.", g:xolox#misc#version, handler, a:location)
|
||||
let cmd = shellescape(handler) . ' ' . shellescape(a:location) . ' 2>&1'
|
||||
call s:handle_error(cmd, system(cmd))
|
||||
return
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
throw printf(s:enoimpl, g:xolox#misc#version, 'xolox#misc#open#file')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#open#url(url) " {{{1
|
||||
" Given a URL as the first argument, this opens the URL in your preferred or
|
||||
" best available web browser:
|
||||
"
|
||||
" - In GUI environments a graphical web browser will open (or a new tab will
|
||||
" be created in an existing window)
|
||||
" - In console Vim without a GUI environment, when you have any of `lynx`,
|
||||
" `links` or `w3m` installed it will launch a command line web browser in
|
||||
" front of Vim (temporarily suspending Vim)
|
||||
let url = a:url
|
||||
if url !~ '^\w\+://'
|
||||
call xolox#misc#msg#debug("vim-misc %s: The URL %s doesn't contain a scheme, improvising ..", g:xolox#misc#version, string(url))
|
||||
if url !~ '@'
|
||||
call xolox#misc#msg#debug("vim-misc %s: Defaulting to http:// URL scheme ..", g:xolox#misc#version)
|
||||
let url = 'http://' . url
|
||||
elseif url !~ '^mailto:'
|
||||
call xolox#misc#msg#debug("vim-misc %s: Defaulting to mailto: URL scheme ..", g:xolox#misc#version)
|
||||
let url = 'mailto:' . url
|
||||
endif
|
||||
endif
|
||||
let on_unix = has('unix')
|
||||
let not_on_mac = !xolox#misc#os#is_mac()
|
||||
let no_gui_available = (has('gui_running') == 0 && $DISPLAY == '')
|
||||
if on_unix && not_on_mac && no_gui_available
|
||||
call xolox#misc#msg#debug("vim-misc %s: Using command line web browser because no GUI seems to be available ..", g:xolox#misc#version)
|
||||
for browser in ['lynx', 'links', 'w3m']
|
||||
call xolox#misc#msg#debug("vim-misc %s: Checking whether %s command line web browser is installed ..", g:xolox#misc#version, string(browser))
|
||||
if executable(browser)
|
||||
call xolox#misc#msg#debug("vim-misc %s: Found %s, using it to open %s ..", g:xolox#misc#version, string(browser), string(url))
|
||||
execute '!' . browser fnameescape(url)
|
||||
call s:handle_error(browser . ' ' . url, '')
|
||||
return
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
call xolox#misc#msg#debug("vim-misc %s: Defaulting to GUI web browser to open %s ..", g:xolox#misc#version, string(url))
|
||||
call xolox#misc#open#file(url, 'firefox', 'google-chrome')
|
||||
endfunction
|
||||
|
||||
function! s:handle_error(cmd, output) " {{{1
|
||||
if v:shell_error
|
||||
let message = "vim-misc %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, g:xolox#misc#version, a:cmd, output)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" vim: et ts=2 sw=2 fdm=marker
|
||||
@@ -1,115 +0,0 @@
|
||||
" Vim and plug-in option handling.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 2, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#option#get(name, ...) " {{{1
|
||||
" Expects one or two arguments: 1. The name of a variable and 2. the default
|
||||
" value if the variable does not exist.
|
||||
"
|
||||
" Returns the value of the variable from a buffer local variable, global
|
||||
" variable or the default value, depending on which is defined.
|
||||
"
|
||||
" This is used by some of my Vim plug-ins for option handling, so that users
|
||||
" can customize options for specific buffers.
|
||||
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
|
||||
|
||||
function! xolox#misc#option#split(value) " {{{1
|
||||
" Given a multi-value Vim option like ['runtimepath'] [rtp] this returns a
|
||||
" list of strings. For example:
|
||||
"
|
||||
" :echo xolox#misc#option#split(&runtimepath)
|
||||
" ['/home/peter/Projects/Vim/misc',
|
||||
" '/home/peter/Projects/Vim/colorscheme-switcher',
|
||||
" '/home/peter/Projects/Vim/easytags',
|
||||
" ...]
|
||||
"
|
||||
" [rtp]: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath'
|
||||
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) " {{{1
|
||||
" Given a list of strings like the ones returned by
|
||||
" `xolox#misc#option#split()`, this joins the strings together into a
|
||||
" single value that can be used to set a Vim option.
|
||||
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) " {{{1
|
||||
" Customized version of `xolox#misc#option#split()` with specialized
|
||||
" handling for Vim's ['tags' option] [tags].
|
||||
"
|
||||
" [tags]: http://vimdoc.sourceforge.net/htmldoc/options.html#'tags'
|
||||
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) " {{{1
|
||||
" Customized version of `xolox#misc#option#join()` with specialized
|
||||
" handling for Vim's ['tags' option] [tags].
|
||||
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, ...) " {{{1
|
||||
" Evaluate Vim's ['tags' option] [tags] without looking at the file
|
||||
" system, i.e. this will report tags files that don't exist yet. Expects
|
||||
" the value of the ['tags' option] [tags] as the first argument. If the
|
||||
" optional second argument is 1 (true) only the first match is returned,
|
||||
" otherwise (so by default) a list with all matches is returned.
|
||||
let pathnames = []
|
||||
let first_only = exists('a:1') ? a:1 : 0
|
||||
for pattern in xolox#misc#option#split_tags(a:value)
|
||||
" Make buffer relative pathnames absolute.
|
||||
if pattern =~ '^\./'
|
||||
let suffix = matchstr(pattern, '^./\zs.*$')
|
||||
let pattern = xolox#misc#path#merge(expand('%:p:h'), suffix)
|
||||
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 first_only ? '' : pathnames
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,271 +0,0 @@
|
||||
" Operating system interfaces.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June , 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#os#is_mac() " {{{1
|
||||
" Returns 1 (true) when on Mac OS X, 0 (false) otherwise. You would expect
|
||||
" this to simply check the Vim feature list, but for some obscure reason the
|
||||
" `/usr/bin/vim` included in Mac OS X (verified on version 10.7.5) returns 0
|
||||
" (false) in response to `has('mac')`, so we check the output of `uname`
|
||||
" to avoid false negatives.
|
||||
if !exists('s:is_mac')
|
||||
" By default we assume we are *not* on Mac OS X.
|
||||
let s:is_mac = 0
|
||||
if has('mac') || has('macunix') || has('gui_mac')
|
||||
" If Vim's feature list indicates we are on Mac OS X, we have our answer :-).
|
||||
let s:is_mac = 1
|
||||
elseif !xolox#misc#os#is_win()
|
||||
" Otherwise we check the output of `uname' to avoid false negatives.
|
||||
let result = xolox#misc#os#exec({'command': 'uname', 'check': 0})
|
||||
if result['exit_code'] == 0 && get(result['stdout'], 0, '') == 'Darwin'
|
||||
let s:is_mac = 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return s:is_mac
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#os#is_win() " {{{1
|
||||
" Returns 1 (true) when on Microsoft Windows, 0 (false) otherwise.
|
||||
return has('win16') || has('win32') || has('win64')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#os#find_vim(...) " {{{1
|
||||
" Returns the program name of Vim as a string. On Windows and UNIX this just
|
||||
" [v:progname] [] as an absolute pathname while on Mac OS X there is
|
||||
" some special magic to find MacVim's executable even though it's usually
|
||||
" not on the executable search path. If you want, you can override the
|
||||
" value returned from this function by setting the global variable
|
||||
" `g:xolox#misc#os#vim_progname`.
|
||||
"
|
||||
" By default the choice of console Vim vs graphical Vim is made based on
|
||||
" the value of [v:progname] [], but if you have a preference you can pass
|
||||
" the string `vim` or `gvim` as the first and only argument.
|
||||
"
|
||||
" [v:progname]: http://vimdoc.sourceforge.net/htmldoc/eval.html#v:progname
|
||||
if exists('a:1')
|
||||
let program_name = a:1
|
||||
else
|
||||
let program_name = v:progname
|
||||
endif
|
||||
if exists('g:xolox#misc#os#vim_progname')
|
||||
let pathname = g:xolox#misc#os#vim_progname
|
||||
else
|
||||
let pathname = ''
|
||||
endif
|
||||
if empty(pathname) && xolox#misc#os#is_mac()
|
||||
" Special handling for Mac OS X where MacVim is usually not on the $PATH.
|
||||
" This always returns the "Vim" executable and not "MacVim" (regardless of
|
||||
" the caller's preference) because "MacVim" has funky dock magic going on.
|
||||
call xolox#misc#msg#debug("vim-misc %s: Trying MacVim workaround to find Vim executable ..", g:xolox#misc#version)
|
||||
let segments = xolox#misc#path#split($VIMRUNTIME)
|
||||
if segments[-3:] == ['Resources', 'vim', 'runtime']
|
||||
let pathname = xolox#misc#path#join(segments[0:-4] + ['MacOS', 'Vim'])
|
||||
call xolox#misc#msg#debug("vim-misc %s: The MacVim workaround resulted in the Vim executable %s.", g:xolox#misc#version, string(pathname))
|
||||
endif
|
||||
endif
|
||||
if empty(pathname)
|
||||
" Default logic.
|
||||
call xolox#misc#msg#debug("vim-misc %s: Looking for Vim executable named %s on search path ..", g:xolox#misc#version, string(program_name))
|
||||
let candidates = xolox#misc#path#which(program_name)
|
||||
if !empty(candidates)
|
||||
call xolox#misc#msg#debug("vim-misc %s: Found %i candidate(s) on search path: %s.", g:xolox#misc#version, len(candidates), string(candidates))
|
||||
let pathname = candidates[0]
|
||||
endif
|
||||
endif
|
||||
call xolox#misc#msg#debug("vim-misc %s: Reporting Vim executable %s.", g:xolox#misc#version, string(pathname))
|
||||
return pathname
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#os#exec(options) " {{{1
|
||||
" Execute an external command (hiding the console on Microsoft Windows when
|
||||
" my [vim-shell plug-in] [vim-shell] is installed).
|
||||
"
|
||||
" Expects a dictionary with the following key/value pairs as the first
|
||||
" argument:
|
||||
"
|
||||
" - **command** (required): The command line to execute
|
||||
" - **async** (optional): set this to 1 (true) to execute the command in the
|
||||
" background (asynchronously)
|
||||
" - **stdin** (optional): a string or list of strings with the input for the
|
||||
" external command
|
||||
" - **check** (optional): set this to 0 (false) to disable checking of the
|
||||
" exit code of the external command (by default an exception will be
|
||||
" raised when the command fails)
|
||||
"
|
||||
" Returns a dictionary with one or more of the following key/value pairs:
|
||||
"
|
||||
" - **command** (always available): the generated command line that was used
|
||||
" to run the external command
|
||||
" - **exit_code** (only in synchronous mode): the exit status of the
|
||||
" external command (an integer, zero on success)
|
||||
" - **stdout** (only in synchronous mode): the output of the command on the
|
||||
" standard output stream (a list of strings, one for each line)
|
||||
" - **stderr** (only in synchronous mode): the output of the command on the
|
||||
" standard error stream (as a list of strings, one for each line)
|
||||
"
|
||||
" [vim-shell]: http://peterodding.com/code/vim/shell/
|
||||
try
|
||||
|
||||
" Unpack the options.
|
||||
let cmd = a:options['command']
|
||||
let async = get(a:options, 'async', 0)
|
||||
|
||||
" We need to know in a couple of places whether we are on Windows.
|
||||
let is_win = xolox#misc#os#is_win()
|
||||
|
||||
" Use vim-shell so we don't pop up a console window on Windows? If the
|
||||
" caller specifically asks us *not* to use vim-shell, we'll respect that
|
||||
" choice; this is very useful for automated tests :-).
|
||||
if get(a:options, 'use_dll', 1) == 0
|
||||
let use_dll = 0
|
||||
else
|
||||
let use_dll = xolox#misc#os#can_use_dll()
|
||||
endif
|
||||
|
||||
" Decide whether to redirect the standard output and standard error
|
||||
" streams to temporary files.
|
||||
let redirect_output = !async && (use_dll || !is_win)
|
||||
|
||||
" Write the input for the external command to a temporary file?
|
||||
if has_key(a:options, 'stdin') && use_dll
|
||||
let tempin = tempname()
|
||||
if type(a:options['stdin']) == type([])
|
||||
let lines = a:options['stdin']
|
||||
else
|
||||
let lines = split(a:options['stdin'], "\n")
|
||||
endif
|
||||
call writefile(lines, tempin)
|
||||
let cmd .= ' < ' . xolox#misc#escape#shell(tempin)
|
||||
endif
|
||||
|
||||
" Redirect the standard output and/or standard error streams of the
|
||||
" external process to temporary files? (only in synchronous mode)
|
||||
if redirect_output
|
||||
let tempout = tempname()
|
||||
let temperr = tempname()
|
||||
let cmd = printf('(%s) 1>%s 2>%s', cmd, xolox#misc#escape#shell(tempout), xolox#misc#escape#shell(temperr))
|
||||
endif
|
||||
|
||||
" Use vim-shell or system() to execute the external command?
|
||||
if use_dll
|
||||
call xolox#misc#msg#debug("vim-misc %s: Executing external command using compiled DLL: %s", g:xolox#misc#version, cmd)
|
||||
let exit_code = xolox#shell#execute_with_dll(cmd, async)
|
||||
else
|
||||
|
||||
" Enable asynchronous mode (very platform specific).
|
||||
if async
|
||||
if is_win
|
||||
let cmd = printf('start /b %s', cmd)
|
||||
elseif has('unix')
|
||||
let cmd = printf('(%s) &', cmd)
|
||||
else
|
||||
call xolox#misc#msg#warn("vim-misc %s: I don't know how to execute the command %s asynchronously on your platform! Falling back to synchronous mode...", g:xolox#misc#version, cmd)
|
||||
endif
|
||||
endif
|
||||
|
||||
" On UNIX we explicitly execute the command line using 'sh' instead of
|
||||
" the default shell, because we assume that standard output and standard
|
||||
" error can be redirected separately, but (t)csh does not support this
|
||||
" (and it might be the default shell).
|
||||
if has('unix')
|
||||
call xolox#misc#msg#debug("vim-misc %s: Generated shell expression: %s", g:xolox#misc#version, cmd)
|
||||
let cmd = printf('sh -c %s', xolox#misc#escape#shell(cmd))
|
||||
endif
|
||||
|
||||
" Let the user know what's happening (in case they're interested).
|
||||
if async && is_win
|
||||
call xolox#misc#msg#debug("vim-misc %s: Executing external command using !start command: %s", g:xolox#misc#version, cmd)
|
||||
silent execute '!' . cmd
|
||||
else
|
||||
call xolox#misc#msg#debug("vim-misc %s: Executing external command using system() function: %s", g:xolox#misc#version, cmd)
|
||||
let arguments = [cmd]
|
||||
if has_key(a:options, 'stdin')
|
||||
if type(a:options['stdin']) == type([])
|
||||
call add(arguments, join(a:options['stdin'], "\n"))
|
||||
else
|
||||
call add(arguments, a:options['stdin'])
|
||||
endif
|
||||
endif
|
||||
let stdout = call('system', arguments)
|
||||
let exit_code = v:shell_error
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
" Return the results as a dictionary with one or more key/value pairs.
|
||||
let result = {'command': cmd}
|
||||
if !async
|
||||
let result['exit_code'] = exit_code
|
||||
" Get the standard output of the command.
|
||||
if redirect_output
|
||||
let result['stdout'] = s:readfile(tempout, 'standard output', a:options['command'])
|
||||
elseif exists('stdout')
|
||||
let result['stdout'] = split(stdout, "\n")
|
||||
else
|
||||
let result['stdout'] = []
|
||||
endif
|
||||
" Get the standard error of the command.
|
||||
if exists('temperr')
|
||||
let result['stderr'] = s:readfile(temperr, 'standard error', a:options['command'])
|
||||
else
|
||||
let result['stderr'] = []
|
||||
endif
|
||||
" If we just executed a synchronous command and the caller didn't
|
||||
" specifically ask us *not* to check the exit code of the external
|
||||
" command, we'll do so now. The idea here is that it should be easy
|
||||
" to 'do the right thing'.
|
||||
if get(a:options, 'check', 1) && exit_code != 0
|
||||
" Prepare an error message with enough details so the user can investigate.
|
||||
let msg = printf("vim-misc %s: External command failed with exit code %d!", g:xolox#misc#version, result['exit_code'])
|
||||
let msg .= printf("\nCommand line: %s", result['command'])
|
||||
" If the external command reported an error, we'll include it in our message.
|
||||
if !empty(result['stderr'])
|
||||
" This is where we would normally expect to find an error message.
|
||||
let msg .= printf("\nOutput on standard output stream:\n%s", join(result['stderr'], "\n"))
|
||||
elseif !empty(result['stdout'])
|
||||
" Exuberant Ctags on Windows XP reports errors on standard output :-x.
|
||||
let msg .= printf("\nOutput on standard error stream:\n%s", join(result['stdout'], "\n"))
|
||||
endif
|
||||
throw msg
|
||||
endif
|
||||
endif
|
||||
return result
|
||||
|
||||
finally
|
||||
" Cleanup any temporary files we created.
|
||||
for name in ['tempin', 'tempout', 'temperr']
|
||||
if exists(name)
|
||||
call delete({name})
|
||||
endif
|
||||
endfor
|
||||
endtry
|
||||
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#os#can_use_dll() " {{{1
|
||||
" If a) we're on Microsoft Windows, b) the vim-shell plug-in is installed
|
||||
" and c) the compiled DLL included in vim-shell works, we can use the
|
||||
" vim-shell plug-in to execute external commands! Returns 1 (true)
|
||||
" if we can use the DLL, 0 (false) otherwise.
|
||||
let can_use_dll = 0
|
||||
try
|
||||
let can_use_dll = xolox#shell#can_use_dll()
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
" Silence E117.
|
||||
endtry
|
||||
return can_use_dll
|
||||
endfunction
|
||||
|
||||
function! s:readfile(fname, label, cmd) " {{{1
|
||||
try
|
||||
return readfile(a:fname)
|
||||
catch
|
||||
call xolox#misc#msg#warn("vim-misc %s: Failed to read temporary file (%s) with %s of external command: %s! (external command: %s)", g:xolox#misc#version, a:fname, a:label, v:exception, a:cmd)
|
||||
return []
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,278 +0,0 @@
|
||||
" Pathname manipulation functions.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: July 7, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
let s:windows_compatible = xolox#misc#os#is_win()
|
||||
|
||||
function! xolox#misc#path#which(...) " {{{1
|
||||
" Scan the executable search path (`$PATH`) for one or more external
|
||||
" programs. Expects one or more string arguments with program names. Returns
|
||||
" a list with the absolute pathnames of all found programs. Here's an
|
||||
" example:
|
||||
"
|
||||
" :echo xolox#misc#path#which('gvim', 'vim')
|
||||
" ['/usr/local/bin/gvim',
|
||||
" '/usr/bin/gvim',
|
||||
" '/usr/local/bin/vim',
|
||||
" '/usr/bin/vim']
|
||||
let extensions = s:windows_compatible ? split($PATHEXT, ';') : ['']
|
||||
let matches = []
|
||||
let checked = {}
|
||||
for program in a:000
|
||||
for directory in split($PATH, s:windows_compatible ? ';' : ':')
|
||||
let directory = xolox#misc#path#absolute(directory)
|
||||
if isdirectory(directory)
|
||||
let found = 0
|
||||
for extension in extensions
|
||||
let path = xolox#misc#path#merge(directory, program . extension)
|
||||
if executable(path)
|
||||
call add(matches, path)
|
||||
let found = 1
|
||||
endif
|
||||
endfor
|
||||
if s:windows_compatible && ! found
|
||||
" Maybe the extension is already contained in program; try without
|
||||
" $PATHEXT.
|
||||
let path = xolox#misc#path#merge(directory, program)
|
||||
if executable(path)
|
||||
call add(matches, path)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
return xolox#misc#list#unique(matches)
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#split(path) " {{{1
|
||||
" Split a pathname (the first and only argument) into a list of pathname
|
||||
" components.
|
||||
"
|
||||
" On Windows, pathnames starting with two slashes or backslashes are UNC
|
||||
" paths where the leading slashes are significant... In this case we split
|
||||
" like this:
|
||||
"
|
||||
" - Input: `'//server/share/directory'`
|
||||
" - Result: `['//server', 'share', 'directory']`
|
||||
"
|
||||
" Everything except Windows is treated like UNIX until someone has a better
|
||||
" suggestion :-). In this case we split like this:
|
||||
"
|
||||
" - Input: `'/foo/bar/baz'`
|
||||
" - Result: `['/', 'foo', 'bar', 'baz']`
|
||||
"
|
||||
" To join a list of pathname components back into a single pathname string,
|
||||
" use the `xolox#misc#path#join()` function.
|
||||
if type(a:path) == type('')
|
||||
if s:windows_compatible
|
||||
if a:path =~ '^[\/][\/]'
|
||||
" UNC pathname.
|
||||
return split(a:path, '\%>2c[\/]\+')
|
||||
else
|
||||
" If it's not a UNC pathname we can simply split on slashes and
|
||||
" backslashes, although we should preserve a leading slash (which
|
||||
" denotes a pathname that is 'absolute to the current drive').
|
||||
let absolute = (a:path =~ '^[\/]')
|
||||
let segments = split(a:path, '[\/]\+')
|
||||
return absolute ? insert(segments, a:path[0]) : segments
|
||||
endif
|
||||
else
|
||||
" Everything else is treated as UNIX.
|
||||
let absolute = (a:path =~ '^/')
|
||||
let segments = split(a:path, '/\+')
|
||||
return absolute ? insert(segments, '/') : segments
|
||||
endif
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#join(parts) " {{{1
|
||||
" Join a list of pathname components (the first and only argument) into a
|
||||
" single pathname string. This is the counterpart to the
|
||||
" `xolox#misc#path#split()` function and it expects a list of pathname
|
||||
" components as returned by `xolox#misc#path#split()`.
|
||||
if type(a:parts) == type([])
|
||||
if s:windows_compatible
|
||||
return join(a:parts, xolox#misc#path#directory_separator())
|
||||
elseif get(a:parts, 0) == '/'
|
||||
" Absolute path on UNIX (non-Windows).
|
||||
return '/' . join(a:parts[1:], '/')
|
||||
else
|
||||
" Relative path on UNIX (non-Windows).
|
||||
return join(a:parts, '/')
|
||||
endif
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#directory_separator() " {{{1
|
||||
" Find the preferred directory separator for the platform and settings.
|
||||
return exists('+shellslash') && &shellslash ? '/' : '\'
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#absolute(path) " {{{1
|
||||
" Canonicalize and resolve a pathname, *regardless of whether it exists*.
|
||||
" This is intended to support string comparison to determine whether two
|
||||
" pathnames point to the same directory or file.
|
||||
if type(a:path) == type('')
|
||||
let path = a:path
|
||||
" Make the pathname absolute.
|
||||
if path =~ '^\~'
|
||||
" Expand ~ to $HOME.
|
||||
let path = $HOME . '/' . path[1:]
|
||||
elseif xolox#misc#path#is_relative(path)
|
||||
" Make relative pathnames absolute.
|
||||
let path = getcwd() . '/' . path
|
||||
endif
|
||||
" Resolve symbolic links to find the canonical pathname. In my tests this
|
||||
" also removes all symbolic pathname segments (`.' and `..'), even when
|
||||
" the pathname does not exist. Also there used to be a bug in resolve()
|
||||
" where it wouldn't resolve pathnames ending in a directory separator.
|
||||
" Since it's not much trouble to work around, that's what we do.
|
||||
let path = resolve(substitute(path, s:windows_compatible ? '[\/]\+$' : '/\+$', '', ''))
|
||||
" Normalize directory separators (especially relevant on Windows).
|
||||
let parts = xolox#misc#path#split(path)
|
||||
if s:windows_compatible && parts[0] =~ '^[\/][\/]'
|
||||
" Also normalize the two leading "directory separators" (I'm not
|
||||
" sure what else to call them :-) in Windows UNC pathnames.
|
||||
let parts[0] = repeat(xolox#misc#path#directory_separator(), 2) . parts[0][2:]
|
||||
elseif s:windows_compatible && parts[0] =~ '^[\/]$'
|
||||
" If a pathname is relative to the current drive we should add
|
||||
" the drive letter in order to make the pathname absolute.
|
||||
let parts[0] = matchstr(getcwd(), '^\a:')
|
||||
endif
|
||||
return xolox#misc#path#join(parts)
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#relative(path, base) " {{{1
|
||||
" Make an absolute pathname (the first argument) relative to a directory
|
||||
" (the second argument).
|
||||
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
|
||||
|
||||
function! xolox#misc#path#merge(parent, child, ...) " {{{1
|
||||
" Join a directory pathname and filename into a single pathname.
|
||||
if type(a:parent) == type('') && type(a:child) == type('')
|
||||
" TODO Use xolox#misc#path#is_relative()?
|
||||
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
|
||||
|
||||
function! xolox#misc#path#commonprefix(paths) " {{{1
|
||||
" Find the common prefix of path components in a list of pathnames.
|
||||
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
|
||||
|
||||
function! xolox#misc#path#starts_with(a, b) " {{{1
|
||||
" Check whether the first pathname starts with the second pathname (expected
|
||||
" to be a directory). This does not perform a regular string comparison;
|
||||
" first it normalizes both pathnames, then it splits them into their
|
||||
" pathname segments and then it compares the segments.
|
||||
let a = xolox#misc#path#split(xolox#misc#path#absolute(a:a))
|
||||
let b = xolox#misc#path#split(xolox#misc#path#absolute(a:b))
|
||||
return a[0 : len(b) - 1] == b
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#encode(path) " {{{1
|
||||
" Encode a pathname so it can be used as a filename. This uses URL encoding
|
||||
" to encode special characters.
|
||||
if s:windows_compatible
|
||||
let mask = '[*|\\/:"<>?%]'
|
||||
elseif xolox#misc#os#is_mac()
|
||||
let mask = '[\\/%:]'
|
||||
else
|
||||
let mask = '[\\/%]'
|
||||
endif
|
||||
return substitute(a:path, mask, '\=printf("%%%x", char2nr(submatch(0)))', 'g')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#decode(encoded_path) " {{{1
|
||||
" Decode a pathname previously encoded with `xolox#misc#path#encode()`.
|
||||
return substitute(a:encoded_path, '%\(\x\x\?\)', '\=nr2char("0x" . submatch(1))', 'g')
|
||||
endfunction
|
||||
|
||||
" xolox#misc#path#equals(a, b) - Check whether two pathnames point to the same file. {{{1
|
||||
|
||||
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
|
||||
|
||||
function! xolox#misc#path#is_relative(path) " {{{1
|
||||
" Returns true (1) when the pathname given as the first argument is
|
||||
" relative, false (0) otherwise.
|
||||
if a:path =~ '^\w\+://'
|
||||
return 0
|
||||
elseif s:windows_compatible
|
||||
return a:path !~ '^\(\w:\|[\\/]\)'
|
||||
else
|
||||
return a:path !~ '^/'
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#path#tempdir() " {{{1
|
||||
" Create a temporary directory and return the pathname of the directory.
|
||||
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 /^Vim\%((\a\+)\)\=:E739/
|
||||
" Keep looking for a non-existing directory.
|
||||
endtry
|
||||
let s:tempdir_counter += 1
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,100 +0,0 @@
|
||||
" Manipulation of UNIX file permissions.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 30, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" Vim's [writefile()][] function cannot set file permissions for newly created
|
||||
" files and although Vim script has a function to get file permissions (see
|
||||
" [getfperm()][]) there is no equivalent for changing a file's permissions.
|
||||
"
|
||||
" This omission breaks the otherwise very useful idiom of updating a file by
|
||||
" writing its new contents to a temporary file and then renaming the temporary
|
||||
" file into place (which is as close as you're going to get to atomically
|
||||
" updating a file's contents on UNIX) because the file's permissions will not
|
||||
" be preserved!
|
||||
"
|
||||
" **Here's a practical example:** My [vim-easytags][] plug-in writes tags file
|
||||
" updates to a temporary file and renames the temporary file into place. When
|
||||
" I use `sudo -s` on Ubuntu Linux it preserves my environment variables so my
|
||||
" `~/.vimrc` and the [vim-easytags][] plug-in are still loaded. Now when a
|
||||
" tags file is written the file becomes owned by root (my effective user id in
|
||||
" the `sudo` session). Once I leave the `sudo` session I can no longer update
|
||||
" my tags file because it's now owned by root … ಠ_ಠ
|
||||
"
|
||||
" [getfperm()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#getfperm()
|
||||
" [vim-easytags]: http://peterodding.com/code/vim/easytags/
|
||||
" [writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
|
||||
|
||||
function! xolox#misc#perm#update(fname, contents)
|
||||
" Atomically update a file's contents while preserving the owner, group and
|
||||
" mode. The first argument is the pathname of the file to update (a string).
|
||||
" The second argument is the list of lines to be written to the file. Writes
|
||||
" the new contents to a temporary file and renames the temporary file into
|
||||
" place, thereby preventing readers from reading a partially written file.
|
||||
" Returns 1 if the file is successfully updated, 0 otherwise.
|
||||
"
|
||||
" Note that if `xolox#misc#perm#get()` and `xolox#misc#perm#set()` cannot be
|
||||
" used to preserve the file owner/group/mode the file is still updated using
|
||||
" a rename (for compatibility with non-UNIX systems and incompatible
|
||||
" `/usr/bin/stat` implementations) so in that case you can still lose the
|
||||
" file's owner/group/mode.
|
||||
let starttime = xolox#misc#timer#start()
|
||||
let temporary_file = printf('%s.tmp', a:fname)
|
||||
call xolox#misc#msg#debug("vim-misc %s: Writing new contents of %s to temporary file %s ..", g:xolox#misc#version, a:fname, temporary_file)
|
||||
if writefile(a:contents, temporary_file) == 0
|
||||
call xolox#misc#perm#set(temporary_file, xolox#misc#perm#get(a:fname))
|
||||
call xolox#misc#msg#debug("vim-misc %s: Replacing %s with %s ..", g:xolox#misc#version, a:fname, temporary_file)
|
||||
if rename(temporary_file, a:fname) == 0
|
||||
call xolox#misc#timer#stop("vim-misc %s: Successfully updated %s using atomic rename in %s.", g:xolox#misc#version, a:fname, starttime)
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
if filereadable(temporary_file)
|
||||
call delete(temporary_file)
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#perm#get(fname)
|
||||
" Get the owner, group and permissions of the pathname given as the first
|
||||
" argument. Returns an opaque value which you can later pass to
|
||||
" `xolox#misc#perm#set()`.
|
||||
let pathname = xolox#misc#path#absolute(a:fname)
|
||||
if filereadable(pathname)
|
||||
let command = printf('stat --format %s %s', '%U:%G:%a', shellescape(pathname))
|
||||
let result = xolox#misc#os#exec({'command': command, 'check': 0})
|
||||
if result['exit_code'] == 0 && len(result['stdout']) >= 1
|
||||
let tokens = split(result['stdout'][0], ':')
|
||||
if len(tokens) == 3
|
||||
let [owner, group, mode] = tokens
|
||||
let mode = '0' . mode
|
||||
call xolox#misc#msg#debug("vim-misc %s: File %s has owner %s, group %s, mode %s.", g:xolox#misc#version, pathname, owner, group, mode)
|
||||
return [owner, group, mode]
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#perm#set(fname, perms)
|
||||
" Set the permissions (the second argument) of the pathname given as the
|
||||
" first argument. Expects a permissions value created by
|
||||
" `xolox#misc#perm#get()`.
|
||||
if !empty(a:perms)
|
||||
let pathname = xolox#misc#path#absolute(a:fname)
|
||||
let [owner, group, mode] = a:perms
|
||||
if s:run('chown %s:%s %s', owner, group, pathname) && s:run('chmod %s %s', mode, pathname)
|
||||
call xolox#misc#msg#debug("vim-misc %s: Successfully set %s owner to %s, group to %s and permissions to %s.", g:xolox#misc#version, pathname, owner, group, mode)
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:run(command, ...)
|
||||
let args = map(copy(a:000), 'shellescape(v:val)')
|
||||
call insert(args, a:command, 0)
|
||||
let result = xolox#misc#os#exec({'command': call('printf', args), 'check': 0})
|
||||
return result['exit_code'] == 0
|
||||
endfunction
|
||||
@@ -1,50 +0,0 @@
|
||||
" Persist/recall Vim values from/to files.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 30, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" Vim's [string()][] function can be used to serialize Vim script values like
|
||||
" numbers, strings, lists, dictionaries and composites of them to a string
|
||||
" which can later be evaluated using the [eval()][] function to turn it back
|
||||
" into the original value. This Vim script provides functions to use these
|
||||
" functions to persist and recall Vim values from/to files. This is very
|
||||
" useful for communication between (possibly concurrent) Vim processes.
|
||||
|
||||
function! xolox#misc#persist#load(filename, ...) " {{{1
|
||||
" Read a Vim value like a number, string, list or dictionary from a file
|
||||
" using [readfile()][] and [eval()][]. The first argument is the filename of
|
||||
" the file to read (a string). The optional second argument specifies the
|
||||
" default value which is returned when the file can't be loaded. This
|
||||
" function returns the loaded value or the default value (which itself
|
||||
" defaults to the integer 0).
|
||||
"
|
||||
" [eval()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#eval()
|
||||
" [readfile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#readfile()
|
||||
let default_value = exists('a:1') ? a:1 : 0
|
||||
try
|
||||
let lines = readfile(a:filename)
|
||||
return eval(join(lines, "\n"))
|
||||
catch
|
||||
return default_value
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#persist#save(filename, value) " {{{1
|
||||
" Write a Vim value like a number, string, list or dictionary to a file
|
||||
" using [string()][] and [writefile()][]. The first argument is the filename
|
||||
" of the file to write (a string) and the second argument is the value to
|
||||
" write (any value).
|
||||
"
|
||||
" This function writes the serialized value to an intermediate file which is
|
||||
" then renamed into place atomically. This avoids issues with concurrent
|
||||
" processes where for example a producer has written a partial file which is
|
||||
" read by a consumer before the file is complete. In this case the consumer
|
||||
" would read a corrupt value. The rename trick avoids this problem.
|
||||
"
|
||||
" [string()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#string()
|
||||
" [writefile()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#writefile()
|
||||
return xolox#misc#perm#update(a:filename, split(string(a:value), "\n"))
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,74 +0,0 @@
|
||||
" String handling.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: September 17, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#str#slug(s) " {{{1
|
||||
" Convert a string to a "slug" - something that can be safely used in
|
||||
" filenames and URLs without worrying about quoting/escaping of special
|
||||
" characters.
|
||||
return join(split(tolower(a:s), '\W\+'), '-')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#str#ucfirst(s) " {{{1
|
||||
" Uppercase the first character in a string (the first argument).
|
||||
return substitute(a:s, '^.', '\U\0', '')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#str#unescape(s) " {{{1
|
||||
" Remove back slash escapes from a string (the first argument).
|
||||
return substitute(a:s, '\\\(\_.\)', '\1', 'g')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#str#compact(s) " {{{1
|
||||
" Compact whitespace in a string (the first argument).
|
||||
return join(split(a:s), " ")
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#str#trim(s) " {{{1
|
||||
" Trim all whitespace from the start and end of a string (the first
|
||||
" argument).
|
||||
return substitute(a:s, '^\_s*\(.\{-}\)\_s*$', '\1', '')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#str#indent(text, num_spaces) " {{{1
|
||||
" Indent all lines in a multi-line string (the first argument) with a
|
||||
" specific number of *space characters* (the second argument, an integer).
|
||||
let lines = split(a:text, "\n")
|
||||
let indent = repeat(' ', a:num_spaces)
|
||||
let [idx, limit] = [0, len(lines)]
|
||||
while idx < limit
|
||||
if lines[idx] =~ '\S'
|
||||
let lines[idx] = indent . lines[idx]
|
||||
endif
|
||||
let idx += 1
|
||||
endwhile
|
||||
return join(lines, "\n")
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#str#dedent(text) " {{{1
|
||||
" Remove common whitespace from a multi line string.
|
||||
let lines = split(a:text, "\n")
|
||||
" First we need to determine the common indentation of all non-empty lines.
|
||||
for line in lines
|
||||
if line =~ '\S'
|
||||
let indent = matchstr(line, '^\s*')
|
||||
if !exists('common_indent')
|
||||
let common_indent = indent
|
||||
elseif len(indent) < len(common_indent)
|
||||
let common_indent = indent
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
" Now we will strip the common indentation.
|
||||
let [idx, limit] = [0, len(lines)]
|
||||
let pattern = '^' . common_indent
|
||||
while idx < limit
|
||||
let lines[idx] = substitute(lines[idx], pattern, '', '')
|
||||
let idx += 1
|
||||
endwhile
|
||||
return join(lines, "\n")
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,125 +0,0 @@
|
||||
" Test runner & infrastructure for Vim plug-ins.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 2, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" The Vim auto-load script `autoload/xolox/misc/test.vim` contains
|
||||
" infrastructure that can be used to run an automated Vim plug-in test suite.
|
||||
" It provides a framework for running test functions, keeping track of the
|
||||
" test status, making assertions and reporting test results to the user.
|
||||
|
||||
" The process handling tests cannot use the built-in "echo" command from the
|
||||
" Windows shell because it has way too much idiosyncrasies for me to put up
|
||||
" with. Seriously. Instead I'm using an "echo.exe" from the UnxUtils project.
|
||||
if xolox#misc#os#is_win()
|
||||
let g:xolox#misc#test#echo = xolox#misc#escape#shell(xolox#misc#path#merge(expand('<sfile>:p:h'), 'echo.exe'))
|
||||
else
|
||||
let g:xolox#misc#test#echo = 'echo'
|
||||
endif
|
||||
|
||||
function! xolox#misc#test#reset() " {{{1
|
||||
" Reset counters for executed tests and passed/failed assertions.
|
||||
let s:num_executed = 0
|
||||
let s:num_passed = 0
|
||||
let s:num_failed = 0
|
||||
let s:tests_started_at = xolox#misc#timer#start()
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#summarize() " {{{1
|
||||
" Print a summary of test results, to be interpreted interactively.
|
||||
call s:delimit_output()
|
||||
call xolox#misc#timer#force("Took %s to run %s: %s passed, %s failed.",
|
||||
\ s:tests_started_at,
|
||||
\ xolox#misc#format#pluralize(s:num_executed, 'test', 'tests'),
|
||||
\ xolox#misc#format#pluralize(s:num_passed, 'assertion', 'assertions'),
|
||||
\ xolox#misc#format#pluralize(s:num_failed, 'assertion', 'assertions'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#wrap(function) " {{{1
|
||||
" Call a function in a try/catch block and prevent exceptions from bubbling.
|
||||
" The name of the function should be passed as the first and only argument;
|
||||
" it should be a string containing the name of a Vim auto-load function.
|
||||
let num_failed = s:num_failed
|
||||
try
|
||||
if s:num_passed + s:num_failed > 0
|
||||
call s:delimit_output()
|
||||
endif
|
||||
let test_name = split(a:function, '#')[-1]
|
||||
let test_name = substitute(test_name, '_', ' ', 'g')
|
||||
let test_name = substitute(test_name, '^.', '\U\0', '')
|
||||
call xolox#misc#msg#info("Running test #%i: %s", s:num_executed + 1, test_name)
|
||||
call call(a:function, [])
|
||||
catch
|
||||
call xolox#misc#msg#warn("Test %s raised exception:", a:function)
|
||||
call xolox#misc#msg#warn("%s", v:exception)
|
||||
call xolox#misc#msg#warn("(at %s)", v:throwpoint)
|
||||
if num_failed == s:num_failed
|
||||
" Make sure exceptions are counted as failures, but don't inflate the
|
||||
" number of failed assertions when it's not needed (it can produce
|
||||
" confusing test output).
|
||||
call xolox#misc#test#failed()
|
||||
endif
|
||||
endtry
|
||||
let s:num_executed += 1
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#passed() " {{{1
|
||||
" Record a test which succeeded.
|
||||
let s:num_passed += 1
|
||||
call s:print_feedback()
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#failed() " {{{1
|
||||
" Record a test which failed.
|
||||
let s:num_failed += 1
|
||||
call s:print_feedback()
|
||||
endfunction
|
||||
|
||||
function! s:delimit_output() " {{{1
|
||||
" Print a delimiter between output of tests.
|
||||
call xolox#misc#msg#info("%s", repeat("-", 40))
|
||||
endfunction
|
||||
|
||||
function! s:print_feedback() " {{{1
|
||||
" Let the user know the status of the test suite.
|
||||
call xolox#misc#msg#info("Test status: %s passed, %s failed ..",
|
||||
\ xolox#misc#format#pluralize(s:num_passed, 'assertion', 'assertions'),
|
||||
\ xolox#misc#format#pluralize(s:num_failed, 'assertion', 'assertions'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#assert_true(expr) " {{{1
|
||||
" Check whether an expression is true.
|
||||
if a:expr
|
||||
call xolox#misc#test#passed()
|
||||
else
|
||||
call xolox#misc#test#failed()
|
||||
let msg = "Expected value to be true, got %s instead"
|
||||
throw printf(msg, string(a:expr))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#assert_equals(expected, received) " {{{1
|
||||
" Check whether two values are the same.
|
||||
call xolox#misc#test#assert_same_type(a:expected, a:received)
|
||||
if a:expected == a:received
|
||||
call xolox#misc#test#passed()
|
||||
else
|
||||
call xolox#misc#test#failed()
|
||||
let msg = "Expected value %s, received value %s!"
|
||||
throw printf(msg, string(a:expected), string(a:received))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#test#assert_same_type(expected, received) " {{{1
|
||||
" Check whether two values are of the same type.
|
||||
if type(a:expected) == type(a:received)
|
||||
call xolox#misc#test#passed()
|
||||
else
|
||||
call xolox#misc#test#failed()
|
||||
let msg = "Expected value of same type as %s, got value %s!"
|
||||
throw printf(msg, string(a:expected), string(a:received))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
call xolox#misc#test#reset()
|
||||
@@ -1,301 +0,0 @@
|
||||
" Tests for the miscellaneous Vim scripts.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June , 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
"
|
||||
" The Vim auto-load script `autoload/xolox/misc/tests.vim` contains the
|
||||
" automated test suite of the miscellaneous Vim scripts. Right now the
|
||||
" coverage is not very high yet, but this will improve over time.
|
||||
|
||||
let s:use_dll = 0
|
||||
let s:can_use_dll = xolox#misc#os#can_use_dll()
|
||||
|
||||
function! xolox#misc#tests#run() " {{{1
|
||||
" Run the automated test suite of the miscellaneous Vim scripts. To be used
|
||||
" interactively. Intended to be safe to execute irrespective of context.
|
||||
call xolox#misc#test#reset()
|
||||
" Run the tests.
|
||||
call s:test_string_escaping()
|
||||
call s:test_list_handling()
|
||||
call s:test_option_handling()
|
||||
call s:test_command_execution()
|
||||
call s:test_string_handling()
|
||||
call s:test_version_handling()
|
||||
" Report a short summary to the user.
|
||||
call xolox#misc#test#summarize()
|
||||
endfunction
|
||||
|
||||
function! s:wrap_exec_test(function)
|
||||
" Wrapper for tests that use xolox#misc#os#exec(). If we're on Windows and
|
||||
" the vim-shell plug-in is installed, the test will be run twice: Once with
|
||||
" vim-shell disabled and once with vim-shell enabled. This makes sure that
|
||||
" all code paths are tested as much as possible.
|
||||
call xolox#misc#msg#debug("vim-misc %s: Temporarily disabling vim-shell so we can test vim-misc ..", g:xolox#misc#version)
|
||||
let s:use_dll = 0
|
||||
call xolox#misc#test#wrap(a:function)
|
||||
if s:can_use_dll
|
||||
call xolox#misc#msg#debug("vim-misc %s: Re-enabling vim-shell so we can test that as well ..", g:xolox#misc#version)
|
||||
let s:use_dll = 1
|
||||
call xolox#misc#test#wrap(a:function)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Tests for autoload/xolox/misc/escape.vim {{{1
|
||||
|
||||
function! s:test_string_escaping()
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#pattern_escaping')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#substitute_escaping')
|
||||
call s:wrap_exec_test('xolox#misc#tests#shell_escaping')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#pattern_escaping() " {{{2
|
||||
" Test escaping of regular expression patterns with
|
||||
" `xolox#misc#escape#pattern()`.
|
||||
call xolox#misc#test#assert_equals('foo [qux] baz', substitute('foo [bar] baz', xolox#misc#escape#pattern('[bar]'), '[qux]', 'g'))
|
||||
call xolox#misc#test#assert_equals('also very nasty', substitute('also ~ nasty', xolox#misc#escape#pattern('~'), 'very', 'g'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#substitute_escaping() " {{{2
|
||||
" Test escaping of substitution strings with
|
||||
" `xolox#misc#escape#substitute()`.
|
||||
call xolox#misc#test#assert_equals('nasty & tricky stuff', substitute('tricky stuff', 'tricky', xolox#misc#escape#substitute('nasty & tricky'), 'g'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#shell_escaping() " {{{2
|
||||
" Test escaping of shell arguments with `xolox#misc#escape#shell()`.
|
||||
let expected_value = 'this < is > a | very " scary ^ string '' indeed'
|
||||
let result = xolox#misc#os#exec({'command': g:xolox#misc#test#echo . ' ' . xolox#misc#escape#shell(expected_value), 'use_dll': s:use_dll})
|
||||
call xolox#misc#test#assert_equals(0, result['exit_code'])
|
||||
call xolox#misc#test#assert_equals(0, result['exit_code'])
|
||||
call xolox#misc#test#assert_same_type([], result['stdout'])
|
||||
call xolox#misc#test#assert_equals(1, len(result['stdout']))
|
||||
" XXX On Windows using system() there's a trailing space I can't explain.
|
||||
" However the point of this test was to show that all characters pass
|
||||
" through unharmed, so for now I'll just ignore the space :-)
|
||||
call xolox#misc#test#assert_equals(expected_value, xolox#misc#str#trim(result['stdout'][0]))
|
||||
endfunction
|
||||
|
||||
" Tests for autoload/xolox/misc/list.vim {{{1
|
||||
|
||||
function! s:test_list_handling()
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#making_a_list_unique')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#binary_insertion')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#making_a_list_unique() " {{{2
|
||||
" Test removing of duplicate values from lists with
|
||||
" `xolox#misc#list#unique()`.
|
||||
call xolox#misc#test#assert_equals([1, 2, 3, 4, 5], xolox#misc#list#unique([1, 1, 2, 3, 3, 4, 5, 5]))
|
||||
" Should work for strings just as well. And it should preserve order.
|
||||
call xolox#misc#test#assert_equals(['a', 'b', 'c'], xolox#misc#list#unique(['a', 'a', 'b', 'b', 'c']))
|
||||
" Just to make sure that lists without duplicate values pass through unharmed.
|
||||
call xolox#misc#test#assert_equals([1, 2, 3, 4, 5], xolox#misc#list#unique([1, 2, 3, 4, 5]))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#binary_insertion() " {{{2
|
||||
" Test the binary insertion algorithm implemented in
|
||||
" `xolox#misc#list#binsert()`.
|
||||
let list = ['a', 'B', 'e']
|
||||
" Insert 'c' (should end up between 'B' and 'e').
|
||||
call xolox#misc#list#binsert(list, 'c', 1)
|
||||
call xolox#misc#test#assert_equals(['a', 'B', 'c', 'e'], list)
|
||||
" Insert 'D' (should end up between 'c' and 'e').
|
||||
call xolox#misc#list#binsert(list, 'D', 1)
|
||||
call xolox#misc#test#assert_equals(['a', 'B', 'c', 'D', 'e'], list)
|
||||
" Insert 'f' (should end up after 'e', at the end).
|
||||
call xolox#misc#list#binsert(list, 'f', 1)
|
||||
call xolox#misc#test#assert_equals(['a', 'B', 'c', 'D', 'e', 'f'], list)
|
||||
endfunction
|
||||
|
||||
" Tests for autoload/xolox/misc/option.vim {{{1
|
||||
|
||||
function! s:test_option_handling()
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#getting_configuration_options')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#splitting_of_multi_valued_options')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#joining_of_multi_valued_options')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#getting_configuration_options() " {{{2
|
||||
" Test getting of scoped plug-in configuration "options" with
|
||||
" `xolox#misc#option#get()`.
|
||||
let magic_name = 'a_variable_that_none_would_use'
|
||||
call xolox#misc#test#assert_equals(0, xolox#misc#option#get(magic_name))
|
||||
" Test custom default values.
|
||||
call xolox#misc#test#assert_equals([], xolox#misc#option#get(magic_name, []))
|
||||
" Set the option as a global variable.
|
||||
let global_value = 'global variable'
|
||||
let g:{magic_name} = global_value
|
||||
call xolox#misc#test#assert_equals(global_value, xolox#misc#option#get(magic_name))
|
||||
" Set the option as a buffer local variable, thereby shadowing the global.
|
||||
let local_value = 'buffer local variable'
|
||||
let b:{magic_name} = local_value
|
||||
call xolox#misc#test#assert_equals(local_value, xolox#misc#option#get(magic_name))
|
||||
" Sanity check that it's possible to unshadow as well.
|
||||
unlet b:{magic_name}
|
||||
call xolox#misc#test#assert_equals(global_value, xolox#misc#option#get(magic_name))
|
||||
" Cleanup after ourselves.
|
||||
unlet g:{magic_name}
|
||||
call xolox#misc#test#assert_equals(0, xolox#misc#option#get(magic_name))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#splitting_of_multi_valued_options() " {{{2
|
||||
" Test splitting of multi-valued Vim options with
|
||||
" `xolox#misc#option#split()`.
|
||||
call xolox#misc#test#assert_equals([], xolox#misc#option#split(''))
|
||||
call xolox#misc#test#assert_equals(['just one value'], xolox#misc#option#split('just one value'))
|
||||
call xolox#misc#test#assert_equals(['value 1', 'value 2'], xolox#misc#option#split('value 1,value 2'))
|
||||
call xolox#misc#test#assert_equals(['value 1', 'value 2', 'tricky,value'], xolox#misc#option#split('value 1,value 2,tricky\,value'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#joining_of_multi_valued_options() " {{{2
|
||||
" Test joining of multi-valued Vim options with `xolox#misc#option#join()`.
|
||||
call xolox#misc#test#assert_equals('', xolox#misc#option#join([]))
|
||||
call xolox#misc#test#assert_equals('just one value', xolox#misc#option#join(['just one value']))
|
||||
call xolox#misc#test#assert_equals('value 1,value 2', xolox#misc#option#join(['value 1', 'value 2']))
|
||||
call xolox#misc#test#assert_equals('value 1,value 2,tricky\,value', xolox#misc#option#join(['value 1', 'value 2', 'tricky,value']))
|
||||
endfunction
|
||||
|
||||
" Tests for autoload/xolox/misc/os.vim {{{1
|
||||
|
||||
function! s:test_command_execution()
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#finding_vim_on_the_search_path')
|
||||
call s:wrap_exec_test('xolox#misc#tests#synchronous_command_execution')
|
||||
call s:wrap_exec_test('xolox#misc#tests#synchronous_command_execution_with_stderr')
|
||||
call s:wrap_exec_test('xolox#misc#tests#synchronous_command_execution_with_raising_of_errors')
|
||||
call s:wrap_exec_test('xolox#misc#tests#synchronous_command_execution_without_raising_errors')
|
||||
call s:wrap_exec_test('xolox#misc#tests#asynchronous_command_execution')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#finding_vim_on_the_search_path() " {{{2
|
||||
" Test looking up Vim's executable on the search path using [v:progname] []
|
||||
" with `xolox#misc#os#find_vim()`.
|
||||
"
|
||||
" [v:progname]: http://vimdoc.sourceforge.net/htmldoc/eval.html#v:progname
|
||||
let pathname = xolox#misc#os#find_vim()
|
||||
call xolox#misc#test#assert_same_type('', pathname)
|
||||
call xolox#misc#test#assert_true(executable(pathname))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#synchronous_command_execution() " {{{2
|
||||
" Test basic functionality of synchronous command execution with
|
||||
" `xolox#misc#os#exec()`.
|
||||
let result = xolox#misc#os#exec({'command': printf('%s output', g:xolox#misc#test#echo), 'use_dll': s:use_dll})
|
||||
call xolox#misc#test#assert_same_type({}, result)
|
||||
call xolox#misc#test#assert_equals(0, result['exit_code'])
|
||||
call xolox#misc#test#assert_equals(['output'], result['stdout'])
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#synchronous_command_execution_with_stderr() " {{{2
|
||||
" Test basic functionality of synchronous command execution with
|
||||
" `xolox#misc#os#exec()` including the standard error stream (not available
|
||||
" on Windows when vim-shell is not installed).
|
||||
if !(xolox#misc#os#is_win() && !s:use_dll)
|
||||
let result = xolox#misc#os#exec({'command': printf('%s output && %s errors >&2', g:xolox#misc#test#echo, g:xolox#misc#test#echo), 'use_dll': s:use_dll})
|
||||
call xolox#misc#test#assert_same_type({}, result)
|
||||
call xolox#misc#test#assert_equals(0, result['exit_code'])
|
||||
call xolox#misc#test#assert_equals(['output'], result['stdout'])
|
||||
call xolox#misc#test#assert_equals(['errors'], result['stderr'])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#synchronous_command_execution_with_raising_of_errors() " {{{2
|
||||
" Test raising of errors during synchronous command execution with
|
||||
" `xolox#misc#os#exec()`.
|
||||
try
|
||||
call xolox#misc#os#exec({'command': 'exit 1', 'use_dll': s:use_dll})
|
||||
call xolox#misc#test#assert_true(0)
|
||||
catch
|
||||
call xolox#misc#test#assert_true(1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#synchronous_command_execution_without_raising_errors() " {{{2
|
||||
" Test synchronous command execution without raising of errors with
|
||||
" `xolox#misc#os#exec()`.
|
||||
try
|
||||
let result = xolox#misc#os#exec({'command': 'exit 42', 'check': 0, 'use_dll': s:use_dll})
|
||||
call xolox#misc#test#assert_true(1)
|
||||
call xolox#misc#test#assert_equals(42, result['exit_code'])
|
||||
catch
|
||||
call xolox#misc#test#assert_true(0)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#asynchronous_command_execution() " {{{2
|
||||
" Test the basic functionality of asynchronous command execution with
|
||||
" `xolox#misc#os#exec()`. This runs the external command `mkdir` and tests
|
||||
" that the side effect of creating the directory takes place. This might
|
||||
" seem like a peculiar choice, but it's one of the few 100% portable
|
||||
" commands (Windows + UNIX) that doesn't involve input/output streams.
|
||||
let temporary_directory = xolox#misc#path#tempdir()
|
||||
let random_name = printf('%i', localtime())
|
||||
let expected_directory = xolox#misc#path#merge(temporary_directory, random_name)
|
||||
let command = 'mkdir ' . xolox#misc#escape#shell(expected_directory)
|
||||
let result = xolox#misc#os#exec({'command': command, 'async': 1, 'use_dll': s:use_dll})
|
||||
call xolox#misc#test#assert_same_type({}, result)
|
||||
" Make sure the command is really executed.
|
||||
let timeout = localtime() + 30
|
||||
while !isdirectory(expected_directory) && localtime() < timeout
|
||||
sleep 500 m
|
||||
endwhile
|
||||
call xolox#misc#test#assert_true(isdirectory(expected_directory))
|
||||
endfunction
|
||||
|
||||
" Tests for autoload/xolox/misc/str.vim {{{1
|
||||
|
||||
function! s:test_string_handling()
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#string_case_transformation')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#string_whitespace_compaction')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#string_whitespace_trimming')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#multiline_string_dedent')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#string_case_transformation()
|
||||
" Test string case transformation with `xolox#misc#str#ucfirst()`.
|
||||
call xolox#misc#test#assert_equals('Foo', xolox#misc#str#ucfirst('foo'))
|
||||
call xolox#misc#test#assert_equals('BAR', xolox#misc#str#ucfirst('BAR'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#string_whitespace_compaction()
|
||||
" Test compaction of whitespace in strings with `xolox#misc#str#compact()`.
|
||||
call xolox#misc#test#assert_equals('foo bar baz', xolox#misc#str#compact(' foo bar baz '))
|
||||
call xolox#misc#test#assert_equals('test', xolox#misc#str#compact("\ntest "))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#string_whitespace_trimming()
|
||||
" Test trimming of whitespace in strings with `xolox#misc#str#trim()`.
|
||||
call xolox#misc#test#assert_equals('foo bar baz', xolox#misc#str#trim("\nfoo bar baz "))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#multiline_string_dedent()
|
||||
" Test dedenting of multi-line strings with `xolox#misc#str#dedent()`.
|
||||
call xolox#misc#test#assert_equals('test', xolox#misc#str#dedent(' test'))
|
||||
call xolox#misc#test#assert_equals("1\n\n2", xolox#misc#str#dedent(" 1\n\n 2"))
|
||||
call xolox#misc#test#assert_equals("1\n\n 2", xolox#misc#str#dedent(" 1\n\n 2"))
|
||||
endfunction
|
||||
|
||||
" Tests for autoload/xolox/misc/version.vim {{{1
|
||||
|
||||
function! s:test_version_handling()
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#version_string_parsing')
|
||||
call xolox#misc#test#wrap('xolox#misc#tests#version_string_comparison')
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#version_string_parsing() " {{{2
|
||||
" Test parsing of version strings with `xolox#misc#version#parse()`.
|
||||
call xolox#misc#test#assert_equals([1], xolox#misc#version#parse('1'))
|
||||
call xolox#misc#test#assert_equals([1, 5], xolox#misc#version#parse('1.5'))
|
||||
call xolox#misc#test#assert_equals([1, 22, 3333, 44444, 55555], xolox#misc#version#parse('1.22.3333.44444.55555'))
|
||||
call xolox#misc#test#assert_equals([1, 5], xolox#misc#version#parse('1x.5y'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#tests#version_string_comparison() " {{{2
|
||||
" Test comparison of version strings with `xolox#misc#version#at_least()`.
|
||||
call xolox#misc#test#assert_true(xolox#misc#version#at_least('1', '1'))
|
||||
call xolox#misc#test#assert_true(!xolox#misc#version#at_least('1', '0'))
|
||||
call xolox#misc#test#assert_true(xolox#misc#version#at_least('1', '2'))
|
||||
call xolox#misc#test#assert_true(xolox#misc#version#at_least('1.2.3', '1.2.3'))
|
||||
call xolox#misc#test#assert_true(!xolox#misc#version#at_least('1.2.3', '1.2'))
|
||||
call xolox#misc#test#assert_true(xolox#misc#version#at_least('1.2.3', '1.2.4'))
|
||||
endfunction
|
||||
@@ -1,130 +0,0 @@
|
||||
" Timing of long during operations.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: July 19, 2014
|
||||
" 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')
|
||||
let s:unique_marker = 'xolox#misc#timer#value'
|
||||
|
||||
function! xolox#misc#timer#resumable() " {{{1
|
||||
" Create a resumable timer object. This returns an object (a dictionary with
|
||||
" functions) with the following "methods":
|
||||
"
|
||||
" - `start()` instructs the timer object to start counting elapsed time
|
||||
" (when a timer object is created it is not automatically started).
|
||||
"
|
||||
" - `stop()` instructs the timer object to stop counting elapsed time.
|
||||
" This adds the time elapsed since `start()` was last called to the
|
||||
" total elapsed time. This method will raise an error if called out of
|
||||
" sequence.
|
||||
"
|
||||
" - `format()` takes the total elapsed time and reports it as a string
|
||||
" containing a formatted floating point number.
|
||||
"
|
||||
" Timer objects are meant to accurately time short running operations so
|
||||
" they're dependent on Vim's [reltime()][] and [reltimestr()][] functions.
|
||||
" In order to make it possible to use timer objects in my Vim plug-ins
|
||||
" unconditionally there's a fall back to [localtime()][] when [reltime()][]
|
||||
" is not available. In this mode the timer objects are not very useful but
|
||||
" at least they shouldn't raise errors.
|
||||
"
|
||||
" [localtime()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#localtime()
|
||||
" [reltime()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#reltime()
|
||||
" [reltimestr()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#reltimestr()
|
||||
let object = {'total': [0, 0]}
|
||||
function object.start() dict
|
||||
if s:has_reltime
|
||||
let self.current = reltime()
|
||||
else
|
||||
let self.current = localtime()
|
||||
endif
|
||||
endfunction
|
||||
function object.stop() dict
|
||||
if empty(get(self, 'current'))
|
||||
throw "timer.stop() called on a timer that was never started!"
|
||||
endif
|
||||
if s:has_reltime
|
||||
let elapsed_time_string = xolox#misc#str#trim(reltimestr(reltime(self.current)))
|
||||
" This is a bit silly (converting to a string and then parsing that) but
|
||||
" the value of reltime() is documented as being platform specific...
|
||||
let [seconds, microseconds] = split(elapsed_time_string, '\.')
|
||||
let self.total[0] += substitute(seconds, '^0*', '', '')
|
||||
let self.total[1] += substitute(microseconds, '^0*', '', '')
|
||||
let self.current = []
|
||||
else
|
||||
let self.total[0] += localtime() - self.current
|
||||
let self.current = 0
|
||||
endif
|
||||
endfunction
|
||||
function object.format() dict
|
||||
let seconds = self.total[0]
|
||||
let microseconds = self.total[1]
|
||||
if microseconds >= 1000000
|
||||
let additional_seconds = microseconds / 1000000
|
||||
let seconds += additional_seconds
|
||||
let microseconds -= additional_seconds * 1000000
|
||||
endif
|
||||
return printf('%i.%06i', seconds, microseconds)
|
||||
endfunction
|
||||
return object
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#timer#start() " {{{1
|
||||
" Start a timer. This returns a list which can later be passed to
|
||||
" `xolox#misc#timer#stop()`.
|
||||
return [s:unique_marker, s:has_reltime ? reltime() : localtime()]
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#timer#stop(...) " {{{1
|
||||
" Show a formatted debugging message to the user, if the user has enabled
|
||||
" increased verbosity by setting Vim's ['verbose'] [verbose] option to one
|
||||
" (1) or higher.
|
||||
"
|
||||
" This function has the same argument handling as Vim's [printf()] [printf]
|
||||
" function with one difference: At the point where you want the elapsed time
|
||||
" to be embedded, you write `%s` and you pass the list returned by
|
||||
" `xolox#misc#timer#start()` as an argument.
|
||||
"
|
||||
" [verbose]: http://vimdoc.sourceforge.net/htmldoc/options.html#'verbose'
|
||||
" [printf]: http://vimdoc.sourceforge.net/htmldoc/eval.html#printf()
|
||||
if (g:timer_enabled || &verbose >= g:timer_verbosity)
|
||||
call call('xolox#misc#msg#info', map(copy(a:000), 'xolox#misc#timer#convert(v:val)'))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#timer#force(...) " {{{1
|
||||
" Show a formatted message to the user. This function has the same argument
|
||||
" handling as Vim's [printf()] [printf] function with one difference: At the
|
||||
" point where you want the elapsed time to be embedded, you write `%s` and
|
||||
" you pass the list returned by `xolox#misc#timer#start()` as an argument.
|
||||
call call('xolox#misc#msg#info', map(copy(a:000), 'xolox#misc#timer#convert(v:val)'))
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#timer#convert(value) " {{{1
|
||||
" Convert the value returned by `xolox#misc#timer#start()` to a string
|
||||
" representation of the elapsed time since `xolox#misc#timer#start()` was
|
||||
" called. Other values are returned unmodified (this allows using it with
|
||||
" Vim's [map()][] function).
|
||||
"
|
||||
" [map()]: http://vimdoc.sourceforge.net/htmldoc/eval.html#map()
|
||||
if type(a:value) == type([]) && len(a:value) == 2 && a:value[0] == s:unique_marker
|
||||
if s:has_reltime
|
||||
let ts = xolox#misc#str#trim(reltimestr(reltime(a:value[1])))
|
||||
else
|
||||
let ts = localtime() - a:value[1]
|
||||
endif
|
||||
return xolox#misc#format#timestamp(ts)
|
||||
endif
|
||||
return a:value
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,34 +0,0 @@
|
||||
" Version string handling.
|
||||
"
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 22, 2013
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
function! xolox#misc#version#parse(version_string)
|
||||
" Convert a version string to a list of integers.
|
||||
let result = map(split(a:version_string, '\.'), 'v:val + 0')
|
||||
call xolox#misc#msg#debug("vim-misc %s: Parsed version string %s into %s.", g:xolox#misc#version, string(a:version_string), string(result))
|
||||
return result
|
||||
endfunction
|
||||
|
||||
function! xolox#misc#version#at_least(expected_version, available_version)
|
||||
" Check whether the second version string is equal to or greater than the
|
||||
" first version string. Returns 1 (true) when it is, 0 (false) otherwise.
|
||||
let expected_version = xolox#misc#version#parse(a:expected_version)
|
||||
let available_version = xolox#misc#version#parse(a:available_version)
|
||||
for idx in range(max([len(expected_version), len(available_version)]))
|
||||
let expected_number = get(expected_version, idx, 0)
|
||||
let available_number = get(available_version, idx, 0)
|
||||
if available_number > expected_number
|
||||
call xolox#misc#msg#debug("vim-misc %s: Available version (%s) is higher than expected version (%s).", g:xolox#misc#version, a:available_version, a:expected_version)
|
||||
return 1
|
||||
elseif available_number < expected_number
|
||||
call xolox#misc#msg#debug("vim-misc %s: Available version (%s) is lower than expected version (%s).", g:xolox#misc#version, a:available_version, a:expected_version)
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
call xolox#misc#msg#debug("vim-misc %s: Available version (%s) is equal to expected version (%s).", g:xolox#misc#version, a:available_version, a:expected_version)
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
1
.vim/bundle/vim-easytags
Submodule
1
.vim/bundle/vim-easytags
Submodule
Submodule .vim/bundle/vim-easytags added at 72a8753b5d
@@ -1,55 +0,0 @@
|
||||
'''
|
||||
This Python script is part of the easytags plug-in for the Vim text editor. The
|
||||
Python Interface to Vim is used to load this script which accelerates dynamic
|
||||
syntax highlighting by reimplementing tag file reading and :syntax command
|
||||
generation in Python with a focus on doing the least amount of work.
|
||||
|
||||
Author: Peter Odding <peter@peterodding.com>
|
||||
Last Change: March 8, 2014
|
||||
URL: http://peterodding.com/code/vim/easytags
|
||||
'''
|
||||
|
||||
# TODO Cache the contents of tags files to further improve performance?
|
||||
|
||||
import re
|
||||
import vim
|
||||
import sys
|
||||
|
||||
def easytags_ping():
|
||||
print 'it works!'
|
||||
|
||||
def easytags_gensyncmd(tagsfiles, filetype, tagkinds, syntaxgroup, prefix, suffix, filters, ignoresyntax):
|
||||
# Get arguments from Vim.
|
||||
if filters:
|
||||
tagkinds = filters['kind']
|
||||
# Shallow parse tags files for matching identifiers.
|
||||
pattern = '^([^\t]+)\t[^\t]+\t[^\t]+\t' + tagkinds + '\tlanguage:' + re.escape(filetype)
|
||||
compiled_pattern = re.compile(pattern, re.IGNORECASE)
|
||||
matches = {}
|
||||
for fname in tagsfiles:
|
||||
handle = open(fname)
|
||||
for line in handle:
|
||||
m = compiled_pattern.match(line)
|
||||
if m and ('match' not in filters or re.search(filters['match'], line)) \
|
||||
and ('nomatch' not in filters or not re.search(filters['nomatch'], line)):
|
||||
matches[m.group(1)] = True
|
||||
handle.close()
|
||||
# Generate Vim :syntax command to highlight identifiers.
|
||||
patterns, commands = [], []
|
||||
counter, limit = 0, 1024 * 20
|
||||
to_escape = re.compile(r'[.*^$/\\~\[\]]')
|
||||
for ident in matches.keys():
|
||||
escaped = to_escape.sub(r'\\\g<0>', ident)
|
||||
patterns.append(escaped)
|
||||
counter += len(escaped)
|
||||
if counter > limit:
|
||||
commands.append(_easytags_makecmd(syntaxgroup, prefix, suffix, patterns, ignoresyntax))
|
||||
patterns = []
|
||||
counter = 0
|
||||
if patterns:
|
||||
commands.append(_easytags_makecmd(syntaxgroup, prefix, suffix, patterns, ignoresyntax))
|
||||
return ' | '.join(commands)
|
||||
|
||||
def _easytags_makecmd(syntaxgroup, prefix, suffix, patterns, ignoresyntax):
|
||||
template = r'syntax match %s /%s\%%(%s\)%s/ containedin=ALLBUT,%s'
|
||||
return template % (syntaxgroup, prefix, r'\|'.join(patterns), suffix, ignoresyntax)
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
'''
|
||||
Resolve symbolic links in tags files and remove duplicate entries from the
|
||||
resulting list of tags. If your tags files contain symbolic links as well as
|
||||
canonical filenames this can significantly reduce the size of your tags file.
|
||||
This script makes a backup of the tags file in case something goes wrong.
|
||||
|
||||
Author: Peter Odding <peter@peterodding.com>
|
||||
Last Change: September 4, 2011
|
||||
URL: https://github.com/xolox/vim-easytags/blob/master/normalize-tags.py
|
||||
'''
|
||||
|
||||
import os, sys, time
|
||||
|
||||
def main(arguments):
|
||||
for tagsfile in arguments or [os.path.expanduser('~/.vimtags')]:
|
||||
normalize(tagsfile)
|
||||
print "Done!"
|
||||
|
||||
def normalize(tagsfile):
|
||||
|
||||
# Setup.
|
||||
tempname = '%s-new-%d' % (tagsfile, time.time())
|
||||
results, cache = {}, {}
|
||||
infile = open(tagsfile)
|
||||
outfile = open(tempname, 'w')
|
||||
nprocessed = 0
|
||||
fold_case = False
|
||||
|
||||
# Read tags file.
|
||||
for line in infile:
|
||||
line = line.rstrip()
|
||||
fields = line.split('\t')
|
||||
if line.startswith('!_TAG_'):
|
||||
results[line] = True
|
||||
if line.startswith('!_TAG_FILE_SORTED\t2'):
|
||||
fold_case = True
|
||||
else:
|
||||
pathname = fields[1]
|
||||
if pathname not in cache:
|
||||
if os.path.exists(pathname):
|
||||
cache[pathname] = os.path.realpath(pathname)
|
||||
else:
|
||||
cache[pathname] = ''
|
||||
if cache[pathname]:
|
||||
fields[1] = cache[pathname]
|
||||
results['\t'.join(fields)] = True
|
||||
nprocessed += 1
|
||||
infile.close()
|
||||
|
||||
# Sort tags.
|
||||
lines = results.keys()
|
||||
if fold_case:
|
||||
lines.sort(key=str.lower)
|
||||
else:
|
||||
lines.sort()
|
||||
|
||||
# Write tags file.
|
||||
outfile.write('\n'.join(lines))
|
||||
outfile.write('\n')
|
||||
outfile.close()
|
||||
|
||||
# Backup old tags file.
|
||||
backup = '%s-backup-%d' % (tagsfile, time.time())
|
||||
print "Making a backup of %s as %s" % (tagsfile, backup)
|
||||
os.rename(tagsfile, backup)
|
||||
|
||||
# Replace tags file.
|
||||
print "Replacing old", tagsfile, "with new one"
|
||||
os.rename(tempname, tagsfile)
|
||||
|
||||
# Report results.
|
||||
nfiltered = nprocessed - len(lines)
|
||||
print "Filtered %d out of %d entries" % (nfiltered, nprocessed)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
|
||||
# vim: ts=2 sw=2 et
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
'''
|
||||
Determine which files are contributing the most to the size of a tags file. You
|
||||
can specify the location of the tags file as a command line argument. If you
|
||||
pass a numeric argument, no more than that many files will be reported.
|
||||
|
||||
Author: Peter Odding <peter@peterodding.com>
|
||||
Last Change: May 11, 2011
|
||||
URL: https://github.com/xolox/vim-easytags/blob/master/why-so-slow.py
|
||||
'''
|
||||
|
||||
import os, sys
|
||||
|
||||
tagsfile = '~/.vimtags'
|
||||
topfiles = 10
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
if os.path.isfile(arg):
|
||||
tagsfile = arg
|
||||
else:
|
||||
topfiles = int(arg)
|
||||
|
||||
infile = open(os.path.expanduser(tagsfile))
|
||||
counters = {}
|
||||
|
||||
for line in infile:
|
||||
fields = line.split('\t')
|
||||
filename = fields[1]
|
||||
counters[filename] = counters.get(filename, 0) + len(line)
|
||||
infile.close()
|
||||
|
||||
sortedfiles = sorted([(s, n) for (n, s) in counters.iteritems()], reverse=True)
|
||||
for filesize, filename in sortedfiles[:topfiles]:
|
||||
if filename.startswith(os.environ['HOME']):
|
||||
filename = filename.replace(os.environ['HOME'], '~')
|
||||
print '%i KB - %s' % (filesize / 1024, filename)
|
||||
|
||||
# vim: ts=2 sw=2 et
|
||||
@@ -1,144 +0,0 @@
|
||||
" Vim plug-in
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 29, 2014
|
||||
" URL: http://peterodding.com/code/vim/easytags/
|
||||
" Requires: Exuberant Ctags (http://ctags.sf.net)
|
||||
|
||||
" Support for automatic update using the GLVS plug-in.
|
||||
" GetLatestVimScripts: 3114 1 :AutoInstall: easytags.zip
|
||||
|
||||
" Don't source the plug-in when it's already been loaded or &compatible is set.
|
||||
if &cp || exists('g:loaded_easytags')
|
||||
finish
|
||||
endif
|
||||
|
||||
" Make sure vim-misc is installed. {{{1
|
||||
|
||||
try
|
||||
" The point of this code is to do something completely innocent while making
|
||||
" sure the vim-misc plug-in is installed. We specifically don't use Vim's
|
||||
" exists() function because it doesn't load auto-load scripts that haven't
|
||||
" already been loaded yet (last tested on Vim 7.3).
|
||||
call type(g:xolox#misc#version)
|
||||
catch
|
||||
echomsg "Warning: The vim-easytags plug-in requires the vim-misc plug-in which seems not to be installed! For more information please review the installation instructions in the readme (also available on the homepage and on GitHub). The vim-easytags plug-in will now be disabled."
|
||||
let g:loaded_easytags = 1
|
||||
finish
|
||||
endtry
|
||||
|
||||
" Configuration defaults and initialization. {{{1
|
||||
|
||||
if !exists('g:easytags_file')
|
||||
if xolox#misc#os#is_win()
|
||||
let g:easytags_file = '~\_vimtags'
|
||||
else
|
||||
let g:easytags_file = '~/.vimtags'
|
||||
endif
|
||||
endif
|
||||
|
||||
if !exists('g:easytags_by_filetype')
|
||||
let g:easytags_by_filetype = ''
|
||||
endif
|
||||
|
||||
if !exists('g:easytags_events')
|
||||
let g:easytags_events = ['BufWritePost']
|
||||
if !exists('g:easytags_on_cursorhold') || g:easytags_on_cursorhold
|
||||
call extend(g:easytags_events, ['CursorHold', 'CursorHoldI'])
|
||||
endif
|
||||
if exists('g:easytags_always_enabled') && g:easytags_always_enabled
|
||||
call extend(g:easytags_events, ['BufReadPost', 'FocusGained', 'ShellCmdPost', 'ShellFilterPost'])
|
||||
endif
|
||||
endif
|
||||
|
||||
if !exists('g:easytags_ignored_filetypes')
|
||||
let g:easytags_ignored_filetypes = '^tex$'
|
||||
endif
|
||||
|
||||
if exists('g:easytags_ignored_syntax_groups')
|
||||
call xolox#misc#msg#warn("easytags.vim %s: The 'g:easytags_ignored_syntax_groups' option is no longer supported. It has been moved back into the code base for more flexible handling at runtime.", g:xolox#easytags#version)
|
||||
endif
|
||||
|
||||
if !exists('g:easytags_python_script')
|
||||
let g:easytags_python_script = expand('<sfile>:p:h') . '/../misc/easytags/highlight.py'
|
||||
endif
|
||||
|
||||
" Make sure Exuberant Ctags >= 5.5 is installed.
|
||||
if !xolox#easytags#initialize('5.5')
|
||||
" Did the user configure the plug-in to suppress the regular warning message?
|
||||
if !(exists('g:easytags_suppress_ctags_warning') && g:easytags_suppress_ctags_warning)
|
||||
" Explain to the user what went wrong:
|
||||
if !exists('g:easytags_ctags_version') || empty(g:easytags_ctags_version)
|
||||
" Exuberant Ctags is not installed / could not be found.
|
||||
let s:msg = "easytags.vim %s: Plug-in not loaded because Exuberant Ctags isn't installed!"
|
||||
if executable('apt-get')
|
||||
let s:msg .= " On Ubuntu & Debian you can install Exuberant Ctags by"
|
||||
let s:msg .= " installing the package named `exuberant-ctags':"
|
||||
let s:msg .= " sudo apt-get install exuberant-ctags"
|
||||
else
|
||||
let s:msg .= " Please download & install Exuberant Ctags from http://ctags.sf.net"
|
||||
endif
|
||||
call xolox#misc#msg#warn(s:msg, g:xolox#easytags#version)
|
||||
else
|
||||
" The installed version is too old.
|
||||
let s:msg = "easytags.vim %s: Plug-in not loaded because Exuberant Ctags 5.5"
|
||||
let s:msg .= " or newer is required while you have version %s installed!"
|
||||
call xolox#misc#msg#warn(s:msg, g:xolox#easytags#version, g:easytags_ctags_version)
|
||||
endif
|
||||
unlet s:msg
|
||||
endif
|
||||
" Stop loading the plug-in; don't define the (automatic) commands.
|
||||
finish
|
||||
endif
|
||||
|
||||
" The plug-in initializes the &tags option as soon as possible so that the
|
||||
" global tags file is available when using "vim -t some_tag". If &tags is
|
||||
" reset, we'll try again on the "VimEnter" automatic command event (below).
|
||||
call xolox#easytags#register(1)
|
||||
|
||||
" The :UpdateTags and :HighlightTags commands. {{{1
|
||||
|
||||
command! -bar -bang -nargs=* -complete=file UpdateTags call xolox#easytags#update(0, <q-bang> == '!', [<f-args>])
|
||||
command! -bar HighlightTags call xolox#easytags#highlight()
|
||||
command! -bang TagsByFileType call xolox#easytags#update#convert_by_filetype(<q-bang> == '!')
|
||||
|
||||
" Automatic commands. {{{1
|
||||
|
||||
augroup PluginEasyTags
|
||||
autocmd!
|
||||
" This is the alternative way of registering the global tags file using
|
||||
" the automatic command event "VimEnter". Apparently this makes the
|
||||
" plug-in behave better when used together with tplugin?
|
||||
autocmd VimEnter * call xolox#easytags#register(1)
|
||||
" Define the automatic commands to perform updating/highlighting.
|
||||
for s:eventname in g:easytags_events
|
||||
if s:eventname !~? 'cursorhold'
|
||||
execute 'autocmd' s:eventname '* call xolox#easytags#autoload(' string(s:eventname) ')'
|
||||
endif
|
||||
endfor
|
||||
" Define an automatic command to register file type specific tags files?
|
||||
if !empty(g:easytags_by_filetype)
|
||||
autocmd FileType * call xolox#easytags#register(0)
|
||||
endif
|
||||
" After reloading a buffer the dynamic syntax highlighting is lost. The
|
||||
" following code makes sure the highlighting is refreshed afterwards.
|
||||
autocmd BufReadPost * unlet! b:easytags_last_highlighted
|
||||
" During :vimgrep each searched buffer triggers an asynchronous tags file
|
||||
" update resulting in races for the tags file. To avoid this we temporarily
|
||||
" disable automatic tags file updates during :vimgrep.
|
||||
autocmd QuickFixCmdPre *vimgrep* call xolox#easytags#disable_automatic_updates()
|
||||
autocmd QuickFixCmdPost *vimgrep* call xolox#easytags#restore_automatic_updates()
|
||||
augroup END
|
||||
|
||||
" Use vim-misc to register an event handler for Vim's CursorHold and
|
||||
" CursorHoldI events which is rate limited so that our event handler is never
|
||||
" called more than once every interval.
|
||||
if index(g:easytags_events, 'CursorHold') >= 0
|
||||
call xolox#misc#cursorhold#register({'function': 'xolox#easytags#autoload', 'arguments': ['CursorHold'], 'interval': xolox#misc#option#get('easytags_updatetime_min', 4000)/1000})
|
||||
endif
|
||||
|
||||
" }}}1
|
||||
|
||||
" Make sure the plug-in is only loaded once.
|
||||
let g:loaded_easytags = 1
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
@@ -1,19 +0,0 @@
|
||||
" Vim plug-in
|
||||
" Author: Peter Odding <peter@peterodding.com>
|
||||
" Last Change: June 21, 2014
|
||||
" URL: http://peterodding.com/code/vim/misc/
|
||||
|
||||
" Don't source the plug-in when it's already been loaded or &compatible is set.
|
||||
if &cp || exists('g:loaded_xolox_misc')
|
||||
finish
|
||||
endif
|
||||
|
||||
" Automatic commands used by the vim-misc plug-in.
|
||||
augroup PluginXoloxMisc
|
||||
autocmd! CursorHold,CursorHoldI * call xolox#misc#cursorhold#autocmd()
|
||||
augroup END
|
||||
|
||||
" Make sure the plug-in is only loaded once.
|
||||
let g:loaded_xolox_misc = 1
|
||||
|
||||
" vim: ts=2 sw=2 et
|
||||
Reference in New Issue
Block a user