mirror of
https://github.com/junegunn/fzf.vim.git
synced 2026-01-09 21:12:32 +08:00
* Nanoseconds instead of seconds for buffer timestmaps Fzf :Buffers uses a dictionary where it stores the last time accessed of the current buffers. This is used to sort the buffers. The timestamp is retrieved from a call to localtime() which returns the current second the buffer is accessed. However, fzf functions in such a way that before it enters the newly selected buffer, it enters the current buffer. Sometimes this causes the last two buffers to have the same timestamp. Repeatedly switching between two buffers yields inconsistent results. This commit fixes the issue by using seconds and microseconds from the bash call `date`. The buffers are now guaranteed to be sorted by last accessed. * Use reltime() instead of linux date This solution is better for compatability reasons. * Remove trailing whitespace
1089 lines
33 KiB
VimL
1089 lines
33 KiB
VimL
" Copyright (c) 2016 Junegunn Choi
|
|
"
|
|
" MIT License
|
|
"
|
|
" Permission is hereby granted, free of charge, to any person obtaining
|
|
" a copy of this software and associated documentation files (the
|
|
" "Software"), to deal in the Software without restriction, including
|
|
" without limitation the rights to use, copy, modify, merge, publish,
|
|
" distribute, sublicense, and/or sell copies of the Software, and to
|
|
" permit persons to whom the Software is furnished to do so, subject to
|
|
" the following conditions:
|
|
"
|
|
" The above copyright notice and this permission notice shall be
|
|
" included in all copies or substantial portions of the Software.
|
|
"
|
|
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
" 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
|
|
|
|
" ------------------------------------------------------------------
|
|
" Common
|
|
" ------------------------------------------------------------------
|
|
function! fzf#vim#wrap(opts)
|
|
return extend(copy(a:opts), {
|
|
\ 'options': get(a:opts, 'options', '').' --expect='.join(keys(get(g:, 'fzf_action', s:default_action)), ','),
|
|
\ 'sink*': get(a:opts, 'sink*', s:function('s:common_sink'))})
|
|
endfunction
|
|
|
|
function! fzf#vim#layout(...)
|
|
return (a:0 && a:1) ? {} : copy(get(g:, 'fzf_layout', g:fzf#vim#default_layout))
|
|
endfunction
|
|
|
|
function! s:strip(str)
|
|
return substitute(a:str, '^\s*\|\s*$', '', 'g')
|
|
endfunction
|
|
|
|
function! s:chomp(str)
|
|
return substitute(a:str, '\n*$', '', 'g')
|
|
endfunction
|
|
|
|
function! s:escape(path)
|
|
return escape(a:path, ' $%#''"\')
|
|
endfunction
|
|
|
|
if v:version >= 704
|
|
function! s:function(name)
|
|
return function(a:name)
|
|
endfunction
|
|
else
|
|
function! s:function(name)
|
|
" By Ingo Karkat
|
|
return function(substitute(a:name, '^s:', matchstr(expand('<sfile>'), '<SNR>\d\+_\zefunction$'), ''))
|
|
endfunction
|
|
endif
|
|
|
|
function! s:get_color(attr, ...)
|
|
for group in a:000
|
|
let code = synIDattr(synIDtrans(hlID(group)), a:attr, 'cterm')
|
|
if code =~ '^[0-9]\+$'
|
|
return code
|
|
endif
|
|
endfor
|
|
return ''
|
|
endfunction
|
|
|
|
let s:ansi = {'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36}
|
|
|
|
function! s:ansi(str, group, default, ...)
|
|
let fg = s:get_color('fg', a:group)
|
|
let bg = s:get_color('bg', a:group)
|
|
let color = (empty(fg) ? s:ansi[a:default] : ('38;5;'.fg)) .
|
|
\ (empty(bg) ? '' : (';48;5;'.bg))
|
|
return printf("\x1b[%s%sm%s\x1b[m", color, a:0 ? ';1' : '', a:str)
|
|
endfunction
|
|
|
|
for s:color_name in keys(s:ansi)
|
|
execute "function! s:".s:color_name."(str, ...)\n"
|
|
\ " return s:ansi(a:str, get(a:, 1, ''), '".s:color_name."')\n"
|
|
\ "endfunction"
|
|
endfor
|
|
|
|
function! s:buflisted()
|
|
return filter(range(1, bufnr('$')), 'buflisted(v:val)')
|
|
endfunction
|
|
|
|
function! s:defaults()
|
|
let rules = copy(get(g:, 'fzf_colors', {}))
|
|
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
|
|
return empty(colors) ? '' : ('--color='.colors)
|
|
endfunction
|
|
|
|
function! s:fzf(opts, extra)
|
|
let extra = empty(a:extra) ? fzf#vim#layout() : a:extra[0]
|
|
let eopts = has_key(extra, 'options') ? remove(extra, 'options') : ''
|
|
let merged = extend(copy(a:opts), extra)
|
|
let merged.options = join(filter([s:defaults(), get(merged, 'options', ''), eopts], '!empty(v:val)'))
|
|
call fzf#run(merged)
|
|
return 1
|
|
endfunction
|
|
|
|
let s:default_action = {
|
|
\ 'ctrl-t': 'tab split',
|
|
\ 'ctrl-x': 'split',
|
|
\ 'ctrl-v': 'vsplit' }
|
|
|
|
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(lines) abort
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
let key = remove(a:lines, 0)
|
|
let cmd = get(get(g:, 'fzf_action', s:default_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
|
|
|
|
function! s:align_lists(lists)
|
|
let maxes = {}
|
|
for list in a:lists
|
|
let i = 0
|
|
while i < len(list)
|
|
let maxes[i] = max([get(maxes, i, 0), len(list[i])])
|
|
let i += 1
|
|
endwhile
|
|
endfor
|
|
for list in a:lists
|
|
call map(list, "printf('%-'.maxes[v:key].'s', v:val)")
|
|
endfor
|
|
return a:lists
|
|
endfunction
|
|
|
|
function! s:warn(message)
|
|
echohl WarningMsg
|
|
echom a:message
|
|
echohl None
|
|
return 0
|
|
endfunction
|
|
|
|
function! s:uniq(list)
|
|
let visited = {}
|
|
let ret = []
|
|
for l in a:list
|
|
if !empty(l) && !has_key(visited, l)
|
|
call add(ret, l)
|
|
let visited[l] = 1
|
|
endif
|
|
endfor
|
|
return ret
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Files
|
|
" ------------------------------------------------------------------
|
|
function! fzf#vim#files(dir, ...)
|
|
let args = {'options': '-m'}
|
|
if !empty(a:dir)
|
|
if !isdirectory(expand(a:dir))
|
|
return s:warn('Invalid directory')
|
|
endif
|
|
let dir = substitute(a:dir, '/*$', '/', '')
|
|
let args.dir = dir
|
|
let args.options .= ' --prompt '.shellescape(dir)
|
|
else
|
|
let args.options .= ' --prompt '.shellescape(pathshorten(getcwd())).'/'
|
|
endif
|
|
|
|
return s:fzf(fzf#vim#wrap(args), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Lines
|
|
" ------------------------------------------------------------------
|
|
function! s:line_handler(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
normal! m'
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], '')
|
|
if !empty(cmd) && stridx('edit', cmd) < 0
|
|
execute 'silent' cmd
|
|
endif
|
|
|
|
let keys = split(a:lines[1], '\t')
|
|
execute 'buffer' keys[0]
|
|
execute keys[2]
|
|
normal! ^zz
|
|
endfunction
|
|
|
|
function! fzf#vim#_lines(all)
|
|
let cur = []
|
|
let rest = []
|
|
let buf = bufnr('')
|
|
let longest_name = 0
|
|
let display_bufnames = &columns > 100
|
|
if display_bufnames
|
|
let bufnames = {}
|
|
for b in s:buflisted()
|
|
let bufnames[b] = pathshorten(fnamemodify(bufname(b), ":~:."))
|
|
let longest_name = max([longest_name, len(bufnames[b])])
|
|
endfor
|
|
endif
|
|
let len_bufnames = min([15, longest_name])
|
|
for b in s:buflisted()
|
|
let lines = getbufline(b, 1, "$")
|
|
if empty(lines)
|
|
let path = fnamemodify(bufname(b), ':p')
|
|
let lines = filereadable(path) ? readfile(path) : []
|
|
endif
|
|
if display_bufnames
|
|
let bufname = bufnames[b]
|
|
if len(bufname) > len_bufnames + 1
|
|
let bufname = '…' . bufname[-(len_bufnames+1):]
|
|
endif
|
|
let bufname = printf(s:green("%".len_bufnames."s", "Directory"), bufname)
|
|
else
|
|
let bufname = ''
|
|
endif
|
|
call extend(b == buf ? cur : rest,
|
|
\ filter(
|
|
\ map(lines,
|
|
\ '(!a:all && empty(v:val)) ? "" : printf(s:blue("%2d\t", "TabLine")."%s".s:yellow("\t%4d ", "LineNr")."\t%s", b, bufname, v:key + 1, v:val)'),
|
|
\ 'a:all || !empty(v:val)'))
|
|
endfor
|
|
return [display_bufnames, extend(cur, rest)]
|
|
endfunction
|
|
|
|
function! fzf#vim#lines(...)
|
|
let [display_bufnames, lines] = fzf#vim#_lines(1)
|
|
let nth = display_bufnames ? 3 : 2
|
|
let [query, args] = (a:0 && type(a:1) == type('')) ?
|
|
\ [a:1, a:000[1:]] : ['', a:000]
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': lines,
|
|
\ 'sink*': s:function('s:line_handler'),
|
|
\ 'options': '+m --tiebreak=index --prompt "Lines> " --ansi --extended --nth='.nth.'.. --reverse --tabstop=1'.s:q(query)
|
|
\}), args)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" BLines
|
|
" ------------------------------------------------------------------
|
|
function! s:buffer_line_handler(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
normal! m'
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], '')
|
|
if !empty(cmd)
|
|
execute 'silent' cmd
|
|
endif
|
|
|
|
execute split(a:lines[1], '\t')[0]
|
|
normal! ^zz
|
|
endfunction
|
|
|
|
function! s:buffer_lines()
|
|
return map(getline(1, "$"),
|
|
\ 'printf(s:yellow(" %4d ", "LineNr")."\t%s", v:key + 1, v:val)')
|
|
endfunction
|
|
|
|
function! fzf#vim#buffer_lines(...)
|
|
let [query, args] = (a:0 && type(a:1) == type('')) ?
|
|
\ [a:1, a:000[1:]] : ['', a:000]
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': s:buffer_lines(),
|
|
\ 'sink*': s:function('s:buffer_line_handler'),
|
|
\ 'options': '+m --tiebreak=index --prompt "BLines> " --ansi --extended --nth=2.. --reverse --tabstop=1'.s:q(query)
|
|
\}), args)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Colors
|
|
" ------------------------------------------------------------------
|
|
function! fzf#vim#colors(...)
|
|
return s:fzf({
|
|
\ 'source': map(split(globpath(&rtp, "colors/*.vim"), "\n"),
|
|
\ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"),
|
|
\ 'sink': 'colo',
|
|
\ 'options': '+m --prompt="Colors> "'
|
|
\}, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Locate
|
|
" ------------------------------------------------------------------
|
|
function! fzf#vim#locate(query, ...)
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': 'locate '.a:query,
|
|
\ 'options': '-m --prompt "Locate> "'
|
|
\}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" History[:/]
|
|
" ------------------------------------------------------------------
|
|
function! s:all_files()
|
|
return extend(
|
|
\ filter(reverse(copy(v:oldfiles)),
|
|
\ "v:val !~ 'fugitive:\\|__Tagbar__\\|NERD_tree\\|^/tmp/\\|\\.git/\\|term://'"),
|
|
\ filter(map(s:buflisted(), 'bufname(v:val)'), '!empty(v:val)'))
|
|
endfunction
|
|
|
|
function! s:history_source(type)
|
|
let max = histnr(a:type)
|
|
let fmt = ' %'.len(string(max)).'d '
|
|
let list = filter(map(range(1, max), 'histget(a:type, - v:val)'), '!empty(v:val)')
|
|
return extend([' :: Press '.s:magenta('CTRL-E', 'Special').' to edit'],
|
|
\ map(list, 's:yellow(printf(fmt, len(list) - v:key), "Number")." ".v:val'))
|
|
endfunction
|
|
|
|
nnoremap <plug>(-fzf-vim-do) :execute g:__fzf_command<cr>
|
|
|
|
function! s:history_sink(type, lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
|
|
let key = a:lines[0]
|
|
let item = matchstr(a:lines[1], ' *[0-9]\+ *\zs.*')
|
|
if key == 'ctrl-e'
|
|
call histadd(a:type, item)
|
|
redraw
|
|
call feedkeys(a:type."\<up>")
|
|
else
|
|
let g:__fzf_command = "normal ".a:type.item."\<cr>"
|
|
call feedkeys("\<plug>(-fzf-vim-do)")
|
|
endif
|
|
endfunction
|
|
|
|
function! s:cmd_history_sink(lines)
|
|
call s:history_sink(':', a:lines)
|
|
endfunction
|
|
|
|
function! fzf#vim#command_history(...)
|
|
return s:fzf({
|
|
\ 'source': s:history_source(':'),
|
|
\ 'sink*': s:function('s:cmd_history_sink'),
|
|
\ 'options': '+m --ansi --prompt="Hist:> " --header-lines=1 --expect=ctrl-e --tiebreak=index'}, a:000)
|
|
endfunction
|
|
|
|
function! s:search_history_sink(lines)
|
|
call s:history_sink('/', a:lines)
|
|
endfunction
|
|
|
|
function! fzf#vim#search_history(...)
|
|
return s:fzf({
|
|
\ 'source': s:history_source('/'),
|
|
\ 'sink*': s:function('s:search_history_sink'),
|
|
\ 'options': '+m --ansi --prompt="Hist/> " --header-lines=1 --expect=ctrl-e --tiebreak=index'}, a:000)
|
|
endfunction
|
|
|
|
function! fzf#vim#history(...)
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': reverse(s:all_files()),
|
|
\ 'options': '-m --prompt "Hist> "'
|
|
\}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" GitFiles[?]
|
|
" ------------------------------------------------------------------
|
|
|
|
function! s:git_status_sink(lines) abort
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
let lines = extend(a:lines[0:0], map(a:lines[1:], 'v:val[3:]'))
|
|
return s:common_sink(lines)
|
|
endfunction
|
|
|
|
function! fzf#vim#gitfiles(args, ...)
|
|
let root = split(system('git rev-parse --show-toplevel'), '\n')[0]
|
|
if v:shell_error
|
|
return s:warn('Not in git repo')
|
|
endif
|
|
if a:args != '?'
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': 'git ls-files '.a:args,
|
|
\ 'dir': root,
|
|
\ 'options': '-m --prompt "GitFiles> "'
|
|
\}), a:000)
|
|
endif
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': 'git -c color.status=always status --short',
|
|
\ 'dir': root,
|
|
\ 'sink*': s:function('s:git_status_sink'),
|
|
\ 'options': '--ansi --multi --nth 2..,.. --prompt "GitFiles?> "'
|
|
\}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Buffers
|
|
" ------------------------------------------------------------------
|
|
function! s:find_open_window(b)
|
|
let [tcur, tcnt] = [tabpagenr() - 1, tabpagenr('$')]
|
|
for toff in range(0, tabpagenr('$') - 1)
|
|
let t = (tcur + toff) % tcnt + 1
|
|
let buffers = tabpagebuflist(t)
|
|
for w in range(1, len(buffers))
|
|
let b = buffers[w - 1]
|
|
if b == a:b
|
|
return [t, w]
|
|
endif
|
|
endfor
|
|
endfor
|
|
return [0, 0]
|
|
endfunction
|
|
|
|
function! s:jump(t, w)
|
|
execute 'normal!' a:t.'gt'
|
|
execute a:w.'wincmd w'
|
|
endfunction
|
|
|
|
function! s:bufopen(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
let b = matchstr(a:lines[1], '\[\zs[0-9]*\ze\]')
|
|
if empty(a:lines[0]) && get(g:, 'fzf_buffers_jump')
|
|
let [t, w] = s:find_open_window(b)
|
|
if t
|
|
call s:jump(t, w)
|
|
return
|
|
endif
|
|
endif
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], '')
|
|
if !empty(cmd)
|
|
execute 'silent' cmd
|
|
endif
|
|
execute 'buffer' b
|
|
endfunction
|
|
|
|
function! s:format_buffer(b)
|
|
let name = bufname(a:b)
|
|
let name = empty(name) ? '[No Name]' : name
|
|
let flag = a:b == bufnr('') ? s:blue('%', 'Conditional') :
|
|
\ (a:b == bufnr('#') ? s:magenta('#', 'Special') : ' ')
|
|
let modified = getbufvar(a:b, '&modified') ? s:red(' [+]', 'Exception') : ''
|
|
let readonly = getbufvar(a:b, '&modifiable') ? '' : s:green(' [RO]', 'Constant')
|
|
let extra = join(filter([modified, readonly], '!empty(v:val)'), '')
|
|
return s:strip(printf("[%s] %s\t%s\t%s", s:yellow(a:b, 'Number'), flag, name, extra))
|
|
endfunction
|
|
|
|
function! s:sort_buffers(...)
|
|
let [b1, b2] = map(copy(a:000), 'get(g:fzf#vim#buffers, v:val, v:val)')
|
|
" Using minus between a float and a number in a sort function causes an error
|
|
return b1 > b2 ? 1 : -1
|
|
endfunction
|
|
|
|
function! fzf#vim#buffers(...)
|
|
let bufs = map(sort(s:buflisted(), 's:sort_buffers'), 's:format_buffer(v:val)')
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': reverse(bufs),
|
|
\ 'sink*': s:function('s:bufopen'),
|
|
\ 'options': '+m -x --tiebreak=index --ansi -d "\t" -n 2,1..2 --prompt="Buf> "',
|
|
\}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Ag
|
|
" ------------------------------------------------------------------
|
|
function! s:ag_to_qf(line)
|
|
let parts = split(a:line, ':')
|
|
return {'filename': &acd ? fnamemodify(parts[0], ':p') : parts[0], 'lnum': parts[1], 'col': parts[2],
|
|
\ 'text': join(parts[3:], ':')}
|
|
endfunction
|
|
|
|
function! s:ag_handler(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], 'e')
|
|
let list = map(a:lines[1:], 's:ag_to_qf(v:val)')
|
|
|
|
let first = list[0]
|
|
try
|
|
call s:open(cmd, first.filename)
|
|
execute first.lnum
|
|
execute 'normal!' first.col.'|zz'
|
|
catch
|
|
endtry
|
|
|
|
if len(list) > 1
|
|
call setqflist(list)
|
|
copen
|
|
wincmd p
|
|
endif
|
|
endfunction
|
|
|
|
" query, [[ag options], options]
|
|
function! fzf#vim#ag(query, ...)
|
|
let query = escape(empty(a:query) ? '^(?=.)' : a:query, '"\-')
|
|
let args = copy(a:000)
|
|
let ag_opts = len(args) > 1 ? remove(args, 0) : ''
|
|
let command = printf('%s "%s"', ag_opts, query)
|
|
return call('fzf#vim#ag_raw', insert(args, command, 0))
|
|
endfunction
|
|
|
|
" ag command suffix, [options]
|
|
function! fzf#vim#ag_raw(command_suffix, ...)
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': 'ag --nogroup --column --color '.a:command_suffix,
|
|
\ 'sink*': s:function('s:ag_handler'),
|
|
\ 'options': '--ansi --delimiter : --nth 4..,.. --prompt "Ag> " '.
|
|
\ '--multi --bind alt-a:select-all,alt-d:deselect-all '.
|
|
\ '--color hl:68,hl+:110'}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" BTags
|
|
" ------------------------------------------------------------------
|
|
function! s:btags_source(tag_cmds)
|
|
if !filereadable(expand('%'))
|
|
throw 'Save the file first'
|
|
endif
|
|
|
|
for cmd in a:tag_cmds
|
|
let lines = split(system(cmd), "\n")
|
|
if !v:shell_error
|
|
break
|
|
endif
|
|
endfor
|
|
if v:shell_error
|
|
throw get(lines, 0, 'Failed to extract tags')
|
|
elseif empty(lines)
|
|
throw 'No tags found'
|
|
endif
|
|
return map(s:align_lists(map(lines, 'split(v:val, "\t")')), 'join(v:val, "\t")')
|
|
endfunction
|
|
|
|
function! s:btags_sink(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
normal! m'
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], '')
|
|
if !empty(cmd)
|
|
execute 'silent' cmd '%'
|
|
endif
|
|
let qfl = []
|
|
for line in a:lines[1:]
|
|
execute split(line, "\t")[2]
|
|
call add(qfl, {'filename': expand('%'), 'lnum': line('.'), 'text': getline('.')})
|
|
endfor
|
|
if len(qfl) > 1
|
|
call setqflist(qfl)
|
|
copen
|
|
wincmd p
|
|
cfirst
|
|
endif
|
|
normal! zz
|
|
endfunction
|
|
|
|
function! s:q(query)
|
|
return ' --query "'.escape(a:query, '"\').'"'
|
|
endfunction
|
|
|
|
" query, [[tag commands], options]
|
|
function! fzf#vim#buffer_tags(query, ...)
|
|
let args = copy(a:000)
|
|
let tag_cmds = len(args) > 1 ? remove(args, 0) : [
|
|
\ printf('ctags -f - --sort=no --excmd=number --language-force=%s %s', &filetype, expand('%:S')),
|
|
\ printf('ctags -f - --sort=no --excmd=number %s', expand('%:S'))]
|
|
try
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': s:btags_source(tag_cmds),
|
|
\ 'sink*': s:function('s:btags_sink'),
|
|
\ 'options': '--reverse -m -d "\t" --with-nth 1,4.. -n 1 --prompt "BTags> "'.s:q(a:query)}), args)
|
|
catch
|
|
return s:warn(v:exception)
|
|
endtry
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Tags
|
|
" ------------------------------------------------------------------
|
|
function! s:tags_sink(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
normal! m'
|
|
let qfl = []
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], 'e')
|
|
let [magic, &magic, wrapscan, &wrapscan] = [&magic, 0, &wrapscan, 1]
|
|
for line in a:lines[1:]
|
|
let parts = split(line, '\t\zs')
|
|
let excmd = matchstr(join(parts[2:], ''), '^.*\ze;"\t')
|
|
call s:open(cmd, parts[1][:-2])
|
|
execute excmd
|
|
call add(qfl, {'filename': expand('%'), 'lnum': line('.'), 'text': getline('.')})
|
|
endfor
|
|
let [&magic, &wrapscan] = [magic, wrapscan]
|
|
if len(qfl) > 1
|
|
call setqflist(qfl)
|
|
copen
|
|
wincmd p
|
|
clast
|
|
endif
|
|
normal! zz
|
|
endfunction
|
|
|
|
function! fzf#vim#tags(query, ...)
|
|
if empty(tagfiles())
|
|
call inputsave()
|
|
echohl WarningMsg
|
|
let gen = input('tags not found. Generate? (y/N) ')
|
|
echohl None
|
|
call inputrestore()
|
|
redraw
|
|
if gen =~ '^y'
|
|
call s:warn('Preparing tags')
|
|
call system(get(g:, 'fzf_tags_command', 'ctags -R'))
|
|
if empty(tagfiles())
|
|
return s:warn('Failed to create tags')
|
|
endif
|
|
else
|
|
return s:warn('No tags found')
|
|
endif
|
|
endif
|
|
|
|
let tagfile = tagfiles()[0]
|
|
" We don't want to apply --ansi option when tags file is large as it makes
|
|
" processing much slower.
|
|
if getfsize(tagfile) > 1024 * 1024 * 20
|
|
let proc = 'grep -v ''^\!'' '
|
|
let copt = ''
|
|
else
|
|
let proc = 'perl -ne ''unless (/^\!/) { s/^(.*?)\t(.*?)\t/'.s:yellow('\1', 'Function').'\t'.s:blue('\2', 'String').'\t/; print }'' '
|
|
let copt = '--ansi '
|
|
endif
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': proc.shellescape(fnamemodify(tagfile, ':t')),
|
|
\ 'sink*': s:function('s:tags_sink'),
|
|
\ 'dir': fnamemodify(tagfile, ':h'),
|
|
\ 'options': copt.'-m --tiebreak=begin --prompt "Tags> "'.s:q(a:query)}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Snippets (UltiSnips)
|
|
" ------------------------------------------------------------------
|
|
function! s:inject_snippet(line)
|
|
let snip = split(a:line, "\t")[0]
|
|
execute 'normal! a'.s:strip(snip)."\<c-r>=UltiSnips#ExpandSnippet()\<cr>"
|
|
endfunction
|
|
|
|
function! fzf#vim#snippets(...)
|
|
if !exists(':UltiSnipsEdit')
|
|
return s:warn('UltiSnips not found')
|
|
endif
|
|
let list = UltiSnips#SnippetsInCurrentScope()
|
|
if empty(list)
|
|
return s:warn('No snippets available here')
|
|
endif
|
|
let aligned = sort(s:align_lists(items(list)))
|
|
let colored = map(aligned, 's:yellow(v:val[0])."\t".v:val[1]')
|
|
return s:fzf({
|
|
\ 'source': colored,
|
|
\ 'options': '--ansi --tiebreak=index +m -n 1 -d "\t"',
|
|
\ 'sink': s:function('s:inject_snippet')}, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Commands
|
|
" ------------------------------------------------------------------
|
|
let s:nbs = nr2char(0x2007)
|
|
|
|
function! s:format_cmd(line)
|
|
return substitute(a:line, '\C \([A-Z]\S*\) ',
|
|
\ '\=s:nbs.s:yellow(submatch(1), "Function").s:nbs', '')
|
|
endfunction
|
|
|
|
function! s:command_sink(cmd)
|
|
let cmd = matchstr(a:cmd, s:nbs.'\zs\S*\ze'.s:nbs)
|
|
call feedkeys(':'.cmd.(a:cmd[0] == '!' ? '' : ' '))
|
|
endfunction
|
|
|
|
function! s:format_excmd(ex)
|
|
let match = matchlist(a:ex, '^|:\(\S\+\)|\s*\S*\(.*\)')
|
|
return printf(' '.s:blue('%-38s', 'Statement').'%s', s:nbs.match[1].s:nbs, s:strip(match[2]))
|
|
endfunction
|
|
|
|
function! s:excmds()
|
|
let help = globpath($VIMRUNTIME, 'doc/index.txt')
|
|
if empty(help)
|
|
return []
|
|
endif
|
|
|
|
let commands = []
|
|
let command = ''
|
|
for line in readfile(help)
|
|
if line =~ '^|:[^|]'
|
|
if !empty(command)
|
|
call add(commands, s:format_excmd(command))
|
|
endif
|
|
let command = line
|
|
elseif line =~ '^\s\+\S' && !empty(command)
|
|
let command .= substitute(line, '^\s*', ' ', '')
|
|
elseif !empty(commands) && line =~ '^\s*$'
|
|
break
|
|
endif
|
|
endfor
|
|
if !empty(command)
|
|
call add(commands, s:format_excmd(command))
|
|
endif
|
|
return commands
|
|
endfunction
|
|
|
|
function! fzf#vim#commands(...)
|
|
redir => cout
|
|
silent command
|
|
redir END
|
|
let list = split(cout, "\n")
|
|
return s:fzf({
|
|
\ 'source': extend(extend(list[0:0], map(list[1:], 's:format_cmd(v:val)')), s:excmds()),
|
|
\ 'sink': s:function('s:command_sink'),
|
|
\ 'options': '--ansi --tiebreak=index --header-lines 1 -x --prompt "Commands> " -n2,3,2..3 -d'.s:nbs}, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Marks
|
|
" ------------------------------------------------------------------
|
|
function! s:format_mark(line)
|
|
return substitute(a:line, '\S', '\=s:yellow(submatch(0))', '')
|
|
endfunction
|
|
|
|
function! s:mark_sink(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
let cmd = get(get(g:, 'fzf_action', s:default_action), a:lines[0], '')
|
|
if !empty(cmd)
|
|
execute 'silent' cmd
|
|
endif
|
|
execute 'normal! `'.matchstr(a:lines[1], '\S').'zz'
|
|
endfunction
|
|
|
|
function! fzf#vim#marks(...)
|
|
redir => cout
|
|
silent marks
|
|
redir END
|
|
let list = split(cout, "\n")
|
|
return s:fzf(fzf#vim#wrap({
|
|
\ 'source': extend(list[0:0], map(list[1:], 's:format_mark(v:val)')),
|
|
\ 'sink*': s:function('s:mark_sink'),
|
|
\ 'options': '+m -x --ansi --tiebreak=index --header-lines 1 --tiebreak=begin --prompt "Marks> "'}), a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Help tags
|
|
" ------------------------------------------------------------------
|
|
function! s:helptag_sink(line)
|
|
let [tag, file, path] = split(a:line, "\t")[0:2]
|
|
let rtp = fnamemodify(path, ':p:h:h')
|
|
if stridx(&rtp, rtp) < 0
|
|
execute 'set rtp+='.s:escape(rtp)
|
|
endif
|
|
execute 'help' tag
|
|
endfunction
|
|
|
|
function! fzf#vim#helptags(...)
|
|
let tags = uniq(sort(split(globpath(&runtimepath, '**/doc/tags'), '\n')))
|
|
|
|
return s:fzf({
|
|
\ 'source': "grep -H '.*' ".join(map(tags, 'shellescape(v:val)')).
|
|
\ "| perl -ne '/(.*?):(.*?)\t(.*?)\t/; printf(qq(".s:green('%-40s', 'Label')."\t%s\t%s\n), $2, $3, $1)' | sort",
|
|
\ 'sink': s:function('s:helptag_sink'),
|
|
\ 'options': '--ansi +m --tiebreak=begin --with-nth ..-2'}, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" File types
|
|
" ------------------------------------------------------------------
|
|
function! fzf#vim#filetypes(...)
|
|
return s:fzf({
|
|
\ 'source': sort(map(split(globpath(&rtp, 'syntax/*.vim'), '\n'),
|
|
\ 'fnamemodify(v:val, ":t:r")')),
|
|
\ 'sink': 'setf',
|
|
\ 'options': '+m --prompt="File types> "'
|
|
\}, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Windows
|
|
" ------------------------------------------------------------------
|
|
function! s:format_win(tab, win, buf)
|
|
let modified = getbufvar(a:buf, '&modified')
|
|
let name = bufname(a:buf)
|
|
let name = empty(name) ? '[No Name]' : name
|
|
let active = tabpagewinnr(a:tab) == a:win
|
|
return (active? s:blue('> ', 'Operator') : ' ') . name . (modified? s:red(' [+]', 'Exception') : '')
|
|
endfunction
|
|
|
|
function! s:windows_sink(line)
|
|
let list = matchlist(a:line, '^ *\([0-9]\+\) *\([0-9]\+\)')
|
|
call s:jump(list[1], list[2])
|
|
endfunction
|
|
|
|
function! fzf#vim#windows(...)
|
|
let lines = []
|
|
for t in range(1, tabpagenr('$'))
|
|
let buffers = tabpagebuflist(t)
|
|
for w in range(1, len(buffers))
|
|
call add(lines,
|
|
\ printf('%s %s %s',
|
|
\ s:yellow(printf('%3d', t), 'Number'),
|
|
\ s:cyan(printf('%3d', w), 'String'),
|
|
\ s:format_win(t, w, buffers[w-1])))
|
|
endfor
|
|
endfor
|
|
return s:fzf({
|
|
\ 'source': extend(['Tab Win Name'], lines),
|
|
\ 'sink': s:function('s:windows_sink'),
|
|
\ 'options': '+m --ansi --tiebreak=begin --header-lines=1'}, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" Commits / BCommits
|
|
" ------------------------------------------------------------------
|
|
function! s:commits_sink(lines)
|
|
if len(a:lines) < 2
|
|
return
|
|
endif
|
|
|
|
let cmd = get(extend({'ctrl-d': ''}, get(g:, 'fzf_action', s:default_action)), a:lines[0], 'e')
|
|
let buf = bufnr('')
|
|
for idx in range(1, len(a:lines) - 1)
|
|
let sha = matchstr(a:lines[idx], '[0-9a-f]\{7}')
|
|
if !empty(sha)
|
|
if empty(cmd)
|
|
if idx > 1
|
|
execute 'tab sb' buf
|
|
endif
|
|
execute 'Gdiff' sha
|
|
else
|
|
" Since fugitive buffers are unlisted, we can't keep using 'e'
|
|
let c = (cmd == 'e' && idx > 1) ? 'tab split' : cmd
|
|
execute c 'fugitive://'.s:git_root.'/.git//'.sha
|
|
endif
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
function! s:commits(buffer_local, args)
|
|
let s:git_root = s:chomp(system('git rev-parse --show-toplevel'))
|
|
if v:shell_error
|
|
return s:warn('Not in git repository')
|
|
endif
|
|
|
|
let source = 'git log '.get(g:, 'fzf_commits_log_options', '--graph --color=always --format="%C(auto)%h%d %s %C(black)%C(bold)%cr"')
|
|
let current = expand('%:S')
|
|
let managed = 0
|
|
if !empty(current)
|
|
call system('git show '.current.' 2> /dev/null')
|
|
let managed = !v:shell_error
|
|
endif
|
|
|
|
if a:buffer_local
|
|
if !managed
|
|
return s:warn('The current buffer is not in the working tree')
|
|
endif
|
|
let source .= ' --follow '.current
|
|
endif
|
|
|
|
let command = a:buffer_local ? 'BCommits' : 'Commits'
|
|
let options = fzf#vim#wrap({
|
|
\ 'source': source,
|
|
\ 'sink*': s:function('s:commits_sink'),
|
|
\ 'options': '--ansi --multi --no-sort --tiebreak=index --reverse '.
|
|
\ '--inline-info --prompt "'.command.'> " --bind=ctrl-s:toggle-sort'
|
|
\ })
|
|
|
|
if a:buffer_local
|
|
let options.options .= ',ctrl-d --header ":: Press '.s:magenta('CTRL-S', 'Special').' to toggle sort, '.s:magenta('CTRL-D', 'Special').' to diff"'
|
|
else
|
|
let options.options .= ' --header ":: Press '.s:magenta('CTRL-S', 'Special').' to toggle sort"'
|
|
endif
|
|
|
|
return s:fzf(options, a:args)
|
|
endfunction
|
|
|
|
function! fzf#vim#commits(...)
|
|
return s:commits(0, a:000)
|
|
endfunction
|
|
|
|
function! fzf#vim#buffer_commits(...)
|
|
return s:commits(1, a:000)
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
" fzf#vim#maps(mode, opts[with count and op])
|
|
" ------------------------------------------------------------------
|
|
function! s:align_pairs(list)
|
|
let maxlen = 0
|
|
let pairs = []
|
|
for elem in a:list
|
|
let match = matchlist(elem, '^\(\S*\)\s*\(.*\)$')
|
|
let [_, k, v] = match[0:2]
|
|
let maxlen = max([maxlen, len(k)])
|
|
call add(pairs, [k, substitute(v, '^\*\?[@ ]\?', '', '')])
|
|
endfor
|
|
let maxlen = min([maxlen, 35])
|
|
return map(pairs, "printf('%-'.maxlen.'s', v:val[0]).' '.v:val[1]")
|
|
endfunction
|
|
|
|
function! s:highlight_keys(str)
|
|
return substitute(
|
|
\ substitute(a:str, '<[^ >]\+>', s:yellow('\0', 'Special'), 'g'),
|
|
\ '<Plug>', s:blue('<Plug>', 'SpecialKey'), 'g')
|
|
endfunction
|
|
|
|
function! s:key_sink(line)
|
|
let key = matchstr(a:line, '^\S*')
|
|
redraw
|
|
call feedkeys(s:map_gv.s:map_cnt.s:map_reg, 'n')
|
|
call feedkeys(s:map_op.
|
|
\ substitute(key, '<[^ >]\+>', '\=eval("\"\\".submatch(0)."\"")', 'g'))
|
|
endfunction
|
|
|
|
function! fzf#vim#maps(mode, ...)
|
|
let s:map_gv = a:mode == 'x' ? 'gv' : ''
|
|
let s:map_cnt = v:count == 0 ? '' : v:count
|
|
let s:map_reg = empty(v:register) ? '' : ('"'.v:register)
|
|
let s:map_op = a:mode == 'o' ? v:operator : ''
|
|
|
|
redir => cout
|
|
silent execute 'verbose' a:mode.'map'
|
|
redir END
|
|
let list = []
|
|
let curr = ''
|
|
for line in split(cout, "\n")
|
|
let src = matchstr(line, 'Last set from \zs.*')
|
|
if empty(src)
|
|
let curr = line[3:]
|
|
else
|
|
let src = ' '.join(reverse(reverse(split(src, '/'))[0:2]), '/')
|
|
call add(list, printf('%s %s', curr, s:black(src, 'Comment')))
|
|
let curr = ''
|
|
endif
|
|
endfor
|
|
if !empty(curr)
|
|
call add(list, curr)
|
|
endif
|
|
let aligned = s:align_pairs(list)
|
|
let sorted = sort(aligned)
|
|
let colored = map(sorted, 's:highlight_keys(v:val)')
|
|
let pcolor = a:mode == 'x' ? 9 : a:mode == 'o' ? 10 : 12
|
|
return s:fzf({
|
|
\ 'source': colored,
|
|
\ 'sink': s:function('s:key_sink'),
|
|
\ 'options': '--prompt "Maps ('.a:mode.')> " --ansi --no-hscroll --nth 1,.. --color prompt:'.pcolor}, a:000)
|
|
endfunction
|
|
|
|
" ----------------------------------------------------------------------------
|
|
" fzf#vim#complete - completion helper
|
|
" ----------------------------------------------------------------------------
|
|
inoremap <silent> <Plug>(-fzf-complete-trigger) <c-o>:call <sid>complete_trigger()<cr>
|
|
|
|
function! s:pluck(dict, key, default)
|
|
return has_key(a:dict, a:key) ? remove(a:dict, a:key) : a:default
|
|
endfunction
|
|
|
|
function! s:complete_trigger()
|
|
let opts = copy(s:opts)
|
|
let opts.options = printf('+m -q %s %s', shellescape(s:query), get(opts, 'options', ''))
|
|
let opts['sink*'] = s:function('s:complete_insert')
|
|
let s:reducer = s:pluck(opts, 'reducer', s:function('s:first_line'))
|
|
call fzf#run(opts)
|
|
endfunction
|
|
|
|
" The default reducer
|
|
function! s:first_line(lines)
|
|
return a:lines[0]
|
|
endfunction
|
|
|
|
function! s:complete_insert(lines)
|
|
if empty(a:lines)
|
|
return
|
|
endif
|
|
|
|
let chars = strchars(s:query)
|
|
if chars == 0 | let del = ''
|
|
elseif chars == 1 | let del = '"_x'
|
|
else | let del = (chars - 1).'"_dvh'
|
|
endif
|
|
|
|
let data = call(s:reducer, [a:lines])
|
|
execute 'normal!' ((s:eol || empty(chars)) ? '' : 'h').del.(s:eol ? 'a': 'i').data
|
|
if has('nvim')
|
|
call feedkeys('a')
|
|
else
|
|
execute "normal! \<esc>la"
|
|
endif
|
|
endfunction
|
|
|
|
let s:TYPE = {'dict': type({}), 'funcref': type(function('call'))}
|
|
|
|
function! s:eval(dict, key, arg)
|
|
if has_key(a:dict, a:key) && type(a:dict[a:key]) == s:TYPE.funcref
|
|
let ret = copy(a:dict)
|
|
let ret[a:key] = call(a:dict[a:key], [a:arg])
|
|
return ret
|
|
endif
|
|
return a:dict
|
|
endfunction
|
|
|
|
function! fzf#vim#complete(...)
|
|
if a:0 == 0
|
|
let s:opts = g:fzf#vim#default_layout
|
|
elseif type(a:1) == s:TYPE.dict
|
|
if has_key(a:1, 'sink') || has_key(a:1, 'sink*')
|
|
echoerr 'sink not allowed'
|
|
return ''
|
|
endif
|
|
let s:opts = copy(a:1)
|
|
else
|
|
let s:opts = extend({'source': a:1}, g:fzf#vim#default_layout)
|
|
endif
|
|
|
|
let eol = col('$')
|
|
let ve = &ve
|
|
set ve=all
|
|
let s:eol = col('.') == eol
|
|
let &ve = ve
|
|
|
|
let Prefix = s:pluck(s:opts, 'prefix', '\k*$')
|
|
if col('.') == 1
|
|
let s:query = ''
|
|
else
|
|
let full_prefix = getline('.')[0 : col('.')-2]
|
|
if type(Prefix) == s:TYPE.funcref
|
|
let s:query = call(Prefix, [full_prefix])
|
|
else
|
|
let s:query = matchstr(full_prefix, Prefix)
|
|
endif
|
|
endif
|
|
let s:opts = s:eval(s:opts, 'source', s:query)
|
|
let s:opts = s:eval(s:opts, 'options', s:query)
|
|
let s:opts = s:eval(s:opts, 'extra_options', s:query)
|
|
if has_key(s:opts, 'extra_options')
|
|
let s:opts.options =
|
|
\ join(filter([get(s:opts, 'options', ''), remove(s:opts, 'extra_options')], '!empty(v:val)'))
|
|
endif
|
|
|
|
call feedkeys("\<Plug>(-fzf-complete-trigger)")
|
|
return ''
|
|
endfunction
|
|
|
|
" ------------------------------------------------------------------
|
|
let &cpo = s:cpo_save
|
|
unlet s:cpo_save
|
|
|