Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
CHANGELOG
=========

0.74.4
------
- Vim plugin
- fzf no longer blocks the editor, so live previews keep working while fzf is open
- `fzf#run` returns an empty list when it runs fzf asynchronously. Use `sink`, `sinklist`, or `exit` to get the result
- The popup layout now works under Zellij
- Added `popup` as a synonym of the `tmux` layout key
```vim
let g:fzf_layout = { 'popup': '90%,70%' }
```

0.74.3
------
- Performance optimizations for non-ASCII input
Expand Down
14 changes: 8 additions & 6 deletions README-VIM.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ The following table summarizes the available options.
| `options` | string/list | Options to fzf |
| `dir` | string | Working directory |
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
| `tmux` | string | (Layout) `--tmux` options (e.g. `90%,70%`) |
| `popup`/`tmux` | string | (Layout) `--popup` options (e.g. `90%,70%`) |
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |

Expand Down Expand Up @@ -458,14 +458,16 @@ endif
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
```

Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
or above) by putting `--tmux` option value in `tmux` key.
Alternatively, you can make fzf open in a popup window (requires tmux 3.3 or
above, or Zellij 0.44 or above) by putting `--popup` option value in `popup`
key. `tmux` is accepted as a synonym, just as `--tmux` is an alias of
`--popup`.

```vim
" See `--tmux` option in `man fzf` for available options
" See `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
if exists('$TMUX')
let g:fzf_layout = { 'tmux': '90%,70%' }
if exists('$TMUX') || exists('$ZELLIJ')
let g:fzf_layout = { 'popup': '90%,70%' }
else
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
Expand Down
13 changes: 7 additions & 6 deletions doc/fzf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ The following table summarizes the available options.
`options` | string/list | Options to fzf
`dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`tmux` | string | (Layout) `--tmux` options (e.g. `90%,70%` )
`popup` / `tmux` | string | (Layout) `--popup` options (e.g. `90%,70%` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
---------------------------+---------------+----------------------------------------------------------------------
Expand Down Expand Up @@ -467,13 +467,14 @@ in Neovim.
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
<
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
or above) by putting `--tmux` options in `tmux` key.
Alternatively, you can make fzf open in a popup window (requires tmux 3.3 or
above, or Zellij 0.44 or above) by putting `--popup` options in `popup` key.
`tmux` is accepted as a synonym, just as `--tmux` is an alias of `--popup`.
>
" See `--tmux` option in `man fzf` for available options
" See `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
if exists('$TMUX')
let g:fzf_layout = { 'tmux': '90%,70%' }
if exists('$TMUX') || exists('$ZELLIJ')
let g:fzf_layout = { 'popup': '90%,70%' }
else
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
Expand Down
137 changes: 116 additions & 21 deletions plugin/fzf.vim
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
if exists('g:loaded_fzf')
finish
endif
let g:loaded_fzf = 1
" Bumped when the plugin gains behavior a caller may need to detect
let g:loaded_fzf = 20260821

let s:is_win = has('win32') || has('win64')
if s:is_win && &shellslash
Expand Down Expand Up @@ -127,7 +128,7 @@ function! s:fzf_tempname()
return s:fzf_call('tempname')
endfunction

let s:layout_keys = ['window', 'tmux', 'up', 'down', 'left', 'right']
let s:layout_keys = ['window', 'popup', 'tmux', 'up', 'down', 'left', 'right']
let s:fzf_go = s:base_dir.'/bin/fzf'
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'

Expand Down Expand Up @@ -255,8 +256,31 @@ function! fzf#exec(...)
return s:exec
endfunction

" Path to the fzf-tmux script, or an empty string if it is not available. Only
" the legacy options still need it. --tmux is handled by fzf itself.
function! s:fzf_tmux_script()
if !executable(s:fzf_tmux)
if !executable('fzf-tmux')
return ''
endif
let s:fzf_tmux = 'fzf-tmux'
endif
return s:fzf_tmux
endfunction

function! s:tmux_enabled()
if has('gui_running') || !exists('$TMUX')
if has('gui_running')
return 0
endif

" --tmux covers Zellij as well, where the fzf-tmux script and the tmux
" version are irrelevant, but the binary only learned it in 0.71.0
if exists('$ZELLIJ')
return exists('s:exec')
\ && s:compare_versions(s:get_version(s:exec), '0.71.0') >= 0
endif

if !exists('$TMUX')
return 0
endif

Expand All @@ -265,16 +289,21 @@ function! s:tmux_enabled()
endif

let s:tmux = 0
if !executable(s:fzf_tmux)
if executable('fzf-tmux')
let s:fzf_tmux = 'fzf-tmux'
else
return 0
endif
let output = system('tmux -V')
if v:shell_error
return s:tmux
endif
" e.g. 'tmux 3.7b', 'tmux next-3.8'
let ver = matchstr(output, '\d\+\.\d\+')

let output = system('tmux -V')
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
" --tmux requires tmux 3.3 or above, and needs no fzf-tmux script
if s:compare_versions(ver, '3.3') >= 0
let s:tmux = 1
return s:tmux
endif

" Older versions still go through the script
let s:tmux = !empty(s:fzf_tmux_script()) && s:compare_versions(ver, '1.7') >= 0
return s:tmux
endfunction

Expand Down Expand Up @@ -502,6 +531,11 @@ try
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()

let dict = exists('a:1') ? copy(a:1) : {}
" 'popup' and 'tmux' are synonyms, as --popup and --tmux are. Normalize here
" so that the rest of the function only has to know about one of them.
if has_key(dict, 'popup')
let dict.tmux = remove(dict, 'popup')
endif
let temps = { 'result': s:fzf_tempname() }
let optstr = s:evaluate_opts(get(dict, 'options', ''))
try
Expand Down Expand Up @@ -539,16 +573,19 @@ try
\ executable('tput') && filereadable('/dev/tty')
let has_vim8_term = has('terminal') && has('patch-8.0.995')
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
let use_term = has_nvim_term || has_vim8_term
\ && !s:need_cmd_window
\ && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_term = (has_nvim_term || has_vim8_term) && !s:need_cmd_window
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
if prefer_tmux && use_tmux
let use_height = 0
let use_term = 0
endif
if use_term
let optstr .= ' --no-height --no-tmux'
let optstr .= ' --no-height'
" Cancel a --popup from $FZF_DEFAULT_OPTS only when the spec asks for a Vim
" window. Without a layout option, respect the user's preference.
if s:present(dict, 'window', 'up', 'down', 'left', 'right')
let optstr .= ' --no-tmux'
endif
elseif use_height
let height = s:calc_size(&lines, dict.down, dict)
let optstr .= ' --no-tmux --height='.height
Expand All @@ -568,8 +605,12 @@ try
return s:execute_term(dict, command, temps)
endif

let lines = use_tmux ? s:execute_tmux(dict, command, temps)
\ : s:execute(dict, command, use_height, temps)
" s:execute_tmux may run fzf asynchronously, so it calls s:callback itself
if use_tmux
return s:execute_tmux(dict, command, temps)
endif

let lines = s:execute(dict, command, use_height, temps)
call s:callback(dict, lines)
return lines
finally
Expand Down Expand Up @@ -597,10 +638,15 @@ function! s:fzf_tmux(dict)
endfor
endif

" Legacy fzf-tmux options
if size =~ '-'
" Legacy fzf-tmux options are flags. A --tmux value never starts with a dash,
" but may contain one, as in '90%,60%,border-native'
if size =~ '^-'
let script = s:fzf_tmux_script()
if empty(script)
throw 'fzf-tmux not found, required for the legacy option: ' . size
endif
return printf('LINES=%d COLUMNS=%d %s %s %s --',
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
\ &lines, &columns, fzf#shellescape(script), size, (has_key(a:dict, 'source') ? '' : '-'))
end

" Using native --tmux option
Expand Down Expand Up @@ -742,6 +788,42 @@ function! s:execute(dict, command, use_height, temps) abort
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
endfunction

" Returns 0 if the job could not be started
function! s:start_popup_job(dict, command, temps) abort
let fzf = { 'dict': a:dict, 'temps': a:temps, 'command': a:command }
" Vim passes (job, status) and Nvim (id, status, event)
function! fzf.on_exit(id, code, ...) abort
redraw!
let lines = s:collect(self.temps)
if s:exit_handler(self.dict, a:code, self.command, 1) >= 2
return
endif
call s:pushd(self.dict)
call s:callback(self.dict, lines)
endfunction

if has('nvim')
return jobstart([&shell, &shellcmdflag, a:command], fzf) > 0
endif

" The command redirects to the result file, so no stream is of interest
let opts = { 'exit_cb': function(fzf.on_exit),
\ 'in_io': 'null', 'out_io': 'null', 'err_io': 'null' }

" job_start() gives the child $TERM=dumb, and the popup inherits the
" environment from here, so fzf would lose its colors. 'env' was added in
" 8.0.902, so set it in the command itself on older versions.
let command = a:command
if has('patch-8.0.902')
let opts.env = { 'TERM': $TERM }
elseif !s:is_win
let command = join(['export TERM=' . fzf#shellescape($TERM) . ';', command])
endif

let job = job_start([&shell, &shellcmdflag, command], opts)
return job_status(job) !=# 'fail'
endfunction

function! s:execute_tmux(dict, command, temps) abort
let command = a:command
let cwd = s:pushd(a:dict)
Expand All @@ -750,11 +832,24 @@ function! s:execute_tmux(dict, command, temps) abort
let command = join(['cd', fzf#shellescape(cwd), '&&', command])
endif

" fzf draws in a pane of its own, so the process we start here only waits for
" it. Hold it with a job instead of system() so that Vim keeps processing its
" event loop, and nothing has to be displayed for it. Fall back to the
" blocking path when the job cannot be started, or the sink would never run
" and the temporary files would be left behind.
if (has('nvim') || has('job')) && s:start_popup_job(a:dict, command, a:temps)
" Restore the working directory while fzf runs. on_exit pushes it again
call s:dopopd()
return []
Comment thread
junegunn marked this conversation as resolved.
endif

call system(command)
let exit_status = v:shell_error
redraw!
let lines = s:collect(a:temps)
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
let lines = s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
call s:callback(a:dict, lines)
return lines
endfunction

function! s:calc_size(max, val, dict)
Expand Down
Loading