mirror of
https://github.com/junegunn/fzf.vim.git
synced 2026-09-07 23:41:19 +08:00
Files, GFiles, GFiles?, Rg/Ag, Buffers, History, Locate, Lines, BLines, Tags, BTags, Marks, Changes, Commits and BCommits. Writes the entry to an fzf#vim#ipc fifo, so it needs the asynchronous fzf#run from junegunn/fzf@5cb7bab7 (junegunn/fzf#4897, 0.74.4), which the g:loaded_fzf check detects. Commands declare '_show' for the entry kind and '_hint' for footer keys, so the footer and the binding cannot disagree. s:can_show withholds both when the run would block, when the window to open in is not visible, or when g:fzf_action, the paste key or the command itself already claims the key. g:fzf_vim.show_key configures the key, empty to turn it off, comma-separated to bind several ('ctrl-o,double-click'). Named for its 'Show' footer label, since Enter already occupies 'Open' there. Only the first key is labelled, and a clash on any of them drops all of them, so the footer cannot list a key that does nothing. Binding Enter drops its 'Open' hint too, since fzf accepts on Enter rather than through g:fzf_action. s:show_entry parses each entry the way that command's sink does and runs win_execute() in the window fzf started from. That keeps the focus in fzf and works from a popup, which win_gotoid() cannot leave. It opens with 'keepalt keepjumps hide', so stepping through entries leaves the alternate file and the jumplist unchanged. BLines, BTags and a lowercase mark carry a position but no buffer, so the callback carries the buffer the run started on as well as the window. fzf#run is asynchronous, so runs can overlap, and a second run would stop the first's ipc channel and overwrite the script locals its sink reads back afterwards. Channels are now keyed by fifo path and per-run state is bound into the callback instead, which is why Buffers, Colors, Jumps, Maps, Helptags and complete change here.
137 lines
4.1 KiB
VimL
137 lines
4.1 KiB
VimL
" Copyright (c) 2024 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.
|
|
|
|
function! s:warn(message)
|
|
echohl WarningMsg
|
|
echom a:message
|
|
echohl None
|
|
return 0
|
|
endfunction
|
|
|
|
" Channels in flight, keyed by fifo path, so concurrent fzf runs do not clash
|
|
let s:channels = {}
|
|
|
|
function! fzf#vim#ipc#start(Callback)
|
|
if !exists('*job_start') && !exists('*jobstart')
|
|
call s:warn('job_start/jobstart function not supported')
|
|
return ''
|
|
endif
|
|
|
|
if !executable('mkfifo')
|
|
call s:warn('mkfifo is not available')
|
|
return ''
|
|
endif
|
|
|
|
let fifo = tempname()
|
|
call system('mkfifo '.shellescape(fifo))
|
|
if v:shell_error
|
|
if !exists('s:mkfifo_failed')
|
|
let s:mkfifo_failed = 1
|
|
call s:warn('Failed to create fifo')
|
|
endif
|
|
return ''
|
|
endif
|
|
|
|
let s:channels[fifo] = { 'fifo': fifo, 'callback': a:Callback }
|
|
call fzf#vim#ipc#restart(fifo)
|
|
if !s:running(s:channels[fifo].job)
|
|
call fzf#vim#ipc#stop(fifo)
|
|
return ''
|
|
endif
|
|
|
|
return fifo
|
|
endfunction
|
|
|
|
" Nvim hands over everything one read produced, which can be several messages
|
|
" as well as the trailing empty element. Vim's out_cb passes one line at a
|
|
" time, so deliver line by line and both behave the same.
|
|
function! s:deliver(Callback, lines)
|
|
for line in a:lines
|
|
if !empty(line)
|
|
call call(a:Callback, [line])
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
" The reader is gone, so leaving the channel registered would block the next
|
|
" write on a fifo nothing reads
|
|
function! s:drop(fifo)
|
|
if has_key(s:channels, a:fifo)
|
|
call delete(remove(s:channels, a:fifo).fifo)
|
|
endif
|
|
endfunction
|
|
|
|
function! fzf#vim#ipc#restart(fifo)
|
|
if !has_key(s:channels, a:fifo)
|
|
throw 'fzf#vim#ipc not started'
|
|
endif
|
|
|
|
let chan = s:channels[a:fifo]
|
|
let Callback = chan.callback
|
|
let fifo = a:fifo
|
|
if exists('*job_start')
|
|
let chan.job = job_start(
|
|
\ ['cat', fifo],
|
|
\ {'out_cb': { _, msg -> has_key(s:channels, fifo) ? call(Callback, [msg]) : '' },
|
|
\ 'exit_cb': { _, status -> status == 0 && has_key(s:channels, fifo) ? fzf#vim#ipc#restart(fifo) : s:drop(fifo) }}
|
|
\ )
|
|
else
|
|
let chan.job = jobstart(
|
|
\ ['cat', fifo],
|
|
\ {'stdout_buffered': 1,
|
|
\ 'on_stdout': { j, msg, e -> has_key(s:channels, fifo) ? s:deliver(Callback, msg) : '' },
|
|
\ 'on_exit': { j, status, e -> status == 0 && has_key(s:channels, fifo) ? fzf#vim#ipc#restart(fifo) : s:drop(fifo) }}
|
|
\ )
|
|
endif
|
|
endfunction
|
|
|
|
" Whether the job handle refers to a live job. Nvim returns -1 or 0 instead of
|
|
" a handle when it cannot start one
|
|
function! s:running(job)
|
|
if exists('*job_status')
|
|
return job_status(a:job) ==# 'run'
|
|
endif
|
|
return a:job > 0
|
|
endfunction
|
|
|
|
function! fzf#vim#ipc#stop(fifo)
|
|
if !has_key(s:channels, a:fifo)
|
|
return
|
|
endif
|
|
|
|
" Drop it first, so the exit handler does not restart the job
|
|
let chan = remove(s:channels, a:fifo)
|
|
" Never throw from here, this runs as part of fzf's exit handling
|
|
try
|
|
if exists('*job_stop')
|
|
call job_stop(chan.job)
|
|
else
|
|
call jobstop(chan.job)
|
|
call jobwait([chan.job])
|
|
endif
|
|
catch
|
|
endtry
|
|
|
|
call delete(chan.fifo)
|
|
endfunction
|