Merge pull request #135 from darcyparker/issue133_ctagsbin

Improved `s:EscapeCtagsCmd()` for windows `cmd.exe` to eliminate issues when `a:ctags_bin` calls a batch file that uses `%~dp0`.
This commit is contained in:
Jan Larres 2013-04-23 04:19:09 -07:00
commit 1c605bd958
2 changed files with 104 additions and 26 deletions

View File

@ -983,7 +983,7 @@ function! s:CreateAutocommands() abort
endfunction
" s:PauseAutocommands() {{{2
" Toggle autocommands
" Toggle autocommands
function! s:PauseAutocommands() abort
if s:autocommands_enabled == 1
autocmd! TagbarAutoCmds
@ -2030,19 +2030,31 @@ function! s:ExecuteCtagsOnFile(fname, realfname, ftype) abort
let typeinfo = s:known_types[a:ftype]
if has_key(typeinfo, 'ctagsargs')
if has_key(typeinfo, 'ctagsargs') && type(typeinfo.ctagsargs)==type(' ')
"if ctagsargs is a string, prepend and append space seperators
let ctags_args = ' ' . typeinfo.ctagsargs . ' '
elseif has_key(typeinfo, 'ctagsargs') && type(typeinfo.ctagsargs)==type([])
let ctags_args = typeinfo.ctagsargs
"otherwise ctagsargs is not defined or not defined as a valid type
else
let ctags_args = ' -f - '
let ctags_args .= ' --format=2 '
let ctags_args .= ' --excmd=pattern '
let ctags_args .= ' --fields=nksSa '
let ctags_args .= ' --extra= '
let ctags_args .= ' --sort=yes '
"Prefer constructing ctags_args as a list rather than a string
"See s:EscapeCtagsCmd() - It's a best practice to shellescape()
"each arg separately because in special cases where space is
"intended to be in an argument, spaces in a single ctag_args
"string would be ambiguous. Is the space an argument separator
"or to be included in the argument
let ctags_args = [ '-f',
\ '-',
\ '--format=2',
\ '--excmd=pattern',
\ '--fields=nksSa',
\ '--extra=',
\ '--sort=yes'
\ ]
" Include extra type definitions
if has_key(typeinfo, 'deffile')
let ctags_args .= ' --options=' . typeinfo.deffile . ' '
let ctags_args += ['--options=' . typeinfo.deffile]
endif
let ctags_type = typeinfo.ctagstype
@ -2052,8 +2064,8 @@ function! s:ExecuteCtagsOnFile(fname, realfname, ftype) abort
let ctags_kinds .= kind.short
endfor
let ctags_args .= ' --language-force=' . ctags_type .
\ ' --' . ctags_type . '-kinds=' . ctags_kinds . ' '
let ctags_args += ['--language-force=' . ctags_type]
let ctags_args += ['--' . ctags_type . '-kinds=' . ctags_kinds]
endif
if has_key(typeinfo, 'ctagsbin')
@ -3195,32 +3207,79 @@ endfunction
" Assemble the ctags command line in a way that all problematic characters are
" properly escaped and converted to the system's encoding
" Optional third parameter is a file name to run ctags on
" Note: The second parameter (a:args) can be a list of args or
" a single string of the args.
" When a:args is a list, each argument in the list will be escaped for the
" current &shell type.
" When a:args is a string, all arguments should be escaped appropriately
" (if required). In most use cases no escaping is required so a string
" is acceptable. But in cases where arguments may need to be escaped
" differently for each &shell type, then pass a list of arguments.
function! s:EscapeCtagsCmd(ctags_bin, args, ...) abort
call s:LogDebugMessage('EscapeCtagsCmd called')
call s:LogDebugMessage('ctags_bin: ' . a:ctags_bin)
call s:LogDebugMessage('ctags_args: ' . a:args)
if type(a:args)==type('')
call s:LogDebugMessage('ctags_args (is a string): ' . a:args)
elseif type(a:args)==type([])
call s:LogDebugMessage('ctags_args (is a list): ' . string(a:args))
endif
if exists('+shellslash')
let shellslash_save = &shellslash
set noshellslash
endif
if a:0 == 1
let fname = shellescape(a:1)
"Set up 0th argument of ctags_cmd
"a:ctags_bin may have special characters that require escaping.
if &shell =~ 'cmd\.exe$' && a:ctags_bin !~ '\s'
"For windows cmd.exe, escaping the 0th argument can cause
"problems if it references a batch file and the batch file uses %~dp0.
"So for windows cmd.exe, only escape the 0th argument iff necessary.
"Only known necessary case is when ctags_bin executable filename has
"whitespace character(s).
" Example: If 0th argument is wrapped in double quotes AND it is not
" an absolute path to ctags_bin, but rather an executable in %PATH%,
" then %~dp0 resolves to the current working directory rather than
" the batch file's directory. Batch files like this generally exepect
" and depend on %~dp0 to resolve the batch file's directory.
" Note: Documentation such as `help cmd.exe` and
" http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true
" suggest other special characters that require escaping for command
" line completion. But tagbar.vim does not use the command line
" completion feature of cmd.exe and testing shows that the only special
" character that needs to be escaped for tagbar.vim is <space> for
" windows cmd.exe.
let ctags_cmd = a:ctags_bin
else
let fname = ''
let ctags_cmd = shellescape(a:ctags_bin)
endif
let ctags_cmd = shellescape(a:ctags_bin) . ' ' . a:args . ' ' . fname
"Add additional arguments to ctags_cmd
if type(a:args)==type('')
"When a:args is a string, append the arguments
"Note: In this case, do not attempt to shell escape a:args string.
"This function expects the string to already be escaped properly for
"the shell type. Why not escape? Because it could be ambiguous about
"whether a space is an argument separator or included in the argument.
"Since escaping rules vary from shell to shell, it is better to pass a
"list of arguments to a:args. With a list, each argument is clearly
"separated, so shellescape() can calculate the appropriate escaping
"for each argument for the current &shell.
let ctags_cmd .= ' ' . a:args
elseif type(a:args)==type([])
"When a:args is a list, shellescape() each argument and append ctags_cmd
"Note: It's a better practice to shellescape() each argument separately so that
"spaces used as a separator between arguments can be distinguished with
"spaces used inside a single argument.
for arg in a:args
let ctags_cmd .= ' ' . shellescape(arg)
endfor
endif
" Stupid cmd.exe quoting
if &shell =~ 'cmd\.exe'
let reserved_chars = '&()@^'
" not allowed in filenames, but escape anyway just in case
let reserved_chars .= '<>|'
let pattern = join(split(reserved_chars, '\zs'), '\|')
let ctags_cmd = substitute(ctags_cmd, '\V\(' . pattern . '\)',
\ '^\0', 'g')
"if a filename was specified, add filename as final argument to ctags_cmd.
if a:0 == 1
let ctags_cmd .= ' ' . shellescape(a:1)
endif
if exists('+shellslash')
@ -3589,7 +3648,7 @@ function! tagbar#RestoreSession() abort
endfunction
function! tagbar#PauseAutocommands() abort
call s:PauseAutocommands()
call s:PauseAutocommands()
endfunction
" }}}2

View File

@ -268,7 +268,7 @@ COMMANDS *tagbar-commands*
an alias for ":TagbarOpen fjc".
:TagbarTogglePause *:TagbarTogglePause*
Freezes/Unfreezes the Tagbar window. Stops the contents of the window
Freezes/Unfreezes the Tagbar window. Stops the contents of the window
from changing when a different source file is selected.
:TagbarSetFoldlevel[!] {number} *:TagbarSetFoldlevel*
@ -793,6 +793,25 @@ ctagsargs: The arguments to be passed to the filetype-specific ctags program
program output its data on stdout. Not used for the normal ctags
program.
The value of ctagsargs may be a |List| of strings (a string for
each argument), or a single string (|expr-string|) of all the
arguments.
When the value of ctagsargs is a list, tagbar.vim takes care of
escaping each argument in the list as required for the current
'shell' type.
When the value of ctagsargs is a string, it must be properly
escaped (if required by the current shell type). The reason
tagbar.vim does not attempt to escape the string in this case is
because if there is a space, it is ambiguous as to whether the
space is delimiting an argument or included in the argument. To
avoid this amiguity, tagbar.vim expects the string to be already
escaped as required.
If special escaping is required for different OS shell types or if
in doubt, then it is recommended to define ctagsargs with a List.
You then have to assign this dictionary to a variable in your vimrc with the
name