Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Makes Vim integration asynchronous and adds native popup support for tmux and Zellij.
Changes:
- Runs terminal and popup workflows asynchronously with callbacks.
- Adds
popupas atmuxlayout synonym. - Updates tests, documentation, and changelog.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
plugin/fzf.vim |
Implements asynchronous execution and popup support. |
test/vim/fzf.vader |
Adapts tests for asynchronous completion. |
README-VIM.md |
Documents popup layouts. |
doc/fzf.txt |
Updates Vim help for popup layouts. |
CHANGELOG.md |
Records asynchronous and popup behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Popup mode held the fzf process with system(), which froze Vim until fzf exited. fzf in a popup draws in a pane of its own, so that process only waits for it and does not need a window. Hold it with a job instead and Vim keeps processing its event loop, which is what a live preview needs. Nothing is displayed for the job. Falls back to the blocking path when the job cannot start, so the sink still runs and temp files are removed. - job_start() sets $TERM=dumb and the popup inherits the environment, so fzf dropped to its 16-color scheme. Restore it via 'env', or in the command itself before 8.0.902, when 'env' was added - Fullscreen now uses a terminal buffer in a new tab on Vim too. use_term lacked parentheses, so && bound tighter than || and the layout test was dead on Neovim, which already behaved this way - fzf#run returns an empty list in these modes. Callers use sink, sinklist or exit, and the vader specs now wait for completion - Append --no-tmux only when the spec asks for a Vim window, so --popup in $FZF_DEFAULT_OPTS survives a spec with no layout option Accept popup as a synonym of the tmux layout key, matching --popup being the name of --tmux. popup wins when both are given. s:tmux_enabled(): - Accept $ZELLIJ, which --popup covers as well - Parse tmux -V with matchstr and compare with s:compare_versions. The old string comparison against 'tmux 1.7' misreads 10.0 - Drop the fzf-tmux requirement on tmux 3.3 or above, where --tmux needs no script. Removing the script silently disabled popups entirely - Resolve the script where it is used, and anchor the legacy test to ^- so a --tmux value containing a dash, as in 90%,60%,border-native, is not mistaken for a legacy flag
Callers cannot detect plugin-side behavior. fzf#exec() reports the version of the binary, not of this plugin, and everything else here is script-local. fzf.vim needs to know whether fzf#run is asynchronous in popup mode before offering a key binding that depends on it. The value was only ever read through the exists() guard, so raising it from 1 breaks nothing.
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The core asynchronous popup path lacks focused coverage for completion, cancellation, and startup failure.
Review details
Suppressed comments (1)
plugin/fzf.vim:795
- The new popup job path is not exercised by the updated tests: the existing
g:FzfRuncases take the terminal-buffer path, while the new popup test only callsfzf#wrap. This leaves job startup/fallback and exit-to-sink delivery unverified on both Vim and Neovim. Please add focused coverage for the asynchronous popup path, including successful selection and cancellation or startup failure.
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
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Balanced
hmr
pushed a commit
to hmr/fzf-hmr
that referenced
this pull request
Aug 25, 2026
* Run fzf asynchronously in the Vim plugin Popup mode held the fzf process with system(), which froze Vim until fzf exited. fzf in a popup draws in a pane of its own, so that process only waits for it and does not need a window. Hold it with a job instead and Vim keeps processing its event loop, which is what a live preview needs. Nothing is displayed for the job. Falls back to the blocking path when the job cannot start, so the sink still runs and temp files are removed. - job_start() sets $TERM=dumb and the popup inherits the environment, so fzf dropped to its 16-color scheme. Restore it via 'env', or in the command itself before 8.0.902, when 'env' was added - Fullscreen now uses a terminal buffer in a new tab on Vim too. use_term lacked parentheses, so && bound tighter than || and the layout test was dead on Neovim, which already behaved this way - fzf#run returns an empty list in these modes. Callers use sink, sinklist or exit, and the vader specs now wait for completion - Append --no-tmux only when the spec asks for a Vim window, so --popup in $FZF_DEFAULT_OPTS survives a spec with no layout option Accept popup as a synonym of the tmux layout key, matching --popup being the name of --tmux. popup wins when both are given. s:tmux_enabled(): - Accept $ZELLIJ, which --popup covers as well - Parse tmux -V with matchstr and compare with s:compare_versions. The old string comparison against 'tmux 1.7' misreads 10.0 - Drop the fzf-tmux requirement on tmux 3.3 or above, where --tmux needs no script. Removing the script silently disabled popups entirely - Resolve the script where it is used, and anchor the legacy test to ^- so a --tmux value containing a dash, as in 90%,60%,border-native, is not mistaken for a legacy flag * Turn g:loaded_fzf into a version marker Callers cannot detect plugin-side behavior. fzf#exec() reports the version of the binary, not of this plugin, and everything else here is script-local. fzf.vim needs to know whether fzf#run is asynchronous in popup mode before offering a key binding that depends on it. The value was only ever read through the exists() guard, so raising it from 1 breaks nothing.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
Files, GFiles, GFiles?, Rg/Ag, Buffers, History, Locate, Lines, BLines, Tags, BTags, Marks and Changes. 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.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
Files, GFiles, GFiles?, Rg/Ag, Buffers, History, Locate, Lines, BLines, Tags, BTags, Marks and Changes. 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.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
Files, GFiles, GFiles?, Rg/Ag, Buffers, History, Locate, Lines, BLines, Tags, BTags, Marks and Changes. 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.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
Files, GFiles, GFiles?, Rg/Ag, Buffers, History, Locate, Lines, BLines, Tags, BTags, Marks and Changes. 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.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
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.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
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.
junegunn
added a commit
to junegunn/fzf.vim
that referenced
this pull request
Aug 31, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contribution Policy
We do not accept pull requests generated primarily by AI without genuine understanding or real-world usage context.
All contributions are expected to demonstrate:
If these expectations are not met, we would prefer to implement the changes ourselves rather than spend time reviewing low-effort submissions.
Acknowledgement