Compare 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
10 changed files with 52 additions and 367 deletions
-6
View File
@@ -3,9 +3,6 @@ CHANGELOG
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)
- 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
@@ -14,9 +11,6 @@ CHANGELOG
```vim
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
0.74.3
------
+19 -73
View File
@@ -1,26 +1,6 @@
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
------------
@@ -153,35 +133,19 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
if exists('$TMUX') || exists('$ZELLIJ')
" 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 } }
endif
" - Popup window (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Here are some more layout examples:
" - 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)
" - Popup window (center of the current window)
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 } }
" - Vim split window: down / up / left / right
" - down / up / left / right
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': '-tabnew' }
let g:fzf_layout = { 'window': '10new' }
@@ -444,10 +408,10 @@ Tips
### fzf inside terminal buffer
When fzf is configured to start in a terminal buffer inside Vim or Neovim, you
may find the default ANSI colors to be different. In that case, configure the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x` in
Neovim.
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
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 Neovim.
```vim
" Terminal colors for seoul256 color scheme
@@ -478,10 +442,7 @@ else
endif
```
### Starting fzf in a Vim popup window
You can configure fzf to start in a Vim popup window by setting the `window` key
in `g:fzf_layout`.
### Starting fzf in a popup window
```vim
" Required:
@@ -497,32 +458,18 @@ in `g:fzf_layout`.
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
```
### Starting fzf in a tmux/Zellij popup window
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.
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 `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
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%' }
else
" Configure the Vim popup window in case not on the multiplexer
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.7 } }
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
```
@@ -530,8 +477,7 @@ endif
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
the window. This applies to the layouts that open inside Vim, not to the tmux
or Zellij pane the default uses, which is not a buffer.
the window.
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
+18 -56
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|
Tips |fzf-tips|
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 tmux/Zellij popup window |fzf-starting-fzf-in-a-tmuxzellij-popup-window|
Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
Hide statusline |fzf-hide-statusline|
License |fzf-license|
@@ -162,35 +161,19 @@ Examples~
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
if exists('$TMUX') || exists('$ZELLIJ')
" 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 } }
endif
" - Popup window (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Here are some more layout examples:
" - 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)
" - Popup window (center of the current window)
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 } }
" - Vim split window: down / up / left / right
" - down / up / left / right
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': '-tabnew' }
let g:fzf_layout = { 'window': '10new' }
@@ -435,8 +418,8 @@ TIPS *fzf-tips*
*fzf-inside-terminal-buffer*
When fzf is configured to start in a terminal buffer inside Vim or Neovim, you
may find the default ANSI colors to be different. In that case, configure the
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
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 Neovim.
@@ -469,11 +452,8 @@ in Neovim.
endif
<
< Starting fzf in a Vim popup window >________________________________________~
*fzf-starting-fzf-in-a-vim-popup-window*
You can configure fzf to start in a Vim popup window by setting the `window`
key in `g:fzf_layout`.
< Starting fzf in a popup window >____________________________________________~
*fzf-starting-fzf-in-a-popup-window*
>
" Required:
" - 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'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
<
< Starting fzf in a tmux/Zellij popup window >________________________________~
*fzf-starting-fzf-in-a-tmuxzellij-popup-window*
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.
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 `--popup` option in `man fzf` for available options
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
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%' }
else
" Configure the Vim popup window in case not on the multiplexer
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.7 } }
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
<
@@ -522,8 +485,7 @@ one.
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
the window. This applies to the layouts that open inside Vim, not to the tmux
or Zellij pane the default uses, which is not a buffer.
the window.
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
+12 -71
View File
@@ -140,11 +140,6 @@ function! s:popup_support()
endfunction
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
if s:floating_pane()
return { 'tmux': '90%,60%' }
endif
return s:popup_support()
\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
\ : { 'down': '~40%' }
@@ -170,12 +165,6 @@ function! fzf#install()
if v:shell_error
throw 'Failed to download fzf: '.script
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
let s:versions = {}
@@ -268,7 +257,7 @@ function! fzf#exec(...)
endfunction
" 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()
if !executable(s:fzf_tmux)
if !executable('fzf-tmux')
@@ -284,7 +273,14 @@ function! s:tmux_enabled()
return 0
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
endif
@@ -292,21 +288,7 @@ function! s:tmux_enabled()
return s:tmux
endif
let [s:tmux, s:tmux_floating] = [0, 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 s:tmux = 0
let output = system('tmux -V')
if v:shell_error
return s:tmux
@@ -314,15 +296,9 @@ function! s:tmux_enabled()
" e.g. 'tmux 3.7b', 'tmux next-3.8'
let ver = matchstr(output, '\d\+\.\d\+')
" --popup requires tmux 3.3 or above, and needs no fzf-tmux script. The
" default layout wants a floating pane, which needs tmux 3.7 or above, fzf
" 0.74.0 or above, and a pane to start from. fzf opens a modal popup when
" any of the three is missing
" --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
let s:tmux_floating = s:compare_versions(ver, '3.7') >= 0
\ && !empty($TMUX_PANE)
\ && s:compare_versions(s:get_version(s:fzf_binary()), '0.74.0') >= 0
return s:tmux
endif
@@ -475,10 +451,7 @@ function! fzf#wrap(...)
if !exists('g:fzf_layout') && exists('g:fzf_height')
let opts.down = g:fzf_height
else
" Not get(), which would evaluate s:default_layout() and run its version
" 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()))
let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout())))
endif
endif
@@ -654,38 +627,6 @@ function! s:present(dict, ...)
return 0
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 fzf will open a floating pane that can be left while fzf runs. A
" tmux popup below 3.7 is modal, and the fzf-tmux script that older tmux
" versions need does not accept a --popup value at all. Without a job to wait
" on fzf, s:execute_tmux() blocks on system() and the window stays visible but
" frozen, so the default keeps fzf inside Vim there
function! s:floating_pane()
return (has('nvim') || has('job'))
\ && s:tmux_enabled() && get(s:, 'tmux_floating', 0)
endfunction
function! s:fzf_tmux(dict)
let size = get(a:dict, 'tmux', '')
if empty(size)
+3 -3
View File
@@ -82,10 +82,10 @@ func buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int,
val = item.TrimLength()
case byPathname:
if validOffsetFound {
// Rune index, to be comparable with minBegin
lastDelim := -1
for i := numChars - 1; i >= 0; i-- {
if r := item.text.Get(i); r == '/' || r == '\\' {
s := item.text.ToString()
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' || s[i] == '\\' {
lastDelim = i
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)
}
-6
View File
@@ -29,9 +29,6 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
}
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")
items1 := [3][]*Item{{item1}, {item1}, nil}
items2 := [3][]*Item{
@@ -258,9 +255,6 @@ func TestQuoteEntry(t *testing.T) {
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`, `\`}
windowsStyle := 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("")
if util.IsWindows() {
-47
View File
@@ -26,7 +26,6 @@ const (
offsetPollTries = 10
queryTimeout = 500 * time.Millisecond
maxInputBuffer = 1024 * 1024
escapeLookback = 256
maxSelectTries = 100
)
@@ -339,45 +338,6 @@ func getEnv(name string, defaultValue int) int {
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) {
return r.getBytesInternal(cancellable, r.buffer, false)
}
@@ -418,13 +378,6 @@ func (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblo
retries = 0
}
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
// This should never happen under normal conditions,
-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)
}
}
}
-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
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):
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')
Log opts
AssertEqual 0.9, opts.window.width