diff --git a/README.md b/README.md index 7b7c1fa..93c7628 100644 --- a/README.md +++ b/README.md @@ -374,8 +374,8 @@ On bash, fuzzy completion is enabled only for a predefined set of commands commands as well like follows. ```sh -# There are also _fzf_path_completion and _fzf_dir_completion -complete -F _fzf_file_completion -o default -o bashdefault doge +complete -F _fzf_path_completion -o default -o bashdefault ag +complete -F _fzf_dir_completion -o default -o bashdefault tree ``` Vim plugin diff --git a/plugin/fzf.vim b/plugin/fzf.vim index e82ef11..8bdeb3b 100644 --- a/plugin/fzf.vim +++ b/plugin/fzf.vim @@ -45,19 +45,39 @@ if s:is_win endtry endfunction - function! s:fzf_shellescape(path) - return substitute(s:fzf_call('shellescape', a:path), '[^\\]\zs\\"$', '\\\\"', '') + " Use utf-8 for fzf.vim commands + " Return array of shell commands for cmd.exe + function! s:wrap_cmds(cmds) + return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] + + \ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) + + \ ['chcp %origchcp% > nul'] endfunction else function! s:fzf_call(fn, ...) return call(a:fn, a:000) endfunction - function! s:fzf_shellescape(path) - return shellescape(a:path) + function! s:wrap_cmds(cmds) + return a:cmds 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 +128,7 @@ function! s:fzf_exec() throw 'fzf executable not found' endif endif - return s:is_win ? s:exec : s:shellesc(s:exec) + return fzf#shellescape(s:exec) endfunction function! s:tmux_enabled() @@ -128,10 +148,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 +266,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 +313,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 = fzf#shellescape(dir.'/'.name) let opts.options = join(['--history', history, opts.options]) endif @@ -348,8 +364,8 @@ 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) + call writefile(s:wrap_cmds(split($FZF_DEFAULT_COMMAND, "\n")), 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 +376,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 +440,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 +509,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,12 +518,13 @@ 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' - call writefile(['@echo off', command], batchfile) + call writefile(s:wrap_cmds(command), batchfile) let command = batchfile + let a:temps.batchfile = batchfile if has('nvim') let s:dict = a:dict let s:temps = a:temps @@ -536,7 +553,7 @@ function! s:execute_tmux(dict, command, temps) abort let command = a:command if s:pushd(a:dict) " -c '#{pane_current_path}' is only available on tmux 1.9 or above - let command = 'cd '.s:escape(a:dict.dir).' && '.command + let command = join(['cd', fzf#shellescape(a:dict.dir), '&&', command]) endif call system(command) 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