mirror of
https://github.com/junegunn/fzf.git
synced 2026-09-04 13:57:03 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31059ca248 |
@@ -1,23 +1,6 @@
|
|||||||
CHANGELOG
|
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
|
|
||||||
- The popup layout now works under Zellij
|
|
||||||
- Added `popup` as a synonym of the `tmux` layout key
|
|
||||||
```vim
|
|
||||||
let g:fzf_layout = { 'popup': '90%,70%' }
|
|
||||||
```
|
|
||||||
- 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
|
0.74.3
|
||||||
------
|
------
|
||||||
- Performance optimizations for non-ASCII input
|
- Performance optimizations for non-ASCII input
|
||||||
|
|||||||
+20
-76
@@ -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' }
|
||||||
@@ -332,7 +296,7 @@ The following table summarizes the available options.
|
|||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
| `popup`/`tmux` | string | (Layout) `--popup` options (e.g. `90%,70%`) |
|
| `tmux` | string | (Layout) `--tmux` options (e.g. `90%,70%`) |
|
||||||
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||||
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
||||||
|
|
||||||
@@ -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,16 @@ 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 tmux popup window (requires tmux 3.2
|
||||||
|
or above) by putting `--tmux` option value in `tmux` key.
|
||||||
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.
|
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
if exists('$TMUX') || exists('$ZELLIJ')
|
" See `--tmux` option in `man fzf` for available options
|
||||||
" See `--popup` option in `man fzf` for available options
|
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
if exists('$TMUX')
|
||||||
let g:fzf_layout = { 'popup': '90%,70%' }
|
let g:fzf_layout = { 'tmux': '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 +475,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
|
||||||
|
|||||||
+18
-57
@@ -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' }
|
||||||
@@ -327,7 +310,7 @@ The following table summarizes the available options.
|
|||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
`popup` / `tmux` | string | (Layout) `--popup` options (e.g. `90%,70%` )
|
`tmux` | string | (Layout) `--tmux` options (e.g. `90%,70%` )
|
||||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||||
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
@@ -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,15 @@ 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 tmux popup window (requires tmux 3.2
|
||||||
< Starting fzf in a tmux/Zellij popup window >________________________________~
|
or above) by putting `--tmux` options in `tmux` key.
|
||||||
*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.
|
|
||||||
>
|
>
|
||||||
if exists('$TMUX') || exists('$ZELLIJ')
|
" See `--tmux` option in `man fzf` for available options
|
||||||
" See `--popup` option in `man fzf` for available options
|
|
||||||
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
let g:fzf_layout = { 'popup': '90%,70%' }
|
if exists('$TMUX')
|
||||||
|
let g:fzf_layout = { 'tmux': '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 +484,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
|
||||||
|
|||||||
+22
-195
@@ -24,8 +24,7 @@
|
|||||||
if exists('g:loaded_fzf')
|
if exists('g:loaded_fzf')
|
||||||
finish
|
finish
|
||||||
endif
|
endif
|
||||||
" Bumped when the plugin gains behavior a caller may need to detect
|
let g:loaded_fzf = 1
|
||||||
let g:loaded_fzf = 20260821
|
|
||||||
|
|
||||||
let s:is_win = has('win32') || has('win64')
|
let s:is_win = has('win32') || has('win64')
|
||||||
if s:is_win && &shellslash
|
if s:is_win && &shellslash
|
||||||
@@ -128,7 +127,7 @@ function! s:fzf_tempname()
|
|||||||
return s:fzf_call('tempname')
|
return s:fzf_call('tempname')
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
let s:layout_keys = ['window', 'popup', 'tmux', 'up', 'down', 'left', 'right']
|
let s:layout_keys = ['window', 'tmux', 'up', 'down', 'left', 'right']
|
||||||
let s:fzf_go = s:base_dir.'/bin/fzf'
|
let s:fzf_go = s:base_dir.'/bin/fzf'
|
||||||
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
||||||
|
|
||||||
@@ -140,11 +139,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
|
|
||||||
if s:floating_pane()
|
|
||||||
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%' }
|
||||||
@@ -170,12 +164,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 = {}
|
||||||
@@ -267,24 +255,8 @@ function! fzf#exec(...)
|
|||||||
return s:exec
|
return s:exec
|
||||||
endfunction
|
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.
|
|
||||||
function! s:fzf_tmux_script()
|
|
||||||
if !executable(s:fzf_tmux)
|
|
||||||
if !executable('fzf-tmux')
|
|
||||||
return ''
|
|
||||||
endif
|
|
||||||
let s:fzf_tmux = 'fzf-tmux'
|
|
||||||
endif
|
|
||||||
return s:fzf_tmux
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:tmux_enabled()
|
function! s:tmux_enabled()
|
||||||
if has('gui_running')
|
if has('gui_running') || !exists('$TMUX')
|
||||||
return 0
|
|
||||||
endif
|
|
||||||
|
|
||||||
if empty($TMUX) && empty($ZELLIJ)
|
|
||||||
return 0
|
return 0
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -292,42 +264,17 @@ function! s:tmux_enabled()
|
|||||||
return s:tmux
|
return s:tmux
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let [s:tmux, s:tmux_floating] = [0, 0]
|
let s:tmux = 0
|
||||||
|
if !executable(s:fzf_tmux)
|
||||||
" --popup covers Zellij as well, where the fzf-tmux script and the tmux
|
if executable('fzf-tmux')
|
||||||
" version are irrelevant. fzf learned it in 0.71.0, and the floating pane
|
let s:fzf_tmux = 'fzf-tmux'
|
||||||
" options it passes need Zellij 0.44 or above. fzf checks tmux first, so
|
else
|
||||||
" this branch is Zellij without tmux. Both non-empty means tmux wins.
|
return 0
|
||||||
" empty(), not exists(), to match how fzf reads the two variables
|
endif
|
||||||
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
|
endif
|
||||||
|
|
||||||
let output = system('tmux -V')
|
let output = system('tmux -V')
|
||||||
if v:shell_error
|
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
|
||||||
return s:tmux
|
|
||||||
endif
|
|
||||||
" 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
|
|
||||||
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_info()
|
|
||||||
\ && s:compare_versions(s:get_version(s:fzf_binary()), '0.74.0') >= 0
|
|
||||||
return s:tmux
|
|
||||||
endif
|
|
||||||
|
|
||||||
" Older versions still go through the script
|
|
||||||
let s:tmux = !empty(s:fzf_tmux_script()) && s:compare_versions(ver, '1.7') >= 0
|
|
||||||
return s:tmux
|
return s:tmux
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -475,10 +422,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
|
||||||
|
|
||||||
@@ -558,11 +502,6 @@ try
|
|||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
|
|
||||||
let dict = exists('a:1') ? copy(a:1) : {}
|
let dict = exists('a:1') ? copy(a:1) : {}
|
||||||
" 'popup' and 'tmux' are synonyms, as --popup and --tmux are. Normalize here
|
|
||||||
" so that the rest of the function only has to know about one of them.
|
|
||||||
if has_key(dict, 'popup')
|
|
||||||
let dict.tmux = remove(dict, 'popup')
|
|
||||||
endif
|
|
||||||
let temps = { 'result': s:fzf_tempname() }
|
let temps = { 'result': s:fzf_tempname() }
|
||||||
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
||||||
try
|
try
|
||||||
@@ -600,19 +539,16 @@ try
|
|||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||||
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||||
let use_term = (has_nvim_term || has_vim8_term) && !s:need_cmd_window
|
let use_term = has_nvim_term || has_vim8_term
|
||||||
|
\ && !s:need_cmd_window
|
||||||
|
\ && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||||
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
||||||
if prefer_tmux && use_tmux
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
let use_term = 0
|
let use_term = 0
|
||||||
endif
|
endif
|
||||||
if use_term
|
if use_term
|
||||||
let optstr .= ' --no-height'
|
let optstr .= ' --no-height --no-tmux'
|
||||||
" Cancel a --popup from $FZF_DEFAULT_OPTS only when the spec asks for a Vim
|
|
||||||
" window. Without a layout option, respect the user's preference.
|
|
||||||
if s:present(dict, 'window', 'up', 'down', 'left', 'right')
|
|
||||||
let optstr .= ' --no-tmux'
|
|
||||||
endif
|
|
||||||
elseif use_height
|
elseif use_height
|
||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --no-tmux --height='.height
|
let optstr .= ' --no-tmux --height='.height
|
||||||
@@ -632,12 +568,8 @@ try
|
|||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" s:execute_tmux may run fzf asynchronously, so it calls s:callback itself
|
let lines = use_tmux ? s:execute_tmux(dict, command, temps)
|
||||||
if use_tmux
|
\ : s:execute(dict, command, use_height, temps)
|
||||||
return s:execute_tmux(dict, command, temps)
|
|
||||||
endif
|
|
||||||
|
|
||||||
let lines = s:execute(dict, command, use_height, temps)
|
|
||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
@@ -654,57 +586,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
|
|
||||||
|
|
||||||
" fzf decides on the running 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
|
|
||||||
function! s:tmux_floating_pane_info()
|
|
||||||
" 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 display-message -p -t '.fzf#shellescape($TMUX_PANE)
|
|
||||||
\ .' ''#{window_width} #{window_height}'' '';'' list-commands new-pane')
|
|
||||||
" A server that does not know the command exits normally with no output
|
|
||||||
if v:shell_error || out !~# 'new-pane'
|
|
||||||
return 0
|
|
||||||
endif
|
|
||||||
" Too small for a floating pane of the minimum size
|
|
||||||
return str2nr(matchstr(out, '^\s*\zs\d\+')) >= 3
|
|
||||||
\ && str2nr(matchstr(out, '^\s*\d\+\s\+\zs\d\+')) >= 3
|
|
||||||
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)
|
function! s:fzf_tmux(dict)
|
||||||
let size = get(a:dict, 'tmux', '')
|
let size = get(a:dict, 'tmux', '')
|
||||||
if empty(size)
|
if empty(size)
|
||||||
@@ -716,15 +597,10 @@ function! s:fzf_tmux(dict)
|
|||||||
endfor
|
endfor
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" Legacy fzf-tmux options are flags. A --tmux value never starts with a dash,
|
" Legacy fzf-tmux options
|
||||||
" but may contain one, as in '90%,60%,border-native'
|
if size =~ '-'
|
||||||
if size =~ '^-'
|
|
||||||
let script = s:fzf_tmux_script()
|
|
||||||
if empty(script)
|
|
||||||
throw 'fzf-tmux not found, required for the legacy option: ' . size
|
|
||||||
endif
|
|
||||||
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||||
\ &lines, &columns, fzf#shellescape(script), size, (has_key(a:dict, 'source') ? '' : '-'))
|
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||||
end
|
end
|
||||||
|
|
||||||
" Using native --tmux option
|
" Using native --tmux option
|
||||||
@@ -866,42 +742,6 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Returns 0 if the job could not be started
|
|
||||||
function! s:start_popup_job(dict, command, temps) abort
|
|
||||||
let fzf = { 'dict': a:dict, 'temps': a:temps, 'command': a:command }
|
|
||||||
" Vim passes (job, status) and Nvim (id, status, event)
|
|
||||||
function! fzf.on_exit(id, code, ...) abort
|
|
||||||
redraw!
|
|
||||||
let lines = s:collect(self.temps)
|
|
||||||
if s:exit_handler(self.dict, a:code, self.command, 1) >= 2
|
|
||||||
return
|
|
||||||
endif
|
|
||||||
call s:pushd(self.dict)
|
|
||||||
call s:callback(self.dict, lines)
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
if has('nvim')
|
|
||||||
return jobstart([&shell, &shellcmdflag, a:command], fzf) > 0
|
|
||||||
endif
|
|
||||||
|
|
||||||
" The command redirects to the result file, so no stream is of interest
|
|
||||||
let opts = { 'exit_cb': function(fzf.on_exit),
|
|
||||||
\ 'in_io': 'null', 'out_io': 'null', 'err_io': 'null' }
|
|
||||||
|
|
||||||
" job_start() gives the child $TERM=dumb, and the popup inherits the
|
|
||||||
" environment from here, so fzf would lose its colors. 'env' was added in
|
|
||||||
" 8.0.902, so set it in the command itself on older versions.
|
|
||||||
let command = a:command
|
|
||||||
if has('patch-8.0.902')
|
|
||||||
let opts.env = { 'TERM': $TERM }
|
|
||||||
elseif !s:is_win
|
|
||||||
let command = join(['export TERM=' . fzf#shellescape($TERM) . ';', command])
|
|
||||||
endif
|
|
||||||
|
|
||||||
let job = job_start([&shell, &shellcmdflag, command], opts)
|
|
||||||
return job_status(job) !=# 'fail'
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:execute_tmux(dict, command, temps) abort
|
function! s:execute_tmux(dict, command, temps) abort
|
||||||
let command = a:command
|
let command = a:command
|
||||||
let cwd = s:pushd(a:dict)
|
let cwd = s:pushd(a:dict)
|
||||||
@@ -910,24 +750,11 @@ function! s:execute_tmux(dict, command, temps) abort
|
|||||||
let command = join(['cd', fzf#shellescape(cwd), '&&', command])
|
let command = join(['cd', fzf#shellescape(cwd), '&&', command])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" fzf draws in a pane of its own, so the process we start here only waits for
|
|
||||||
" it. Hold it with a job instead of system() so that Vim keeps processing its
|
|
||||||
" event loop, and nothing has to be displayed for it. Fall back to the
|
|
||||||
" blocking path when the job cannot be started, or the sink would never run
|
|
||||||
" and the temporary files would be left behind.
|
|
||||||
if (has('nvim') || has('job')) && s:start_popup_job(a:dict, command, a:temps)
|
|
||||||
" Restore the working directory while fzf runs. on_exit pushes it again
|
|
||||||
call s:dopopd()
|
|
||||||
return []
|
|
||||||
endif
|
|
||||||
|
|
||||||
call system(command)
|
call system(command)
|
||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
let lines = s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
call s:callback(a:dict, lines)
|
|
||||||
return lines
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:calc_size(max, val, dict)
|
function! s:calc_size(max, val, dict)
|
||||||
|
|||||||
+3
-3
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const (
|
|||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
queryTimeout = 500 * time.Millisecond
|
queryTimeout = 500 * time.Millisecond
|
||||||
maxInputBuffer = 1024 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
escapeLookback = 256
|
|
||||||
maxSelectTries = 100
|
maxSelectTries = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -339,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)
|
||||||
}
|
}
|
||||||
@@ -418,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,
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-92
@@ -4,44 +4,15 @@ Execute (Setup):
|
|||||||
Log 'Test directory: ' . g:dir
|
Log 'Test directory: ' . g:dir
|
||||||
Save &acd
|
Save &acd
|
||||||
|
|
||||||
" fzf#run runs fzf in a terminal buffer and returns immediately, so the
|
|
||||||
" assertions have to wait for the run to finish. 'exit' is called from the
|
|
||||||
" same handler that runs the sink, just before it, so once it has fired the
|
|
||||||
" sink has run too. It also fires when nothing is selected, unlike the sink.
|
|
||||||
function! g:FzfExit(code)
|
|
||||||
let g:fzf_done = 1
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
" Collects the output without displacing a 'sink' the spec already has.
|
|
||||||
" s:callback runs both
|
|
||||||
function! g:FzfCollect(lines)
|
|
||||||
call extend(g:fzf_lines, a:lines)
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! g:FzfRun(spec) abort
|
|
||||||
let g:fzf_done = 0
|
|
||||||
let g:fzf_lines = []
|
|
||||||
call fzf#run(extend(copy(a:spec),
|
|
||||||
\ { 'exit': function('g:FzfExit'), 'sinklist': function('g:FzfCollect') }))
|
|
||||||
let started = reltime()
|
|
||||||
while !g:fzf_done && reltimefloat(reltime(started)) < 10
|
|
||||||
sleep 10m
|
|
||||||
endwhile
|
|
||||||
if !g:fzf_done
|
|
||||||
throw 'Timed out waiting for fzf#run'
|
|
||||||
endif
|
|
||||||
return g:fzf_lines
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
Execute (fzf#run with dir option):
|
Execute (fzf#run with dir option):
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
let result = g:FzfRun({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
|
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
|
||||||
AssertEqual ['fzf.vader'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
AssertEqual 0, haslocaldir()
|
AssertEqual 0, haslocaldir()
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
|
|
||||||
execute 'lcd' fnameescape(cwd)
|
execute 'lcd' fnameescape(cwd)
|
||||||
let result = sort(g:FzfRun({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
AssertEqual 1, haslocaldir()
|
AssertEqual 1, haslocaldir()
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
@@ -51,32 +22,32 @@ Execute (fzf#run with Funcref command):
|
|||||||
function! g:FzfTest(e)
|
function! g:FzfTest(e)
|
||||||
call add(g:ret, a:e)
|
call add(g:ret, a:e)
|
||||||
endfunction
|
endfunction
|
||||||
let result = sort(g:FzfRun({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
AssertEqual ['fzf.vader'], sort(g:ret)
|
AssertEqual ['fzf.vader'], sort(g:ret)
|
||||||
|
|
||||||
Execute (fzf#run with string source):
|
Execute (fzf#run with string source):
|
||||||
let result = sort(g:FzfRun({ 'source': 'echo hi', 'options': '-f i' }))
|
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
||||||
AssertEqual ['hi'], result
|
AssertEqual ['hi'], result
|
||||||
|
|
||||||
Execute (fzf#run with list source):
|
Execute (fzf#run with list source):
|
||||||
let result = sort(g:FzfRun({ 'source': ['hello', 'world'], 'options': '-f e' }))
|
let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f e' }))
|
||||||
AssertEqual ['hello'], result
|
AssertEqual ['hello'], result
|
||||||
let result = sort(g:FzfRun({ 'source': ['hello', 'world'], 'options': '-f o' }))
|
let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f o' }))
|
||||||
AssertEqual ['hello', 'world'], result
|
AssertEqual ['hello', 'world'], result
|
||||||
|
|
||||||
Execute (fzf#run with string source):
|
Execute (fzf#run with string source):
|
||||||
let result = sort(g:FzfRun({ 'source': 'echo hi', 'options': '-f i' }))
|
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
||||||
AssertEqual ['hi'], result
|
AssertEqual ['hi'], result
|
||||||
|
|
||||||
Execute (fzf#run with dir option and noautochdir):
|
Execute (fzf#run with dir option and noautochdir):
|
||||||
set noacd
|
set noacd
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
call g:FzfRun({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||||
" No change in working directory
|
" No change in working directory
|
||||||
AssertEqual cwd, getcwd()
|
AssertEqual cwd, getcwd()
|
||||||
|
|
||||||
call g:FzfRun({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})
|
call fzf#run({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})
|
||||||
AssertEqual cwd, getcwd()
|
AssertEqual cwd, getcwd()
|
||||||
tabclose
|
tabclose
|
||||||
AssertEqual cwd, getcwd()
|
AssertEqual cwd, getcwd()
|
||||||
@@ -84,13 +55,13 @@ Execute (fzf#run with dir option and noautochdir):
|
|||||||
Execute (Incomplete fzf#run with dir option and autochdir):
|
Execute (Incomplete fzf#run with dir option and autochdir):
|
||||||
set acd
|
set acd
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
call g:FzfRun({'source': [], 'sink': 'e', 'dir': '/tmp', 'options': '-0'})
|
call fzf#run({'source': [], 'sink': 'e', 'dir': '/tmp', 'options': '-0'})
|
||||||
" No change in working directory even if &acd is set
|
" No change in working directory even if &acd is set
|
||||||
AssertEqual cwd, getcwd()
|
AssertEqual cwd, getcwd()
|
||||||
|
|
||||||
Execute (FIXME: fzf#run with dir option and autochdir):
|
Execute (FIXME: fzf#run with dir option and autochdir):
|
||||||
set acd
|
set acd
|
||||||
call g:FzfRun({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||||
" Working directory changed due to &acd
|
" Working directory changed due to &acd
|
||||||
AssertEqual '/foobar', expand('%')
|
AssertEqual '/foobar', expand('%')
|
||||||
AssertEqual '/', getcwd()
|
AssertEqual '/', getcwd()
|
||||||
@@ -98,40 +69,13 @@ Execute (FIXME: fzf#run with dir option and autochdir):
|
|||||||
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
||||||
set acd
|
set acd
|
||||||
cd /tmp
|
cd /tmp
|
||||||
call g:FzfRun({'source': ['/foobar'], 'sink': 'e', 'dir': '/', 'options': '-1'})
|
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/', 'options': '-1'})
|
||||||
" 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
|
||||||
@@ -206,30 +150,6 @@ Execute (fzf#wrap):
|
|||||||
let opts = fzf#wrap({})
|
let opts = fzf#wrap({})
|
||||||
Assert opts.options =~ '--color=fg:'
|
Assert opts.options =~ '--color=fg:'
|
||||||
|
|
||||||
Execute (popup is a synonym of tmux):
|
|
||||||
unlet! g:fzf_layout
|
|
||||||
|
|
||||||
" Treated as a layout option, so g:fzf_layout is not applied on top
|
|
||||||
let opts = fzf#wrap('foobar', {'popup': '90%,70%'})
|
|
||||||
AssertEqual '90%,70%', opts.popup
|
|
||||||
Assert !has_key(opts, 'window')
|
|
||||||
|
|
||||||
" And stripped in fullscreen, like the others
|
|
||||||
let opts = fzf#wrap('foobar', {'popup': '90%,70%'}, 1)
|
|
||||||
Assert !has_key(opts, 'popup')
|
|
||||||
|
|
||||||
" Accepted in g:fzf_layout
|
|
||||||
let g:fzf_layout = {'popup': '90%,70%'}
|
|
||||||
let opts = fzf#wrap('foobar')
|
|
||||||
AssertEqual '90%,70%', opts.popup
|
|
||||||
unlet g:fzf_layout
|
|
||||||
|
|
||||||
" Listed as a valid key
|
|
||||||
let g:fzf_layout = {'bogus': 1}
|
|
||||||
AssertThrows call fzf#wrap('foobar')
|
|
||||||
Assert g:vader_exception =~ 'popup'
|
|
||||||
unlet g:fzf_layout
|
|
||||||
|
|
||||||
Execute (fzf#shellescape with sh):
|
Execute (fzf#shellescape with sh):
|
||||||
AssertEqual '''''', fzf#shellescape('', 'sh')
|
AssertEqual '''''', fzf#shellescape('', 'sh')
|
||||||
AssertEqual '''\''', fzf#shellescape('\', 'sh')
|
AssertEqual '''\''', fzf#shellescape('\', 'sh')
|
||||||
|
|||||||
Reference in New Issue
Block a user