diff --git a/plugin/fzf.vim b/plugin/fzf.vim index e82ef11..fbfcd1d 100644 --- a/plugin/fzf.vim +++ b/plugin/fzf.vim @@ -44,20 +44,28 @@ if s:is_win let &shellslash = shellslash endtry endfunction - - function! s:fzf_shellescape(path) - return substitute(s:fzf_call('shellescape', a:path), '[^\\]\zs\\"$', '\\\\"', '') - endfunction else function! s:fzf_call(fn, ...) return call(a:fn, a:000) endfunction - - function! s:fzf_shellescape(path) - return shellescape(a:path) - endfunction endif +function! s:shellesc_cmd(arg) + let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') + let escaped = substitute(escaped, '%', '%%', 'g') + let escaped = substitute(escaped, '"', '\\^&', 'g') + let escaped = substitute(escaped, '\\\+\(\\^\)', '\\\\\1', 'g') + return '^"'.substitute(escaped, '[^\\]\zs\\$', '\\\\', '').'^"' +endfunction + +function! fzf#shellescape(arg, ...) + let shell = get(a:000, 0, &shell) + if shell =~# 'cmd.exe$' + return s:shellesc_cmd(a:arg) + endif + return s:fzf_call('shellescape', a:arg) +endfunction + function! s:fzf_getcwd() return s:fzf_call('getcwd') endfunction @@ -108,7 +116,7 @@ function! s:fzf_exec() throw 'fzf executable not found' endif endif - return s:is_win ? s:exec : s:shellesc(s:exec) + return s:is_win ? s:exec : fzf#shellescape(s:exec) endfunction function! s:tmux_enabled() @@ -128,10 +136,6 @@ function! s:tmux_enabled() return s:tmux endfunction -function! s:shellesc(arg) - return '"'.substitute(a:arg, '"', '\\"', 'g').'"' -endfunction - function! s:escape(path) let escaped_chars = '$%#''"' @@ -250,7 +254,7 @@ endfunction function! s:evaluate_opts(options) return type(a:options) == type([]) ? - \ join(map(copy(a:options), 's:fzf_shellescape(v:val)')) : a:options + \ join(map(copy(a:options), 'fzf#shellescape(v:val)')) : a:options endfunction " [name string,] [opts dict,] [fullscreen boolean] @@ -297,7 +301,7 @@ function! fzf#wrap(...) if !isdirectory(dir) call mkdir(dir, 'p') endif - let history = s:is_win ? s:fzf_shellescape(dir.'\'.name) : s:escape(dir.'/'.name) + let history = s:is_win ? fzf#shellescape(dir.'\'.name) : s:escape(dir.'/'.name) let opts.options = join(['--history', history, opts.options]) endif @@ -349,7 +353,7 @@ try if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND) let temps.source = s:fzf_tempname().(s:is_win ? '.bat' : '') call writefile((s:is_win ? ['@echo off'] : []) + split($FZF_DEFAULT_COMMAND, "\n"), temps.source) - let dict.source = (empty($SHELL) ? &shell : $SHELL) . (s:is_win ? ' /c ' : ' ') . s:shellesc(temps.source) + let dict.source = (empty($SHELL) ? &shell : $SHELL) . (s:is_win ? ' /c ' : ' ') . fzf#shellescape(temps.source) endif if has_key(dict, 'source') @@ -360,7 +364,7 @@ try elseif type == 3 let temps.input = s:fzf_tempname() call writefile(source, temps.input) - let prefix = (s:is_win ? 'type ' : 'cat ').s:shellesc(temps.input).'|' + let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|' else throw 'Invalid source type' endif @@ -424,7 +428,7 @@ function! s:fzf_tmux(dict) endif endfor return printf('LINES=%d COLUMNS=%d %s %s %s --', - \ &lines, &columns, s:shellesc(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-')) + \ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-')) endfunction function! s:splittable(dict) @@ -493,7 +497,7 @@ function! s:execute(dict, command, use_height, temps) abort if has('unix') && !a:use_height silent! !clear 2> /dev/null endif - let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#!') + let escaped = (a:use_height || s:is_win) ? a:command : escape(substitute(a:command, '\n', '\\n', 'g'), '%#!') if has('gui_running') let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher))) let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher @@ -502,7 +506,7 @@ function! s:execute(dict, command, use_height, temps) abort endif let command = printf(fmt, escaped) else - let command = a:use_height ? a:command : escaped + let command = escaped endif if s:is_win let batchfile = s:fzf_tempname().'.bat' diff --git a/test/fzf.vader b/test/fzf.vader index c6f899f..64a5c7b 100644 --- a/test/fzf.vader +++ b/test/fzf.vader @@ -147,6 +147,24 @@ Execute (fzf#wrap): let opts = fzf#wrap({}) Assert opts.options =~ '^--color=fg:' +Execute (fzf#shellescape with sh): + AssertEqual '''''', fzf#shellescape('', 'sh') + AssertEqual '''""''', fzf#shellescape('""', 'sh') + AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh') + AssertEqual '''\"''', fzf#shellescape('\"', 'sh') + AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh') + +Execute (fzf#shellescape with cmd.exe): + AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe') + AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe') + AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe') + AssertEqual '^"\\\^"\\^"', fzf#shellescape('\\\\\\\\"\', 'cmd.exe') + AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe') + + AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe') + AssertEqual '^"C:/Program Files ^(x86^)/^"', fzf#shellescape('C:/Program Files (x86)/', 'cmd.exe') + " AssertEqual '^"%%USERPROFILE%%^", fzf#shellescape('%USERPROFILE%', 'cmd.exe') + Execute (Cleanup): unlet g:dir Restore