From f787f7e65108938fda981abb71f4fdfb08fd9f31 Mon Sep 17 00:00:00 2001
From: Junegunn Choi <junegunn.c@gmail.com>
Date: Tue, 26 Jul 2016 02:35:40 +0900
Subject: [PATCH] [vim] Add fzf#wrap helper function

Close #627
---
 README.md      |  17 +++++-
 plugin/fzf.vim | 147 +++++++++++++++++++++++++++++++++----------------
 test/fzf.vader |  74 +++++++++++++++++++++++++
 3 files changed, 188 insertions(+), 50 deletions(-)

diff --git a/README.md b/README.md
index 220a6cb3..5de73037 100644
--- a/README.md
+++ b/README.md
@@ -320,10 +320,10 @@ customization.
 
 [fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-FZF-command-(vim)
 
-#### `fzf#run([options])`
+#### `fzf#run`
 
-For more advanced uses, you can use `fzf#run()` function with the following
-options.
+For more advanced uses, you can use `fzf#run([options])` function with the
+following options.
 
 | Option name                | Type          | Description                                                      |
 | -------------------------- | ------------- | ---------------------------------------------------------------- |
@@ -342,6 +342,17 @@ options.
 Examples can be found on [the wiki
 page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
 
+#### `fzf#wrap`
+
+`fzf#wrap(name string, [opts dict, [fullscreen boolean]])` is a helper
+function that decorates the options dictionary so that it understands
+`g:fzf_layout`, `g:fzf_action`, and `g:fzf_history_dir` like `:FZF`.
+
+```vim
+command! -bang MyStuff
+  \ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
+```
+
 Tips
 ----
 
diff --git a/plugin/fzf.vim b/plugin/fzf.vim
index 668af7d5..53cb4cba 100644
--- a/plugin/fzf.vim
+++ b/plugin/fzf.vim
@@ -22,6 +22,7 @@
 " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 let s:default_layout = { 'down': '~40%' }
+let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
 let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
 let s:install = expand('<sfile>:h:h').'/install'
 let s:installed = 0
@@ -104,6 +105,101 @@ function! s:warn(msg)
   echohl None
 endfunction
 
+function! s:has_any(dict, keys)
+  for key in a:keys
+    if has_key(a:dict, key)
+      return 1
+    endif
+  endfor
+  return 0
+endfunction
+
+function! s:open(cmd, target)
+  if stridx('edit', a:cmd) == 0 && fnamemodify(a:target, ':p') ==# expand('%:p')
+    return
+  endif
+  execute a:cmd s:escape(a:target)
+endfunction
+
+function! s:common_sink(action, lines) abort
+  if len(a:lines) < 2
+    return
+  endif
+  let key = remove(a:lines, 0)
+  let cmd = get(a:action, key, 'e')
+  if len(a:lines) > 1
+    augroup fzf_swap
+      autocmd SwapExists * let v:swapchoice='o'
+            \| call s:warn('fzf: E325: swap file exists: '.expand('<afile>'))
+    augroup END
+  endif
+  try
+    let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
+    let autochdir = &autochdir
+    set noautochdir
+    for item in a:lines
+      if empty
+        execute 'e' s:escape(item)
+        let empty = 0
+      else
+        call s:open(cmd, item)
+      endif
+      if exists('#BufEnter') && isdirectory(item)
+        doautocmd BufEnter
+      endif
+    endfor
+  finally
+    let &autochdir = autochdir
+    silent! autocmd! fzf_swap
+  endtry
+endfunction
+
+" name string, [opts dict, [fullscreen boolean]]
+function! fzf#wrap(name, ...)
+  if type(a:name) != type('')
+    throw 'invalid name type: string expected'
+  endif
+  let opts = copy(get(a:000, 0, {}))
+  let bang = get(a:000, 1, 0)
+
+  " Layout: g:fzf_layout (and deprecated g:fzf_height)
+  if bang
+    for key in s:layout_keys
+      if has_key(opts, key)
+        call remove(opts, key)
+      endif
+    endfor
+  elseif !s:has_any(opts, s:layout_keys)
+    if !exists('g:fzf_layout') && exists('g:fzf_height')
+      let opts.down = g:fzf_height
+    else
+      let opts = extend(opts, get(g:, 'fzf_layout', s:default_layout))
+    endif
+  endif
+
+  " History: g:fzf_history_dir
+  let opts.options = get(opts, 'options', '')
+  if len(get(g:, 'fzf_history_dir', ''))
+    let dir = expand(g:fzf_history_dir)
+    if !isdirectory(dir)
+      call mkdir(dir, 'p')
+    endif
+    let opts.options = join(['--history', s:escape(dir.'/'.a:name), opts.options])
+  endif
+
+  " Action: g:fzf_action
+  if !s:has_any(opts, ['sink', 'sink*'])
+    let opts._action = get(g:, 'fzf_action', s:default_action)
+    let opts.options .= ' --expect='.join(keys(opts._action), ',')
+    function! opts.sink(lines) abort
+      return s:common_sink(self._action, a:lines)
+    endfunction
+    let opts['sink*'] = remove(opts, 'sink')
+  endif
+
+  return opts
+endfunction
+
 function! fzf#run(...) abort
 try
   let oshell = &shell
@@ -137,7 +233,7 @@ try
       call writefile(source, temps.input)
       let prefix = 'cat '.s:shellesc(temps.input).'|'
     else
-      throw 'Invalid source type'
+      throw 'invalid source type'
     endif
   else
     let prefix = ''
@@ -435,60 +531,17 @@ function! s:callback(dict, lines) abort
 endfunction
 
 let s:default_action = {
-  \ 'ctrl-m': 'e',
   \ 'ctrl-t': 'tab split',
   \ 'ctrl-x': 'split',
   \ 'ctrl-v': 'vsplit' }
 
-function! s:cmd_callback(lines) abort
-  if empty(a:lines)
-    return
-  endif
-  let key = remove(a:lines, 0)
-  let cmd = get(s:action, key, 'e')
-  if len(a:lines) > 1
-    augroup fzf_swap
-      autocmd SwapExists * let v:swapchoice='o'
-            \| call s:warn('fzf: E325: swap file exists: '.expand('<afile>'))
-    augroup END
-  endif
-  try
-    let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
-    let autochdir = &autochdir
-    set noautochdir
-    for item in a:lines
-      if empty
-        execute 'e' s:escape(item)
-        let empty = 0
-      else
-        execute cmd s:escape(item)
-      endif
-      if exists('#BufEnter') && isdirectory(item)
-        doautocmd BufEnter
-      endif
-    endfor
-  finally
-    let &autochdir = autochdir
-    silent! autocmd! fzf_swap
-  endtry
-endfunction
-
 function! s:cmd(bang, ...) abort
-  let s:action = get(g:, 'fzf_action', s:default_action)
-  let args = extend(['--expect='.join(keys(s:action), ',')], a:000)
+  let args = copy(a:000)
   let opts = {}
-  if len(args) > 0 && isdirectory(expand(args[-1]))
+  if len(args) && isdirectory(expand(args[-1]))
     let opts.dir = substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g')
   endif
-  if !a:bang
-    " For backward compatibility
-    if !exists('g:fzf_layout') && exists('g:fzf_height')
-      let opts.down = g:fzf_height
-    else
-      let opts = extend(opts, get(g:, 'fzf_layout', s:default_layout))
-    endif
-  endif
-  call fzf#run(extend({'options': join(args), 'sink*': function('<sid>cmd_callback')}, opts))
+  call fzf#run(fzf#wrap('FZF', extend({'options': join(args)}, opts), a:bang))
 endfunction
 
 command! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)
diff --git a/test/fzf.vader b/test/fzf.vader
index 47f2bfcd..bab5c162 100644
--- a/test/fzf.vader
+++ b/test/fzf.vader
@@ -1,5 +1,6 @@
 Execute (Setup):
   let g:dir = fnamemodify(g:vader_file, ':p:h')
+  unlet! g:fzf_layout g:fzf_action g:fzf_history_dir
   Log 'Test directory: ' . g:dir
   Save &acd
 
@@ -69,6 +70,79 @@ Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
   " Working directory changed due to &acd
   AssertEqual '/', getcwd()
 
+Execute (fzf#wrap):
+  AssertThrows fzf#wrap({'foo': 'bar'})
+
+  let opts = fzf#wrap('foobar')
+  Log opts
+  AssertEqual '~40%', opts.down
+  Assert opts.options =~ '--expect='
+  Assert !has_key(opts, 'sink')
+  Assert has_key(opts, 'sink*')
+
+  let opts = fzf#wrap('foobar', {}, 0)
+  Log opts
+  AssertEqual '~40%', opts.down
+
+  let opts = fzf#wrap('foobar', {}, 1)
+  Log opts
+  Assert !has_key(opts, 'down')
+
+  let opts = fzf#wrap('foobar', {'down': '50%'})
+  Log opts
+  AssertEqual '50%', opts.down
+
+  let opts = fzf#wrap('foobar', {'down': '50%'}, 1)
+  Log opts
+  Assert !has_key(opts, 'down')
+
+  let opts = fzf#wrap('foobar', {'sink': 'e'})
+  Log opts
+  AssertEqual 'e', opts.sink
+  Assert !has_key(opts, 'sink*')
+
+  let opts = fzf#wrap('foobar', {'options': '--reverse'})
+  Log opts
+  Assert opts.options =~ '--expect='
+  Assert opts.options =~ '--reverse'
+
+  let g:fzf_layout = {'window': 'enew'}
+  let opts = fzf#wrap('foobar')
+  Log opts
+  AssertEqual 'enew', opts.window
+
+  let opts = fzf#wrap('foobar', {}, 1)
+  Log opts
+  Assert !has_key(opts, 'window')
+
+  let opts = fzf#wrap('foobar', {'right': '50%'})
+  Log opts
+  Assert !has_key(opts, 'window')
+  AssertEqual '50%', opts.right
+
+  let opts = fzf#wrap('foobar', {'right': '50%'}, 1)
+  Log opts
+  Assert !has_key(opts, 'window')
+  Assert !has_key(opts, 'right')
+
+  let g:fzf_action = {'a': 'tabe'}
+  let opts = fzf#wrap('foobar')
+  Log opts
+  Assert opts.options =~ '--expect=a'
+  Assert !has_key(opts, 'sink')
+  Assert has_key(opts, 'sink*')
+
+  let opts = fzf#wrap('foobar', {'sink': 'e'})
+  Log opts
+  AssertEqual 'e', opts.sink
+  Assert !has_key(opts, 'sink*')
+
+  let g:fzf_history_dir = '/tmp'
+  let opts = fzf#wrap('foobar', {'options': '--color light'})
+  Log opts
+  Assert opts.options =~ '--history /tmp/foobar'
+  Assert opts.options =~ '--color light'
+
 Execute (Cleanup):
   unlet g:dir
   Restore
-- 
GitLab