From 8aab0fc1894facf9ac51132ff8a832b18bc115ac Mon Sep 17 00:00:00 2001
From: Jan Edmund Lazo <janlazo@users.noreply.github.com>
Date: Sun, 28 May 2017 21:06:06 -0400
Subject: [PATCH] [vim] Replace s:fzf_shellescape and s:shellesc with
 fzf#shellescape (#916)

---
 plugin/fzf.vim | 44 ++++++++++++++++++++++++--------------------
 test/fzf.vader | 18 ++++++++++++++++++
 2 files changed, 42 insertions(+), 20 deletions(-)

diff --git a/plugin/fzf.vim b/plugin/fzf.vim
index e82ef115..fbfcd1dd 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 c6f899f0..64a5c7b8 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
-- 
GitLab