mirror of
https://github.com/junegunn/fzf.git
synced 2026-09-13 22:52:01 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3900dd17e4 | ||
|
|
5e73c2ddd3 | ||
|
|
18e5009e0f |
@@ -3,15 +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)
|
||||
- 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
|
||||
- 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
|
||||
@@ -20,16 +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
|
||||
- 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
|
||||
------
|
||||
|
||||
+19
-73
@@ -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
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ triggered by a tag push.
|
||||
2. Verify file consistency, sign the tag, and push the tag.
|
||||
|
||||
```sh
|
||||
make tag VERSION=0.74.4
|
||||
make tag VERSION=0.74.3
|
||||
```
|
||||
|
||||
`make tag` runs `prerelease` first (checks that the version
|
||||
|
||||
+18
-56
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.74.4
|
||||
version=0.74.3
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
$version="0.74.4"
|
||||
$version="0.74.3"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
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
|
||||
fzf\-tmux - open fzf in tmux split pane
|
||||
|
||||
+1
-4
@@ -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
|
||||
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
|
||||
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
|
||||
.br
|
||||
* 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
|
||||
.BI "\-\-min\-height=" "HEIGHT[+]"
|
||||
|
||||
+12
-81
@@ -140,15 +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.
|
||||
" 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()
|
||||
\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
|
||||
\ : { 'down': '~40%' }
|
||||
@@ -174,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 = {}
|
||||
@@ -272,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')
|
||||
@@ -288,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
|
||||
|
||||
@@ -296,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
|
||||
@@ -318,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 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
|
||||
" --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
|
||||
\ && s:tmux_floating_pane_support()
|
||||
\ && s:compare_versions(s:get_version(s:fzf_binary()), '0.74.0') >= 0
|
||||
return s:tmux
|
||||
endif
|
||||
|
||||
@@ -479,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
|
||||
|
||||
@@ -658,44 +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 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)
|
||||
let size = get(a:dict, 'tmux', '')
|
||||
if empty(size)
|
||||
|
||||
@@ -124,8 +124,10 @@ function fzf_complete -w fzf -d 'fzf command completion and wildcard expansion s
|
||||
|
||||
# Determine the tabstop length for description alignment
|
||||
set -l -- max_columns (math $COLUMNS - 40)
|
||||
for len in (string match -r -- '^[^\\t]*(?=\\t)' $list[1..500] | string length -V)
|
||||
test "$len" -gt "$tabstop" -a "$len" -lt "$max_columns"
|
||||
for i in $list[1..500]
|
||||
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
|
||||
end
|
||||
set -- tabstop (math $tabstop + 4)
|
||||
|
||||
+3
-10
@@ -104,7 +104,7 @@ function fzf_key_bindings
|
||||
# Store current token in $dir as root for the 'find' command
|
||||
function fzf-file-widget -d "List files and folders"
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -lx dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
@@ -112,17 +112,10 @@ function fzf_key_bindings
|
||||
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
|
||||
"--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_COMMAND
|
||||
|
||||
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"
|
||||
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
|
||||
and commandline -rt -- (string join -- ' ' $prefix(string escape -n -- $result))' '
|
||||
|
||||
commandline -f repaint
|
||||
|
||||
+10
-34
@@ -47,20 +47,6 @@ def __fzfcmd []: nothing -> list<string> {
|
||||
['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 {
|
||||
$env.FZF_CTRL_T_OPTS = $env.FZF_CTRL_T_OPTS? | default ""
|
||||
@@ -69,11 +55,11 @@ export-env {
|
||||
}
|
||||
|
||||
# Directories
|
||||
let alt_c = {
|
||||
const alt_c = {
|
||||
name: fzf_dirs
|
||||
modifier: alt
|
||||
keycode: char_c
|
||||
mode: (__fzf_modes)
|
||||
mode: [emacs, vi_normal, vi_insert]
|
||||
event: [
|
||||
{
|
||||
send: executehostcommand
|
||||
@@ -96,11 +82,11 @@ let alt_c = {
|
||||
}
|
||||
|
||||
# History
|
||||
let ctrl_r = {
|
||||
const ctrl_r = {
|
||||
name: fzf_history
|
||||
modifier: control
|
||||
keycode: char_r
|
||||
mode: (__fzf_modes)
|
||||
mode: [emacs, vi_insert, vi_normal]
|
||||
event: [
|
||||
{
|
||||
send: executehostcommand
|
||||
@@ -129,16 +115,16 @@ let ctrl_r = {
|
||||
}
|
||||
|
||||
# Files
|
||||
let ctrl_t = {
|
||||
const ctrl_t = {
|
||||
name: fzf_files
|
||||
modifier: control
|
||||
keycode: char_t
|
||||
mode: (__fzf_modes)
|
||||
mode: [emacs, vi_normal, vi_insert]
|
||||
event: [
|
||||
{
|
||||
send: executehostcommand
|
||||
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 fzf_args = ($fzfcmd | skip 1);
|
||||
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 ' ';
|
||||
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
|
||||
# in a file name is not evaluated when the line is executed.
|
||||
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
|
||||
}
|
||||
let result = ($result | str replace --all (char newline) ' ' | str trim);
|
||||
commandline edit --append $result;
|
||||
commandline set-cursor --end
|
||||
"
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+4
-15
@@ -1547,14 +1547,6 @@ func (t *Terminal) visibleInputLinesInList() int {
|
||||
|
||||
// Extra number of lines needed to display fzf
|
||||
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
|
||||
if !t.inputless {
|
||||
extra++
|
||||
@@ -1570,16 +1562,16 @@ func (t *Terminal) extraLines() int {
|
||||
}
|
||||
if t.headerVisible {
|
||||
if t.hasHeaderWindow() {
|
||||
extra += sectionLines(t.headerBorderShape)
|
||||
extra += borderLines(t.headerBorderShape)
|
||||
}
|
||||
extra += len(t.header0)
|
||||
if w, shape := t.determineHeaderLinesShape(); w {
|
||||
extra += sectionLines(shape)
|
||||
extra += borderLines(shape)
|
||||
}
|
||||
extra += t.headerLines
|
||||
}
|
||||
if len(t.footer) > 0 {
|
||||
extra += sectionLines(t.footerBorderShape)
|
||||
extra += borderLines(t.footerBorderShape)
|
||||
extra += len(t.footer)
|
||||
}
|
||||
return extra
|
||||
@@ -2059,9 +2051,7 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
||||
prevIndex = t.targetIndex
|
||||
t.targetIndex = minItem.Index()
|
||||
}
|
||||
if result.final() {
|
||||
t.progress = 100
|
||||
}
|
||||
t.progress = 100
|
||||
t.merger = merger
|
||||
t.resultMerger = merger
|
||||
t.passMerger = result.passMerger
|
||||
@@ -8599,7 +8589,6 @@ func (t *Terminal) Loop() error {
|
||||
reload := changed || newCommand != nil
|
||||
if reload {
|
||||
t.wait.searching = true
|
||||
t.progress = 0
|
||||
}
|
||||
var reloadRequest *searchRequest
|
||||
if reload {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+2
-53
@@ -26,7 +26,7 @@ const (
|
||||
offsetPollTries = 10
|
||||
queryTimeout = 500 * time.Millisecond
|
||||
maxInputBuffer = 1024 * 1024
|
||||
escapeLookback = 256
|
||||
maxSelectTries = 100
|
||||
)
|
||||
|
||||
const DefaultTtyDevice string = "/dev/tty"
|
||||
@@ -175,7 +175,6 @@ type LightRenderer struct {
|
||||
width int
|
||||
height int
|
||||
yoffset int
|
||||
xoffset int
|
||||
tabstop int
|
||||
escDelay int
|
||||
fullscreen bool
|
||||
@@ -278,7 +277,6 @@ func (r *LightRenderer) Init() error {
|
||||
// increased and we're left with unwanted extra new line.
|
||||
if x > 0 && r.clearOnExit {
|
||||
r.upOneLine = true
|
||||
r.xoffset = x
|
||||
r.makeSpace()
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -419,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,
|
||||
@@ -1180,13 +1132,10 @@ func (r *LightRenderer) Close() {
|
||||
r.rmcup()
|
||||
} else {
|
||||
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 {
|
||||
r.csi("A")
|
||||
r.csi(fmt.Sprintf("%dG", r.xoffset+1))
|
||||
}
|
||||
r.csi("J")
|
||||
}
|
||||
} else if !r.fullscreen {
|
||||
r.stderr("\x1b8") // DECRC: restore cursor position
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
|
||||
}()
|
||||
|
||||
cancelFd := int(rpipe.Fd())
|
||||
for {
|
||||
for range maxSelectTries {
|
||||
var rfds unix.FdSet
|
||||
limit := len(rfds.Bits) * unix.NFDBITS
|
||||
if fd >= limit || cancelFd >= limit {
|
||||
@@ -251,8 +251,6 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
|
||||
rfds.Set(cancelFd)
|
||||
_, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, 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 {
|
||||
continue
|
||||
}
|
||||
@@ -267,6 +265,7 @@ func (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResu
|
||||
return getter()
|
||||
}
|
||||
}
|
||||
return 0, getCharError
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Size() TermSize {
|
||||
|
||||
@@ -882,28 +882,6 @@ class TestLayout < TestInteractive
|
||||
tmux.until { assert_block(block, it) }
|
||||
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
|
||||
tmux.send_keys %(seq 1| #{FZF} --style=full:rounded --height=~100% --header-lines=1 --info=default), :Enter
|
||||
block = <<~BLOCK
|
||||
|
||||
@@ -55,29 +55,6 @@ class TestServer < TestInteractive
|
||||
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
|
||||
uri = URI('http://localhost:6266')
|
||||
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
|
||||
|
||||
@@ -1273,43 +1273,6 @@ class TestNushell < TestBase
|
||||
FileUtils.rm_rf('/tmp/fzf-test')
|
||||
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
|
||||
# as bash/zsh/fish, so test_ctrl_r_multiline is omitted.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user