From e7439ce193f40f55ae128c2ae9426a5b9282b21c Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 25 Mar 2014 19:55:52 +0900 Subject: [PATCH] Major update to Vim plugin --- README.md | 71 +++++++++++++++++++++++++++++++++++------ plugin/fzf.vim | 86 ++++++++++++++++++++++++++++++++++++++------------ test/fzf.vader | 37 ++++++++++++++++++++++ 3 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 test/fzf.vader diff --git a/README.md b/README.md index b8fc8909..3c6ac955 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,9 @@ TODO :smiley: Usage as Vim plugin ------------------- -If you install fzf as a Vim plugin, `:FZF` command will be added. +### `:FZF` + +If you have set up fzf as a Vim plugin, `:FZF` command will be added. ```vim " Look for files under current directory @@ -303,27 +305,76 @@ If you install fzf as a Vim plugin, `:FZF` command will be added. :FZF --no-sort -m /tmp ``` -You can override the source command which produces input to fzf. +Note that environment variables `FZF_DEFAULT_COMMAND` and `FZF_DEFAULT_OPTS` +also apply here. + +### `fzf#run([options])` + +For more advanced uses, you can call `fzf#run()` function which returns the list +of the selected items. + +`fzf#run()` may take an options-dictionary: + +| Option name | Type | Description | +| ----------- | ------- | ---------------------------------------------------------- | +| `source` | string | External command to generate input to fzf (e.g. `find .`) | +| `source` | list | Vim list as input to fzf | +| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) | +| `sink` | funcref | Reference to function to process each selected item | +| `options` | string | Options to fzf | +| `dir` | string | Working directory | + +#### Examples + +If `sink` option is not given, `fzf#run` will simply return the list. ```vim -let g:fzf_source = 'find . -type f' +let items = fzf#run({ 'options': '-m +c', 'dir': '~', 'source': 'ls' }) ``` -And you can predefine default options to fzf command. +But if `sink` is given as a string, the command will be executed for each +selected item. ```vim -let g:fzf_options = '--no-color --extended' +" Each selected item will be opened in a new tab +let items = fzf#run({ 'sink': 'tabe', 'options': '-m +c', 'dir': '~', 'source': 'ls' }) ``` -For more advanced uses, you can call `fzf#run` function as follows. +We can also use a Vim list as the source as follows: ```vim -:call fzf#run('tabedit', '-m +c') +" Choose a color scheme with fzf +call fzf#run({ +\ 'source': +\ map(split(globpath(&rtp, "colors/*.vim"), "\n"), +\ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"), +\ 'sink': 'colo', +\ 'options': '+m' +\ }) ``` -Most of the time, you will prefer native Vim plugins with better integration -with Vim. The only reason one might consider using fzf in Vim is its speed. For -a very large list of files, fzf is significantly faster and it does not block. +`sink` option can be a function reference. The following example creates a +handy mapping that selects an open buffer. + +```vim +" List of buffers +function! g:buflist() + redir => ls + silent ls + redir END + return split(ls, '\n') +endfunction + +function! g:bufopen(e) + execute 'buffer '. matchstr(a:e, '^[ 0-9]*') +endfunction + +nnoremap :call fzf#run({ +\ 'source': g:buflist(), +\ 'sink': function('g:bufopen'), +\ 'options': '+m +s', +\ }) +``` Tips ---- diff --git a/plugin/fzf.vim b/plugin/fzf.vim index 43e73d70..5d7e09f3 100644 --- a/plugin/fzf.vim +++ b/plugin/fzf.vim @@ -1,4 +1,4 @@ -" Copyright (c) 2013 Junegunn Choi +" Copyright (c) 2014 Junegunn Choi " " MIT License " @@ -21,6 +21,9 @@ " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +let s:cpo_save = &cpo +set cpo&vim + call system('type fzf') if v:shell_error let s:fzf_rb = expand(':h:h').'/fzf' @@ -38,32 +41,73 @@ function! s:escape(path) return substitute(a:path, ' ', '\\ ', 'g') endfunction -function! fzf#run(command, ...) - let cwd = getcwd() - try - let args = copy(a:000) - if len(args) > 0 && isdirectory(expand(args[-1])) - let dir = remove(args, -1) - execute 'chdir '.s:escape(dir) +function! fzf#run(...) abort + let dict = exists('a:1') ? a:1 : {} + let temps = [tempname()] + let result = temps[0] + let optstr = get(dict, 'options', '') + let cd = has_key(dict, 'dir') + + if has_key(dict, 'source') + let source = dict.source + let type = type(source) + if type == 1 + let prefix = source.'|' + elseif type == 3 + let input = add(temps, tempname())[-1] + call writefile(source, input) + let prefix = 'cat '.s:escape(input).'|' + else + throw 'Invalid source type' endif - let argstr = join(args) - let tf = tempname() - let prefix = exists('g:fzf_source') ? g:fzf_source.'|' : '' - let options = empty(argstr) ? get(g:, 'fzf_options', '') : argstr - execute 'silent !'.prefix.s:exec.' '.options.' > '.tf - if !v:shell_error - for line in readfile(tf) - if !empty(line) - execute a:command.' '.s:escape(line) + else + let prefix = '' + endif + + try + if cd + let cwd = getcwd() + execute 'chdir '.s:escape(dict.dir) + endif + execute 'silent !'.prefix.s:exec.' '.optstr.' > '.result + redraw! + if v:shell_error + return [] + endif + + let lines = readfile(result) + + if has_key(dict, 'sink') + for line in lines + if type(dict.sink) == 2 + call dict.sink(line) + else + execute dict.sink.' '.s:escape(line) endif endfor endif + return lines finally - execute 'chdir '.s:escape(cwd) - redraw! - silent! call delete(tf) + if cd + execute 'chdir '.s:escape(cwd) + endif + for tf in temps + silent! call delete(tf) + endfor endtry endfunction -command! -nargs=* -complete=dir FZF call fzf#run('silent e', ) +function! s:cmd(...) + let args = copy(a:000) + let opts = {} + if len(args) > 0 && isdirectory(expand(args[-1])) + let opts.dir = remove(args, -1) + endif + call fzf#run(extend({ 'sink': 'e', 'options': join(args) }, opts)) +endfunction + +command! -nargs=* -complete=dir FZF call s:cmd() + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/test/fzf.vader b/test/fzf.vader new file mode 100644 index 00000000..5d40142f --- /dev/null +++ b/test/fzf.vader @@ -0,0 +1,37 @@ +Execute (Setup): + let g:dir = fnamemodify(g:vader_file, ':p:h') + Log 'Test directory: ' . g:dir + +Execute (fzf#run with dir option): + let result = fzf#run({ 'options': '--filter=vdr', 'dir': g:dir }) + AssertEqual ['fzf.vader'], result + + let result = sort(fzf#run({ 'options': '--filter e', 'dir': g:dir })) + AssertEqual ['fzf.vader', 'test_fzf.rb'], result + +Execute (fzf#run with Funcref command): + let g:ret = [] + function! g:proc(e) + call add(g:ret, a:e) + endfunction + let result = sort(fzf#run({ 'sink': function('g:proc'), 'options': '--filter e', 'dir': g:dir })) + AssertEqual ['fzf.vader', 'test_fzf.rb'], result + AssertEqual ['fzf.vader', 'test_fzf.rb'], sort(g:ret) + +Execute (fzf#run with string source): + let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' })) + AssertEqual ['hi'], result + +Execute (fzf#run with list source): + let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f e' })) + AssertEqual ['hello'], result + let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f o' })) + AssertEqual ['hello', 'world'], result + +Execute (fzf#run with string source): + let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' })) + AssertEqual ['hi'], result + +Execute (Cleanup): + unlet g:dir + Restore