Compare commits

..
3 Commits
Author SHA1 Message Date
Junegunn Choi 3900dd17e4 Address copilot comments 2026-08-23 21:49:52 +09:00
Junegunn Choi 5e73c2ddd3 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.
2026-08-21 13:28:40 +09:00
Junegunn Choi 18e5009e0f 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
2026-08-21 13:28:39 +09:00
23 changed files with 82 additions and 550 deletions
-19
View File
@@ -3,15 +3,6 @@ CHANGELOG
0.74.4 0.74.4
------ ------
- Fixed an escape sequence split across reads being parsed as a fragment, which leaked the rest into the query (#4899)
- e.g. A terminal answering the startup `DECRQM` query late left `?2004;2$y`, CTRL-UP left `5A`, and SGR mouse input left `0;1;1M`
- Fixed `--tiebreak=pathname` not detecting the last path separator when the line contains a non-ASCII character before it (#4902)
- Fixed `progress` in the `--listen` status payload staying at 100 while a new search was running, which made a snapshot with a new query and the previous result set look complete (#4903)
- It is now reset when a search starts and reaches 100 on the final result, so `progress` of 100 means the matches belong to the query reported next to them
- Fixed adaptive height not reserving a line for the divider of an inline header or footer border, so the list came up one line short for each of them (#4904)
- e.g. `seq 10 | fzf --height=~100% --list-border --header-lines=1 --header-lines-border=inline`
- Fixed fzf erasing the line the prompt was on when it exits, which made the last line of the prompt flicker in fish, bash, and nushell (#4913)
- Fixed fzf exiting with status 2 while waiting for a key, when `--listen` is used and 100+ signals interrupt the wait (#4917)
- Vim plugin - Vim plugin
- fzf no longer blocks the editor, so live previews keep working while fzf is open - 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 - `fzf#run` returns an empty list when it runs fzf asynchronously. Use `sink`, `sinklist`, or `exit` to get the result
@@ -20,16 +11,6 @@ CHANGELOG
```vim ```vim
let g:fzf_layout = { 'popup': '90%,70%' } let g:fzf_layout = { 'popup': '90%,70%' }
``` ```
- fzf now opens in a tmux or Zellij floating pane by default, so the window it was started from stays visible and can be used while fzf is running
- Requires tmux 3.7+ or Zellij 0.44+
- Set `g:fzf_layout` to pick a different layout
- fish
- Fixed custom CTRL-T command not using the prefixed target directory in some cases (#4498) (@bitraid)
- Optimized description alignment of completion items (#4910) (@bitraid)
- nushell
- Added key bindings for Helix editing modes, on nushell 0.115.0 or above (#4914) (@sim590)
- Fixed CTRL-T inserting the selected paths unquoted
- p4p3r (@P4P3R-HAK) reported the security vulnerability and suggested the fix
0.74.3 0.74.3
------ ------
+19 -73
View File
@@ -1,26 +1,6 @@
FZF Vim integration FZF Vim integration
=================== ===================
<!-- vim-markdown-toc GFM -->
* [Installation](#installation)
* [Summary](#summary)
* [`:FZF[!]`](#fzf)
* [Configuration](#configuration)
* [Examples](#examples)
* [Explanation of `g:fzf_colors`](#explanation-of-gfzf_colors)
* [`fzf#run`](#fzfrun)
* [`fzf#wrap`](#fzfwrap)
* [Global options supported by `fzf#wrap`](#global-options-supported-by-fzfwrap)
* [Tips](#tips)
* [fzf inside terminal buffer](#fzf-inside-terminal-buffer)
* [Starting fzf in a Vim popup window](#starting-fzf-in-a-vim-popup-window)
* [Starting fzf in a tmux/Zellij popup window](#starting-fzf-in-a-tmuxzellij-popup-window)
* [Hide statusline](#hide-statusline)
* [License](#license)
<!-- vim-markdown-toc -->
Installation Installation
------------ ------------
@@ -153,35 +133,19 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
if exists('$TMUX') || exists('$ZELLIJ') " - Popup window (center of the screen)
" The Vim plugin will try to open fzf in a tmux or Zellij popup let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" if possible (requires recent fzf and tmux/zellij) using --popup option,
" with the following argument:
let g:fzf_layout = { 'popup': '90%,60%' }
else
" If --popup option is not available, it will open in a popup window inside
" Vim (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
" Here are some more layout examples: " - Popup window (center of the current window)
" - Tmux or Zellij popup at the bottom 40%
let g:fzf_layout = { 'popup': 'bottom,40%' }
" - Tmux or Zellij popup at the top with a different size
let g:fzf_layout = { 'popup': 'top,90%,40%' }
" - Vim popup window: at the center of the current window (relative)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
" - Vim popup window: anchored to the bottom of the current window " - Popup window (anchored to the bottom of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
" - Vim split window: down / up / left / right " - down / up / left / right
let g:fzf_layout = { 'down': '40%' } let g:fzf_layout = { 'down': '40%' }
" - Vim window using a Vim command " - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' } let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' } let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
@@ -444,10 +408,10 @@ Tips
### fzf inside terminal buffer ### fzf inside terminal buffer
When fzf is configured to start in a terminal buffer inside Vim or Neovim, you On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
may find the default ANSI colors to be different. In that case, configure the If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x` in colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
Neovim. in Neovim.
```vim ```vim
" Terminal colors for seoul256 color scheme " Terminal colors for seoul256 color scheme
@@ -478,10 +442,7 @@ else
endif endif
``` ```
### Starting fzf in a Vim popup window ### Starting fzf in a popup window
You can configure fzf to start in a Vim popup window by setting the `window` key
in `g:fzf_layout`.
```vim ```vim
" Required: " Required:
@@ -497,32 +458,18 @@ in `g:fzf_layout`.
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
``` ```
### Starting fzf in a tmux/Zellij popup window 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`
fzf can also start in a popup of the multiplexer instead of a window inside key. `tmux` is accepted as a synonym, just as `--tmux` is an alias of
Vim, by putting a `--popup` option value in the `popup` key. `tmux` is `--popup`.
accepted as a synonym, just as `--tmux` is an alias of `--popup`.
The layout works on tmux 3.3 or above, or on Zellij 0.44 or above with fzf
0.71.0 or above. It is the default on tmux 3.7 or above with fzf 0.74.0 or
above, and on Zellij, where the pane is not modal: Vim keeps the window fzf
was started from visible, and you can switch to it while fzf is open. Below
those versions tmux gives a popup that cannot be left, so a window inside Vim
is the default there. On tmux, an explicit `--border` style also gives a modal
popup rather than a floating pane, because the native border of a tmux
floating pane cannot be removed. Drop `--border` to keep the floating pane and
its native border. Zellij keeps the floating pane either way, and hides its
native border when fzf draws one. Set `g:fzf_layout` yourself to choose either
one.
```vim ```vim
" See `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
if exists('$TMUX') || exists('$ZELLIJ') if exists('$TMUX') || exists('$ZELLIJ')
" See `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
let g:fzf_layout = { 'popup': '90%,70%' } let g:fzf_layout = { 'popup': '90%,70%' }
else else
" Configure the Vim popup window in case not on the multiplexer let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.7 } }
endif endif
``` ```
@@ -530,8 +477,7 @@ endif
When fzf starts in a terminal buffer, the file type of the buffer is set to When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of `fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window. This applies to the layouts that open inside Vim, not to the tmux the window.
or Zellij pane the default uses, which is not a buffer.
For example, if you open fzf on the bottom on the screen (e.g. `{'down': For example, if you open fzf on the bottom on the screen (e.g. `{'down':
'40%'}`), you might want to temporarily disable the statusline for a cleaner '40%'}`), you might want to temporarily disable the statusline for a cleaner
+1 -1
View File
@@ -18,7 +18,7 @@ triggered by a tag push.
2. Verify file consistency, sign the tag, and push the tag. 2. Verify file consistency, sign the tag, and push the tag.
```sh ```sh
make tag VERSION=0.74.4 make tag VERSION=0.74.3
``` ```
`make tag` runs `prerelease` first (checks that the version `make tag` runs `prerelease` first (checks that the version
+16 -54
View File
@@ -14,8 +14,7 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap| Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
Tips |fzf-tips| Tips |fzf-tips|
fzf inside terminal buffer |fzf-inside-terminal-buffer| fzf inside terminal buffer |fzf-inside-terminal-buffer|
Starting fzf in a Vim popup window |fzf-starting-fzf-in-a-vim-popup-window| Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
Starting fzf in a tmux/Zellij popup window |fzf-starting-fzf-in-a-tmuxzellij-popup-window|
Hide statusline |fzf-hide-statusline| Hide statusline |fzf-hide-statusline|
License |fzf-license| License |fzf-license|
@@ -162,35 +161,19 @@ Examples~
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
if exists('$TMUX') || exists('$ZELLIJ') " - Popup window (center of the screen)
" The Vim plugin will try to open fzf in a tmux or Zellij popup
" if possible (requires recent fzf and tmux/zellij) using --popup option,
" with the following argument:
let g:fzf_layout = { 'popup': '90%,60%' }
else
" If --popup option is not available, it will open in a popup window inside
" Vim (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
" Here are some more layout examples: " - Popup window (center of the current window)
" - Tmux or Zellij popup at the bottom 40%
let g:fzf_layout = { 'popup': 'bottom,40%' }
" - Tmux or Zellij popup at the top with a different size
let g:fzf_layout = { 'popup': 'top,90%,40%' }
" - Vim popup window: at the center of the current window (relative)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
" - Vim popup window: anchored to the bottom of the current window " - Popup window (anchored to the bottom of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
" - Vim split window: down / up / left / right " - down / up / left / right
let g:fzf_layout = { 'down': '40%' } let g:fzf_layout = { 'down': '40%' }
" - Vim window using a Vim command " - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' } let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' } let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
@@ -435,8 +418,8 @@ TIPS *fzf-tips*
*fzf-inside-terminal-buffer* *fzf-inside-terminal-buffer*
When fzf is configured to start in a terminal buffer inside Vim or Neovim, you On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
may find the default ANSI colors to be different. In that case, configure the If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x` colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim. in Neovim.
@@ -469,11 +452,8 @@ in Neovim.
endif endif
< <
< Starting fzf in a Vim popup window >________________________________________~ < Starting fzf in a popup window >____________________________________________~
*fzf-starting-fzf-in-a-vim-popup-window* *fzf-starting-fzf-in-a-popup-window*
You can configure fzf to start in a Vim popup window by setting the `window`
key in `g:fzf_layout`.
> >
" Required: " Required:
" - width [float range [0 ~ 1]] or [integer range [8 ~ ]] " - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
@@ -487,33 +467,16 @@ key in `g:fzf_layout`.
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right' " - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
< <
Alternatively, you can make fzf open in a popup window (requires tmux 3.3 or
< Starting fzf in a tmux/Zellij popup window >________________________________~ above, or Zellij 0.44 or above) by putting `--popup` options in `popup` key.
*fzf-starting-fzf-in-a-tmuxzellij-popup-window* `tmux` is accepted as a synonym, just as `--tmux` is an alias of `--popup`.
fzf can also start in a popup of the multiplexer instead of a window inside
Vim, by putting a `--popup` option value in the `popup` key. `tmux` is
accepted as a synonym, just as `--tmux` is an alias of `--popup`.
The layout works on tmux 3.3 or above, or on Zellij 0.44 or above with fzf
0.71.0 or above. It is the default on tmux 3.7 or above with fzf 0.74.0 or
above, and on Zellij, where the pane is not modal: Vim keeps the window fzf
was started from visible, and you can switch to it while fzf is open. Below
those versions tmux gives a popup that cannot be left, so a window inside Vim
is the default there. On tmux, an explicit `--border` style also gives a modal
popup rather than a floating pane, because the native border of a tmux
floating pane cannot be removed. Drop `--border` to keep the floating pane and
its native border. Zellij keeps the floating pane either way, and hides its
native border when fzf draws one. Set `g:fzf_layout` yourself to choose either
one.
> >
if exists('$TMUX') || exists('$ZELLIJ')
" See `--popup` option in `man fzf` for available options " See `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]] " [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
if exists('$TMUX') || exists('$ZELLIJ')
let g:fzf_layout = { 'popup': '90%,70%' } let g:fzf_layout = { 'popup': '90%,70%' }
else else
" Configure the Vim popup window in case not on the multiplexer let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.7 } }
endif endif
< <
@@ -522,8 +485,7 @@ one.
When fzf starts in a terminal buffer, the file type of the buffer is set to When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of `fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window. This applies to the layouts that open inside Vim, not to the tmux the window.
or Zellij pane the default uses, which is not a buffer.
For example, if you open fzf on the bottom on the screen (e.g. `{'down': For example, if you open fzf on the bottom on the screen (e.g. `{'down':
'40%'}`), you might want to temporarily disable the statusline for a cleaner '40%'}`), you might want to temporarily disable the statusline for a cleaner
+1 -1
View File
@@ -2,7 +2,7 @@
set -u set -u
version=0.74.4 version=0.74.3
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
+1 -1
View File
@@ -1,4 +1,4 @@
$version="0.74.4" $version="0.74.3"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
+1 -1
View File
@@ -21,7 +21,7 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Sep 2026" "fzf 0.74.4" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Aug 2026" "fzf 0.74.3" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane
+1 -4
View File
@@ -21,7 +21,7 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Sep 2026" "fzf 0.74.4" "fzf - a command-line fuzzy finder" .TH fzf 1 "Aug 2026" "fzf 0.74.3" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -408,9 +408,6 @@ Adaptive height has the following limitations:
* Cannot be used with top/bottom margin and padding given in percent size * Cannot be used with top/bottom margin and padding given in percent size
.br .br
* It will not find the right size when there are multi-line items * It will not find the right size when there are multi-line items
.br
* fzf cannot start until the input ends or exceeds the height, so a stream
that stays below it will delay the first render
.TP .TP
.BI "\-\-min\-height=" "HEIGHT[+]" .BI "\-\-min\-height=" "HEIGHT[+]"
+12 -81
View File
@@ -140,15 +140,6 @@ function! s:popup_support()
endfunction endfunction
function! s:default_layout() function! s:default_layout()
" A floating pane leaves the window fzf was started from visible and
" reachable while fzf is open. A popup covers it, inside Vim or not.
" Without a job, s:execute_tmux() blocks and freezes Vim. fzf also wants a
" tmux window of at least 3x3, and Vim's pane is never larger than the
" window, so asking Vim is free and survives a resize
if (has('nvim') || has('job')) && &columns >= 3 && &lines >= 3
\ && s:tmux_enabled() && get(s:, 'tmux_floating', 0)
return { 'tmux': '90%,60%' }
endif
return s:popup_support() return s:popup_support()
\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } } \ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
\ : { 'down': '~40%' } \ : { 'down': '~40%' }
@@ -174,12 +165,6 @@ function! fzf#install()
if v:shell_error if v:shell_error
throw 'Failed to download fzf: '.script throw 'Failed to download fzf: '.script
endif endif
" A new binary invalidates the chosen executable and everything derived from
" its version, including whether fzf opens a floating pane. fzf#install() is
" also the vim-plug 'do' hook, so this can run long after the first fzf call
let [s:versions, s:checked] = [{}, {}]
unlet! s:exec s:tmux s:tmux_floating
endfunction endfunction
let s:versions = {} let s:versions = {}
@@ -272,7 +257,7 @@ function! fzf#exec(...)
endfunction endfunction
" Path to the fzf-tmux script, or an empty string if it is not available. Only " Path to the fzf-tmux script, or an empty string if it is not available. Only
" the legacy options still need it. --popup is handled by fzf itself. " the legacy options still need it. --tmux is handled by fzf itself.
function! s:fzf_tmux_script() function! s:fzf_tmux_script()
if !executable(s:fzf_tmux) if !executable(s:fzf_tmux)
if !executable('fzf-tmux') if !executable('fzf-tmux')
@@ -288,7 +273,14 @@ function! s:tmux_enabled()
return 0 return 0
endif endif
if empty($TMUX) && empty($ZELLIJ) " --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 return 0
endif endif
@@ -296,21 +288,7 @@ function! s:tmux_enabled()
return s:tmux return s:tmux
endif endif
let [s:tmux, s:tmux_floating] = [0, 0] let s:tmux = 0
" --popup covers Zellij as well, where the fzf-tmux script and the tmux
" version are irrelevant. fzf learned it in 0.71.0, and the floating pane
" options it passes need Zellij 0.44 or above. fzf checks tmux first, so
" this branch is Zellij without tmux. Both non-empty means tmux wins.
" empty(), not exists(), to match how fzf reads the two variables
if empty($TMUX)
let s:tmux =
\ s:compare_versions(s:get_version(s:fzf_binary()), '0.71.0') >= 0
\ && s:compare_versions(s:zellij_version(), '0.44') >= 0
let s:tmux_floating = s:tmux
return s:tmux
endif
let output = system('tmux -V') let output = system('tmux -V')
if v:shell_error if v:shell_error
return s:tmux return s:tmux
@@ -318,15 +296,9 @@ function! s:tmux_enabled()
" e.g. 'tmux 3.7b', 'tmux next-3.8' " e.g. 'tmux 3.7b', 'tmux next-3.8'
let ver = matchstr(output, '\d\+\.\d\+') let ver = matchstr(output, '\d\+\.\d\+')
" --popup requires tmux 3.3 or above, and needs no fzf-tmux script. The " --tmux requires tmux 3.3 or above, and needs no fzf-tmux script
" default layout wants a floating pane, which also needs fzf 0.74.0 or
" above. fzf opens a modal popup otherwise. The version here only skips the
" probe for servers too old to answer it
if s:compare_versions(ver, '3.3') >= 0 if s:compare_versions(ver, '3.3') >= 0
let s:tmux = 1 let s:tmux = 1
let s:tmux_floating = s:compare_versions(ver, '3.7') >= 0
\ && s:tmux_floating_pane_support()
\ && s:compare_versions(s:get_version(s:fzf_binary()), '0.74.0') >= 0
return s:tmux return s:tmux
endif endif
@@ -479,10 +451,7 @@ function! fzf#wrap(...)
if !exists('g:fzf_layout') && exists('g:fzf_height') if !exists('g:fzf_layout') && exists('g:fzf_height')
let opts.down = g:fzf_height let opts.down = g:fzf_height
else else
" Not get(), which would evaluate s:default_layout() and run its version let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout())))
" checks even when g:fzf_layout makes the answer irrelevant
let opts = extend(opts, s:validate_layout(
\ exists('g:fzf_layout') ? g:fzf_layout : s:default_layout()))
endif endif
endif endif
@@ -658,44 +627,6 @@ function! s:present(dict, ...)
return 0 return 0
endfunction endfunction
" The binary fzf#exec() would choose, without its prompting or installing.
" Layout selection runs before fzf#exec() has resolved one
function! s:fzf_binary()
if exists('s:exec')
return s:exec
endif
let bins = filter(['fzf', s:fzf_go], 'executable(v:val)')
if empty(bins)
return ''
endif
return len(bins) > 1 ? sort(bins, 's:compare_binary_versions')[-1] : bins[0]
endfunction
function! s:zellij_version()
if !exists('s:zellij_ver')
let output = systemlist('zellij --version')
let s:zellij_ver = v:shell_error || empty(output)
\ ? '' : matchstr(output[0], '[0-9.]\+')
endif
return s:zellij_ver
endfunction
" Whether the running server can put fzf in a floating pane. fzf decides on
" the server, not on the version the tmux client reports, so ask it the same
" question rather than predicting the answer. See tmuxFloatingPaneInfo in
" src/tmux.go, which also requires the tmux window to be at least 3x3; that
" one changes with a resize, so s:default_layout() reads it from Vim instead
" of caching it here
function! s:tmux_floating_pane_support()
" fzf does not use a floating pane when it was not started from a pane
if empty($TMUX_PANE)
return 0
endif
let out = system('tmux list-commands new-pane')
" A server that does not know the command exits normally with no output
return !v:shell_error && out =~# 'new-pane'
endfunction
function! s:fzf_tmux(dict) function! s:fzf_tmux(dict)
let size = get(a:dict, 'tmux', '') let size = get(a:dict, 'tmux', '')
if empty(size) if empty(size)
+4 -2
View File
@@ -124,8 +124,10 @@ function fzf_complete -w fzf -d 'fzf command completion and wildcard expansion s
# Determine the tabstop length for description alignment # Determine the tabstop length for description alignment
set -l -- max_columns (math $COLUMNS - 40) set -l -- max_columns (math $COLUMNS - 40)
for len in (string match -r -- '^[^\\t]*(?=\\t)' $list[1..500] | string length -V) for i in $list[1..500]
test "$len" -gt "$tabstop" -a "$len" -lt "$max_columns" set -l -- item (string split -f 1 -- \t $i)
and set -l -- len (string length -V -- $item)
and test "$len" -gt "$tabstop" -a "$len" -lt "$max_columns"
and set -- tabstop $len and set -- tabstop $len
end end
set -- tabstop (math $tabstop + 4) set -- tabstop (math $tabstop + 4)
+3 -10
View File
@@ -104,7 +104,7 @@ function fzf_key_bindings
# Store current token in $dir as root for the 'find' command # Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders" function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline) set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1] set -lx dir $commandline[1]
set -l fzf_query $commandline[2] set -l fzf_query $commandline[2]
set -l prefix $commandline[3] set -l prefix $commandline[3]
@@ -112,17 +112,10 @@ function fzf_key_bindings
"--reverse --walker=file,dir,follow,hidden --scheme=path" \ "--reverse --walker=file,dir,follow,hidden --scheme=path" \
"--multi $FZF_CTRL_T_OPTS --print0") "--multi $FZF_CTRL_T_OPTS --print0")
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
if test -n "$FZF_CTRL_T_COMMAND"
set -f result (eval $FZF_CTRL_T_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
else
set -f result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
end
test -n "$result"
and commandline -rt -- (string join -- ' ' $prefix(string escape -n -- $result))' ' and commandline -rt -- (string join -- ' ' $prefix(string escape -n -- $result))' '
commandline -f repaint commandline -f repaint
+9 -33
View File
@@ -47,20 +47,6 @@ def __fzfcmd []: nothing -> list<string> {
['fzf'] ['fzf']
} }
# Keybinding modes to activate. The Helix modes only exist since Nushell
# 0.115.0, so they are only included when running a version that supports
# them (major > 0 covers a hypothetical 1.0+ where the minor resets).
def __fzf_modes []: nothing -> list<string> {
let v = version
let major = ($v.major | into int)
let minor = ($v.minor | into int)
if ($major > 0) or ($minor >= 115) {
['emacs', 'vi_normal', 'vi_insert', 'helix_normal', 'helix_select', 'helix_insert']
} else {
['emacs', 'vi_normal', 'vi_insert']
}
}
export-env { export-env {
$env.FZF_CTRL_T_OPTS = $env.FZF_CTRL_T_OPTS? | default "" $env.FZF_CTRL_T_OPTS = $env.FZF_CTRL_T_OPTS? | default ""
@@ -69,11 +55,11 @@ export-env {
} }
# Directories # Directories
let alt_c = { const alt_c = {
name: fzf_dirs name: fzf_dirs
modifier: alt modifier: alt
keycode: char_c keycode: char_c
mode: (__fzf_modes) mode: [emacs, vi_normal, vi_insert]
event: [ event: [
{ {
send: executehostcommand send: executehostcommand
@@ -96,11 +82,11 @@ let alt_c = {
} }
# History # History
let ctrl_r = { const ctrl_r = {
name: fzf_history name: fzf_history
modifier: control modifier: control
keycode: char_r keycode: char_r
mode: (__fzf_modes) mode: [emacs, vi_insert, vi_normal]
event: [ event: [
{ {
send: executehostcommand send: executehostcommand
@@ -129,16 +115,16 @@ let ctrl_r = {
} }
# Files # Files
let ctrl_t = { const ctrl_t = {
name: fzf_files name: fzf_files
modifier: control modifier: control
keycode: char_t keycode: char_t
mode: (__fzf_modes) mode: [emacs, vi_normal, vi_insert]
event: [ event: [
{ {
send: executehostcommand send: executehostcommand
cmd: " cmd: "
let fzf_opts = (__fzf_defaults '--reverse --walker=file,dir,follow,hidden --scheme=path' $'($env.FZF_CTRL_T_OPTS) -m --print0'); let fzf_opts = (__fzf_defaults '--reverse --walker=file,dir,follow,hidden --scheme=path' $'($env.FZF_CTRL_T_OPTS) -m');
let fzfcmd = (__fzfcmd); let fzfcmd = (__fzfcmd);
let fzf_args = ($fzfcmd | skip 1); let fzf_args = ($fzfcmd | skip 1);
let ctrl_t_cmd = ($env.FZF_CTRL_T_COMMAND? | default null); let ctrl_t_cmd = ($env.FZF_CTRL_T_COMMAND? | default null);
@@ -149,19 +135,9 @@ let ctrl_t = {
let sh_cmd = [$ctrl_t_cmd '|' $fzf_cmd_str] | str join ' '; let sh_cmd = [$ctrl_t_cmd '|' $fzf_cmd_str] | str join ' ';
with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^sh -c $sh_cmd } with-env { FZF_DEFAULT_OPTS: $fzf_opts, FZF_DEFAULT_OPTS_FILE: '' } { ^sh -c $sh_cmd }
}; };
# Serialize each path as a Nushell string literal, so that syntax let result = ($result | str replace --all (char newline) ' ' | str trim);
# in a file name is not evaluated when the line is executed. commandline edit --append $result;
let result = (
$result
| split row (char nul)
| where {|path| $path != ''}
| each {|path| $path | to nuon}
| str join ' '
);
if ($result | is-not-empty) {
commandline edit --append $'($result) ';
commandline set-cursor --end commandline set-cursor --end
}
" "
} }
] ]
+3 -3
View File
@@ -82,10 +82,10 @@ func buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int,
val = item.TrimLength() val = item.TrimLength()
case byPathname: case byPathname:
if validOffsetFound { if validOffsetFound {
// Rune index, to be comparable with minBegin
lastDelim := -1 lastDelim := -1
for i := numChars - 1; i >= 0; i-- { s := item.text.ToString()
if r := item.text.Get(i); r == '/' || r == '\\' { for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' || s[i] == '\\' {
lastDelim = i lastDelim = i
break break
} }
-25
View File
@@ -272,28 +272,3 @@ func TestRadixSortResults(t *testing.T) {
} }
} }
} }
func TestPathnameTiebreak(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byPathname}
score := 100
test := func(input string, offset Offset, expected uint16) {
for _, chars := range []util.Chars{util.ToChars([]byte(input)), util.RunesToChars([]rune(input))} {
item := buildResult(withIndex(&Item{text: chars}, 1), []Offset{offset}, score)
if item.points[3] != math.MaxUint16-uint16(score) || item.points[2] != expected {
t.Error(input, item.points, expected)
}
}
}
// Match in the file name
test("x/foo/foo.txt", Offset{6, 9}, 1)
// Match in the directory path
test("x/foo/aa.txt", Offset{2, 5}, math.MaxUint16)
// Offsets are rune indexes, so a multi-byte character before the last
// delimiter must not shift the delimiter position
test("一x/foo/foo.txt", Offset{7, 10}, 1)
test("一x/foo/aa.txt", Offset{3, 6}, math.MaxUint16)
}
+3 -14
View File
@@ -1547,14 +1547,6 @@ func (t *Terminal) visibleInputLinesInList() int {
// Extra number of lines needed to display fzf // Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int { func (t *Terminal) extraLines() int {
// borderLines() reports zero for BorderInline, but addInline() still
// reserves a divider line for it
sectionLines := func(shape tui.BorderShape) int {
if shape == tui.BorderInline {
return 1
}
return borderLines(shape)
}
extra := 0 extra := 0
if !t.inputless { if !t.inputless {
extra++ extra++
@@ -1570,16 +1562,16 @@ func (t *Terminal) extraLines() int {
} }
if t.headerVisible { if t.headerVisible {
if t.hasHeaderWindow() { if t.hasHeaderWindow() {
extra += sectionLines(t.headerBorderShape) extra += borderLines(t.headerBorderShape)
} }
extra += len(t.header0) extra += len(t.header0)
if w, shape := t.determineHeaderLinesShape(); w { if w, shape := t.determineHeaderLinesShape(); w {
extra += sectionLines(shape) extra += borderLines(shape)
} }
extra += t.headerLines extra += t.headerLines
} }
if len(t.footer) > 0 { if len(t.footer) > 0 {
extra += sectionLines(t.footerBorderShape) extra += borderLines(t.footerBorderShape)
extra += len(t.footer) extra += len(t.footer)
} }
return extra return extra
@@ -2059,9 +2051,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
prevIndex = t.targetIndex prevIndex = t.targetIndex
t.targetIndex = minItem.Index() t.targetIndex = minItem.Index()
} }
if result.final() {
t.progress = 100 t.progress = 100
}
t.merger = merger t.merger = merger
t.resultMerger = merger t.resultMerger = merger
t.passMerger = result.passMerger t.passMerger = result.passMerger
@@ -8599,7 +8589,6 @@ func (t *Terminal) Loop() error {
reload := changed || newCommand != nil reload := changed || newCommand != nil
if reload { if reload {
t.wait.searching = true t.wait.searching = true
t.progress = 0
} }
var reloadRequest *searchRequest var reloadRequest *searchRequest
if reload { if reload {
-6
View File
@@ -29,9 +29,6 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
} }
func TestReplacePlaceholder(t *testing.T) { func TestReplacePlaceholder(t *testing.T) {
// Pin $SHELL so the quoting style doesn't depend on the test runner's shell
t.Setenv("SHELL", "cmd")
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m") item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
items1 := [3][]*Item{{item1}, {item1}, nil} items1 := [3][]*Item{{item1}, {item1}, nil}
items2 := [3][]*Item{ items2 := [3][]*Item{
@@ -258,9 +255,6 @@ func TestQuoteEntry(t *testing.T) {
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`, `\`} unixStyle := quotes{``, `'`, `'\''`, `"`, `\`, `\`}
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`, `\`} windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`, `\`}
var effectiveStyle quotes var effectiveStyle quotes
// Pin $SHELL so the quoting style doesn't depend on the test runner's shell
t.Setenv("SHELL", "cmd")
exec := util.NewExecutor("") exec := util.NewExecutor("")
if util.IsWindows() { if util.IsWindows() {
+2 -53
View File
@@ -26,7 +26,7 @@ const (
offsetPollTries = 10 offsetPollTries = 10
queryTimeout = 500 * time.Millisecond queryTimeout = 500 * time.Millisecond
maxInputBuffer = 1024 * 1024 maxInputBuffer = 1024 * 1024
escapeLookback = 256 maxSelectTries = 100
) )
const DefaultTtyDevice string = "/dev/tty" const DefaultTtyDevice string = "/dev/tty"
@@ -175,7 +175,6 @@ type LightRenderer struct {
width int width int
height int height int
yoffset int yoffset int
xoffset int
tabstop int tabstop int
escDelay int escDelay int
fullscreen bool fullscreen bool
@@ -278,7 +277,6 @@ func (r *LightRenderer) Init() error {
// increased and we're left with unwanted extra new line. // increased and we're left with unwanted extra new line.
if x > 0 && r.clearOnExit { if x > 0 && r.clearOnExit {
r.upOneLine = true r.upOneLine = true
r.xoffset = x
r.makeSpace() r.makeSpace()
} }
// We assume that --no-clear is used for repetitive relaunching of fzf. // We assume that --no-clear is used for repetitive relaunching of fzf.
@@ -340,45 +338,6 @@ func getEnv(name string, defaultValue int) int {
return atoi(env, defaultValue) return atoi(env, defaultValue)
} }
// Bytes of a CSI sequence: parameter and intermediate bytes continue it, a
// final byte ends it. Order is not enforced. Strictness would only make fzf
// give up on a sequence it could have framed.
//
// https://vt100.net/emu/dec_ansi_parser
func csiContinues(b byte) bool { return b >= 0x20 && b <= 0x3f }
func csiFinal(b byte) bool { return b >= 0x40 && b <= 0x7e }
// incompleteEscape reports whether the buffer ends in an escape sequence that
// has not been terminated yet. The read loop keeps waiting in that case, so the
// parser is never handed a fragment to guess at.
func incompleteEscape(buffer []byte) bool {
// Only the tail can hold a sequence still arriving. This runs once per byte
// read, so scanning all of a large paste would make the read quadratic.
tail := buffer
if len(tail) > escapeLookback {
tail = tail[len(tail)-escapeLookback:]
}
start := bytes.LastIndexByte(tail, Esc.Byte())
if start < 0 || len(tail)-start < 2 {
return false
}
switch tail[start+1] {
case '[':
for _, b := range tail[start+2:] {
if csiFinal(b) {
return false
}
if !csiContinues(b) {
return false // malformed, do not wait for a terminator
}
}
return true
case 'O':
return len(tail)-start < 3
}
return false
}
func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) { func (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {
return r.getBytesInternal(cancellable, r.buffer, false) return r.getBytesInternal(cancellable, r.buffer, false)
} }
@@ -419,13 +378,6 @@ func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblo
retries = 0 retries = 0
} }
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
// Keep waiting while a sequence is still arriving. Dropping the budget
// after every byte left fzf parsing whatever the read happened to end on.
// Past the introducer this is not the ESC key, so the wait costs no
// Escape latency and ESCDELAY=0 must not reduce it to nothing.
if retries == 0 && incompleteEscape(buffer) {
retries = max(r.escDelay, defaultEscDelay) / escPollInterval
}
pc = c pc = c
// This should never happen under normal conditions, // This should never happen under normal conditions,
@@ -1180,13 +1132,10 @@ func (r *LightRenderer) Close() {
r.rmcup() r.rmcup()
} else { } else {
r.origin() r.origin()
// Erase our own area first, then step back onto the line the
// prompt was on, so nothing on that line is touched
r.csi("J")
if r.upOneLine { if r.upOneLine {
r.csi("A") r.csi("A")
r.csi(fmt.Sprintf("%dG", r.xoffset+1))
} }
r.csi("J")
} }
} else if !r.fullscreen { } else if !r.fullscreen {
r.stderr("\x1b8") // DECRC: restore cursor position r.stderr("\x1b8") // DECRC: restore cursor position
-53
View File
@@ -1,53 +0,0 @@
package tui
import (
"strings"
"testing"
)
func TestIncompleteEscape(t *testing.T) {
for _, c := range []struct {
buffer string
want bool
}{
// Complete sequences: nothing to wait for
{"\x1b[A", false},
{"\x1bOA", false},
{"\x1b[1;5A", false},
{"\x1b[200~", false},
{"\x1b[<0;1;1M", false},
{"\x1b[12;34R", false},
{"\x1b[?2004;2$y", false},
{"\x1b[?1;2c", false},
// Fragments: keep waiting
{"\x1b[", true},
{"\x1b[?", true},
{"\x1b[1;", true},
{"\x1b[?2004;2$", true},
{"\x1bO", true},
{"\x1b[<0;1;", true},
// Only the trailing sequence matters
{"ab\x1b[?2004;2$", true},
{"\x1b[A\x1b[", true},
{"\x1b[A\x1b[B", false},
// Long buffers: only the tail is scanned, so an introducer further
// back than escapeLookback is not waited for
{strings.Repeat("a", 100000), false},
{"\x1b[" + strings.Repeat("a", 100000), false},
{strings.Repeat("a", 100000) + "\x1b[1;", true},
// Not a sequence fzf waits on
{"", false},
{"abc", false},
{"\x1b", false}, // lone ESC, handled by the existing escDelay branch
{"\x1ba", false}, // ALT-a
{"\x1b[\x01", false}, // malformed, do not stall on it
} {
if got := incompleteEscape([]byte(c.buffer)); got != c.want {
t.Errorf("incompleteEscape(%q) = %v, want %v", c.buffer, got, c.want)
}
}
}
+2 -3
View File
@@ -240,7 +240,7 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
}() }()
cancelFd := int(rpipe.Fd()) cancelFd := int(rpipe.Fd())
for { for range maxSelectTries {
var rfds unix.FdSet var rfds unix.FdSet
limit := len(rfds.Bits) * unix.NFDBITS limit := len(rfds.Bits) * unix.NFDBITS
if fd >= limit || cancelFd >= limit { if fd >= limit || cancelFd >= limit {
@@ -251,8 +251,6 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
rfds.Set(cancelFd) rfds.Set(cancelFd)
_, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil) _, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil)
if err != nil { if err != nil {
// An interrupted wait is not a failed read, so it must not count
// against anything. Retry until the fd is ready or the wait fails.
if err == syscall.EINTR { if err == syscall.EINTR {
continue continue
} }
@@ -267,6 +265,7 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
return getter() return getter()
} }
} }
return 0, getCharError
} }
func (r *LightRenderer) Size() TermSize { func (r *LightRenderer) Size() TermSize {
-22
View File
@@ -882,28 +882,6 @@ class TestLayout < TestInteractive
tmux.until { assert_block(block, it) } tmux.until { assert_block(block, it) }
end end
def test_adaptive_height_with_inline_sections
tmux.send_keys %(seq 10 | #{FZF} --height=~100% --list-border --header-lines=1 --header-lines-border=inline), :Enter
block = <<~BLOCK
10
9
8
7
6
5
4
3
> 2
1
9/9
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_style_full_adaptive_height def test_style_full_adaptive_height
tmux.send_keys %(seq 1| #{FZF} --style=full:rounded --height=~100% --header-lines=1 --info=default), :Enter tmux.send_keys %(seq 1| #{FZF} --style=full:rounded --height=~100% --header-lines=1 --info=default), :Enter
block = <<~BLOCK block = <<~BLOCK
-23
View File
@@ -55,29 +55,6 @@ class TestServer < TestInteractive
end end
end end
def test_listen_progress
tmux.send_keys "seq 10 | #{FZF} --listen 6266", :Enter
tmux.until { |lines| assert_equal 10, lines.match_count }
uri = URI('http://localhost:6266')
state = -> { JSON.parse(Net::HTTP.get(uri), symbolize_names: true) }
# Idle: the last search is complete
assert_equal 100, state.call[:progress]
# While a search is running, progress is not left at 100 from the
# previous one, so it can tell a settled snapshot from a stale one
Net::HTTP.post(uri, 'reload(sleep 1; seq 100)')
tmux.until { assert_equal 0, state.call[:progress] }
# Settled: matches belong to the snapshot that reports 100
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.until do
st = state.call
assert_equal 100, st[:progress]
assert_equal 100, st[:matchCount]
end
end
def test_listen_with_api_key def test_listen_with_api_key
uri = URI('http://localhost:6266') uri = URI('http://localhost:6266')
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
-37
View File
@@ -1273,43 +1273,6 @@ class TestNushell < TestBase
FileUtils.rm_rf('/tmp/fzf-test') FileUtils.rm_rf('/tmp/fzf-test')
end end
# Override: paths are inserted as Nushell string literals, so the
# selections appear quoted on the command line.
def test_ctrl_t
set_var('FZF_CTRL_T_COMMAND', 'seq 100')
tmux.prepare
tmux.send_keys 'C-t'
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.send_keys :Tab, :Tab, :Tab
tmux.until { |lines| assert lines.any_include?(' (3)') }
tmux.send_keys :Enter
tmux.until { |lines| assert lines.any_include?('"1" "2" "3"') }
tmux.send_keys 'C-c'
end
# A path is inserted as a Nushell string literal, so that syntax in a file
# name is not evaluated and each path stays a single argument.
def test_ctrl_t_quoting
marker = "#{tempname}-marker"
FileUtils.rm_f(marker)
writelines(["fzf-inject$(touch #{marker}).txt", 'fzf-inject space.txt'])
set_var('FZF_CTRL_T_COMMAND', "cat #{tempname}")
tmux.prepare
tmux.send_keys '^printf "%s\n" ', 'C-t'
tmux.until { |lines| assert_equal 2, lines.match_count }
tmux.send_keys :Tab, :Tab
tmux.until { |lines| assert_equal 2, lines.select_count }
tmux.send_keys :Enter
tmux.until { |lines| assert_includes lines[-1].to_s, '"fzf-inject$(touch' }
tmux.send_keys :Enter
tmux.until do |lines|
assert_equal ["fzf-inject$(touch #{marker}).txt", 'fzf-inject space.txt'], lines[-2..]
end
refute_path_exists marker
end
# Nushell does not support multiline command recall the same way # Nushell does not support multiline command recall the same way
# as bash/zsh/fish, so test_ctrl_r_multiline is omitted. # as bash/zsh/fish, so test_ctrl_r_multiline is omitted.
-27
View File
@@ -102,36 +102,9 @@ Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
" Working directory changed due to &acd " Working directory changed due to &acd
AssertEqual '/', getcwd() AssertEqual '/', getcwd()
Execute (Default layout):
unlet! g:fzf_layout g:fzf_height
let layout_keys = ['window', 'popup', 'tmux', 'up', 'down', 'left', 'right']
let opts = fzf#wrap('foobar')
Log opts
let found = filter(copy(layout_keys), 'has_key(opts, v:val)')
AssertEqual 1, len(found)
if found[0] ==# 'tmux'
" Only where fzf opens a floating pane, which can be left while fzf runs
Assert !empty($TMUX) || !empty($ZELLIJ)
AssertEqual '90%,60%', opts.tmux
elseif found[0] ==# 'window'
AssertEqual 0.9, opts.window.width
else
" No popup support in this build
AssertEqual '~40%', opts.down
endif
" Fullscreen strips it, whichever it was
let opts = fzf#wrap('foobar', {}, 1)
Log opts
AssertEqual [], filter(copy(layout_keys), 'has_key(opts, v:val)')
Execute (fzf#wrap): Execute (fzf#wrap):
AssertThrows fzf#wrap({'foo': 'bar'}) AssertThrows fzf#wrap({'foo': 'bar'})
" Pin the layout so the assertions do not depend on the environment
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
let opts = fzf#wrap('foobar') let opts = fzf#wrap('foobar')
Log opts Log opts
AssertEqual 0.9, opts.window.width AssertEqual 0.9, opts.window.width