m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-13 13:53:47 -05:00

Compare commits

...

30 Commits

Author SHA1 Message Date
Junegunn Choi
30577b0c17 0.20.0 2019-12-18 01:07:25 +09:00
Junegunn Choi
212de25409 Fix incorrect header array mutation 2019-12-16 18:47:05 +09:00
Jan Edmund Lazo
5da8bbf45a [vim] Encode list source to codepage (#1794) 2019-12-16 14:41:03 +09:00
Jan Edmund Lazo
aa0e10ead7 [vim] Use cterm colors on Windows (#1793)
Truecolor does not work on default Windows terminal.
It is a problem in neovim GUIs.

https://github.com/sainnhe/edge/issues/5#issuecomment-565748240
2019-12-15 22:17:24 +09:00
msr1k
a9906c7c29 Add MSYS2 support as a vim plugin (#1677)
* Add MSYS2 support as a vim plugin

Add &shellcmdflag and TERM environment variable treatment.

- Make &shellcmdflag `/C` when &shell turns into `cmd.exe`
- Delete %TERM% environment variable before fzf execution

* Change shellescape default value depending on s:is_win flag

* Make TERM environment empty only when gui is running

* Stop checking &shell in fzf#shellescape function

This funcion's behavior is controlled by only if it is Windows or not.
So there is no need to check &shell.

* Take neovim into consideration when to set shellcmdflag

* Add &shellxquote control
2019-12-15 18:25:58 +09:00
Junegunn Choi
9fefe08b3f Revert README as preview-{fg,bg} are only available on master 2019-12-13 12:46:27 +09:00
Junegunn Choi
684bfff713 Update README/CHANGELOG 2019-12-13 12:43:34 +09:00
Junegunn Choi
3db6b88d82 Add preview-fg and preview-bg for --color
Close #1776
2019-12-12 23:03:17 +09:00
Junegunn Choi
8ae96774df Gutter color of 16-color theme should be undefined by default 2019-12-12 22:53:28 +09:00
Junegunn Choi
f68017d21e [windows/vim] Encode batchfile in current codepage
Backport https://github.com/junegunn/vim-plug/pull/913
2019-12-12 12:29:14 +09:00
Junegunn Choi
2b725a4db5 Defer resetting multi-selection on reload 2019-12-09 21:32:58 +09:00
Junegunn Choi
af1a5f130b Add clear-query and clear-selection
Close #1787
Related #1364
2019-12-07 14:44:24 +09:00
Junegunn Choi
86e3994e87 Properly clear list when --header-lines not filled on reload 2019-12-06 22:34:45 +09:00
Junegunn Choi
1e6ac5590e 'reload' action should be allowed even where there's no match
If the command template doesn't have any placeholder expressions.

    : | fzf --bind 'space:reload:seq 10'
2019-12-06 22:34:30 +09:00
Henré Botha
5e42b1c9f8 [ssh completion] Skip only aliases matching * (#1788)
This commit fixes a bug where lines that declare multiple hostnames get
omitted from completion entirely if one of the hostnames matches *. For
example:

	Host foo.com bar.dev baz.*
2019-12-06 17:58:53 +09:00
Junegunn Choi
9d842630c9 Mention _fzf_setup_completion helper function for bash 2019-12-06 12:03:34 +09:00
David Gray
77cb906dfe [completion] Add support for HostName lines in ~/.ssh/config (#1785)
Close #1783
2019-12-06 10:14:15 +09:00
Junegunn Choi
a59e846f74 Update installation instruction
Close #1707
Close #1779
2019-12-05 23:10:41 +09:00
infokiller
6e6340a0c9 Ignore zcompile output files (*.zwc files) (#1775) 2019-12-05 22:27:30 +09:00
John Purnell
357e82e51b [completion] Ignore hg repos (#1777)
* Update completion.bash
* Update completion.zsh
2019-12-05 22:27:04 +09:00
Junegunn Choi
394d8cfd18 Remove immediate flickering on reload action 2019-12-05 22:27:18 +09:00
Junegunn Choi
ef80bd401f Update installation instruction using Linux package managers
Added NixOS instruction. Close #1731
2019-12-01 23:48:48 +09:00
midchildan
f51d61d57a [zsh] Prevent the current directory from appearing as ~dir in prompts (#1774)
The zsh version of the cd widget sets the variable `dir` to the path of
the target directory before invoking `cd`. This causes zsh to treat the
target directory as a named directory, which has the effect of zsh
substituting '%~' with '~dir' instead of the proper path when it
performs prompt expansion.

This commit will cause the widget to unset `dir` before redrawing the
prompt to fix this issue.

Details of zsh prompt expansion can be found in:
http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html
2019-12-01 23:20:26 +09:00
Junegunn Choi
1dd256a68a Update README-VIM 2019-11-29 18:32:49 +09:00
Junegunn Choi
85644aa3fb Revamp README-VIM.md 2019-11-23 01:54:52 +09:00
Junegunn Choi
effbc258bb Update CHANGELOG 2019-11-21 23:06:14 +09:00
Junegunn Choi
e615600ff1 Allow action composition over multiple --bind
# Note + prefix in the second bind expression
  fzf --bind u:up --bind u:+up
  fzf --bind u:up+up
2019-11-21 23:06:14 +09:00
Junegunn Choi
60465c4664 Fix parse error of --bind expression 2019-11-21 23:06:13 +09:00
Jan Edmund Lazo
c03c058bd5 [install] Support busybox uname on Windows (#1758) 2019-11-16 22:23:42 +09:00
Junegunn Choi
7238c8944d Update CHANGELOG 2019-11-16 01:23:01 +09:00
24 changed files with 696 additions and 407 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ Gemfile.lock
doc/tags doc/tags
vendor vendor
gopath gopath
*.zwc

View File

@@ -1,9 +1,46 @@
CHANGELOG CHANGELOG
========= =========
0.20.0
------
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)
```sh
fzf --preview 'cat {}' \
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899' \
--border --height 20 --layout reverse --info inline
```
- Removed the immediate flicking of the screen on `reload` action.
```sh
: | fzf --bind 'change:reload:seq {q}' --phony
```
- Added `clear-query` and `clear-selection` actions for `--bind`
- It is now possible to split a composite bind action over multiple `--bind`
expressions by prefixing the later ones with `+`.
```sh
fzf --bind 'ctrl-a:up+up'
# Can be now written as
fzf --bind 'ctrl-a:up' --bind 'ctrl-a:+up'
# This is useful when you need to write special execute/reload form (i.e. `execute:...`)
# to avoid parse errors and add more actions to the same key
fzf --multi --bind 'ctrl-l:select-all+execute:less {+f}' --bind 'ctrl-l:+deselect-all'
```
- Fixed parse error of `--bind` expression where concatenated execute/reload
action contains `+` character.
```sh
fzf --multi --bind 'ctrl-l:select-all+execute(less {+f})+deselect-all'
```
- Fixed bugs of reload action
- Not triggered when there's no match even when the command doesn't have
any placeholder expressions
- Screen not properly cleared when `--header-lines` not filled on reload
0.19.0 0.19.0
------ ------
- Added `--phony` option which completely disables search functionality.
Useful when you want to use fzf only as a selector interface. See below.
- Added "reload" action for dynamically updating the input list without - Added "reload" action for dynamically updating the input list without
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
more about it. more about it.
@@ -11,7 +48,7 @@ CHANGELOG
# Using fzf as the selector interface for ripgrep # Using fzf as the selector interface for ripgrep
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="foo" INITIAL_QUERY="foo"
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \ FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY' || true" \
fzf --bind "change:reload:$RG_PREFIX {q} || true" \ fzf --bind "change:reload:$RG_PREFIX {q} || true" \
--ansi --phony --query "$INITIAL_QUERY" --ansi --phony --query "$INITIAL_QUERY"
``` ```

View File

@@ -1,16 +1,33 @@
FZF Vim integration FZF Vim integration
=================== ===================
This repository only enables basic integration with Vim. If you're looking for Summary
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project. -------
(Note: To use fzf in GVim, an external terminal emulator is required.) The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. **`fzf#run([spec dict])`**
- Starts fzf inside Vim with the given spec
- `:call fzf#run({'source': 'ls'})`
2. **`fzf#wrap([spec dict]) -> (dict)`**
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echo fzf#wrap({'source': 'ls'})`
- We usually *wrap* a spec with `fzf#wrap` before passing it to `fzf#run`
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
3. **`:FZF [fzf_options string] [path string]`**
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript
to implement custom commands
- If you're looking for more such commands, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
The most important of all is `fzf#run`, but it would be easier to understand
the whole if we start off with `:FZF` command.
`:FZF[!]` `:FZF[!]`
--------- ---------
If you have set up fzf for Vim, `:FZF` command will be added.
```vim ```vim
" Look for files under current directory " Look for files under current directory
:FZF :FZF
@@ -18,8 +35,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
" Look for files under your home directory " Look for files under your home directory
:FZF ~ :FZF ~
" With options " With fzf command-line options
:FZF --no-sort --reverse --inline-info /tmp :FZF --reverse --info=inline /tmp
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
@@ -75,6 +92,7 @@ let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
" Customize fzf colors to match your color scheme " Customize fzf colors to match your color scheme
" - fzf#wrap translates this to a set of `--color` options
let g:fzf_colors = let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'], \ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'], \ 'bg': ['bg', 'Normal'],
@@ -90,69 +108,62 @@ let g:fzf_colors =
\ 'spinner': ['fg', 'Label'], \ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] } \ 'header': ['fg', 'Comment'] }
" Enable per-command history. " Enable per-command history
" CTRL-N and CTRL-P will be automatically bound to next-history and " - History files will be stored in the specified directory
" previous-history instead of down and up. If you don't like the change, " - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS. " 'previous-history' instead of 'down' and 'up'.
let g:fzf_history_dir = '~/.local/share/fzf-history' let g:fzf_history_dir = '~/.local/share/fzf-history'
``` ```
`fzf#run` `fzf#run`
--------- ---------
For more advanced uses, you can use `fzf#run([options])` function.
`fzf#run()` function is the core of Vim integration. It takes a single `fzf#run()` function is the core of Vim integration. It takes a single
dictionary argument. At the very least, specify `sink` option to tell what it dictionary argument, *a spec*, and starts fzf process accordingly. At the very
should do with the selected entry. least, specify `sink` option to tell what it should do with the selected
entry.
```vim ```vim
call fzf#run({'sink': 'e'}) call fzf#run({'sink': 'e'})
``` ```
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if We haven't specified the `source`, so this is equivalent to starting fzf on
defined) to list the files under the current directory. When you select one, command line without standard input pipe; fzf will use find command (or
it will open it with `:e` command. If you want to open it in a new tab, you `$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
can pass `:tabedit` command instead as the sink. directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
```vim ```vim
call fzf#run({'sink': 'tabedit'}) call fzf#run({'sink': 'tabedit'})
``` ```
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
you can change its bottom-up layout with `--reverse` option. Such options can
be specified as `options`.
```vim
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
```
Instead of using the default find command, you can use any shell command as Instead of using the default find command, you can use any shell command as
the source. This will list the files managed by git. the source. The following example will list the files managed by git. It's
equivalent to running `git ls-files | fzf` on shell.
```vim ```vim
call fzf#run({'source': 'git ls-files', 'sink': 'e'}) call fzf#run({'source': 'git ls-files', 'sink': 'e'})
``` ```
Pass a layout option if you don't want fzf window to take up the entire screen. fzf options can be specified as `options` entry in spec dictionary.
```vim
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
```
You can also pass a layout option if you don't want fzf window to take up the
entire screen.
```vim ```vim
" up / down / left / right / window are allowed " up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
``` ```
`source` doesn't have to be an external shell command, you can pass a Vim `source` doesn't have to be an external shell command, you can pass a Vim
array as the source. In the following example, we use the names of the open array as the source. In the next example, we pass the names of color
buffers as the source. schemes as the source to implement a color scheme selector.
```vim
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
\ 'bufname(v:val)'),
\ 'sink': 'e', 'down': '30%'})
```
Or the names of color schemes.
```vim ```vim
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')), call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
@@ -160,7 +171,7 @@ call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
\ 'sink': 'colo', 'left': '25%'}) \ 'sink': 'colo', 'left': '25%'})
``` ```
The following table shows the available options. The following table summarizes the available options.
| Option name | Type | Description | | Option name | Type | Description |
| -------------------------- | ------------- | ---------------------------------------------------------------- | | -------------------------- | ------------- | ---------------------------------------------------------------- |
@@ -171,14 +182,11 @@ The following table shows the available options.
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once | | `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
| `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 | Use tmux pane with the given size (e.g. `20`, `50%`) | | `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
| `window` (Vim 8 / Neovim) | string | 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`) |
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
`options` entry can be either a string or a list. For simple cases, string `options` entry can be either a string or a list. For simple cases, string
should suffice, but prefer to use list type if you're concerned about escaping should suffice, but prefer to use list type to avoid escaping issues.
issues on different platforms.
```vim ```vim
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'}) call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
@@ -188,57 +196,126 @@ call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
`fzf#wrap` `fzf#wrap`
---------- ----------
`:FZF` command provided by default knows how to handle `CTRL-T`, `CTRL-X`, and We have seen that several aspects of `:FZF` command can be configured with
`CTRL-V` and opens the selected file in a new tab, in a horizontal split, or a set of global option variables; different ways to open files
in a vertical split respectively. And these key bindings can be configured via (`g:fzf_action`), window position and size (`g:fzf_layout`), color palette
`g:fzf_action`. This is implemented using `--expect` option of fzf and the (`g:fzf_colors`), etc.
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
options.
By *"wrapping"* your options dictionary with `fzf#wrap` before passing it to So how can we make our custom `fzf#run` calls also respect those variables?
`fzf#run`, you can make your command also support the options. Simply by *"wrapping"* the spec dictionary with `fzf#wrap` before passing it
to `fzf#run`.
- **`fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`**
- All arguments are optional. Usually we only need to pass a spec dictionary.
- `name` is for managing history files. It is ignored if
`g:fzf_history_dir` is not defined.
- `fullscreen` can be either `0` or `1` (default: 0).
`fzf#wrap` takes a spec and returns an extended version of it (also
a dictionary) with additional options for addressing global preferences. You
can examine the return value of it like so:
```vim ```vim
" Usage: echo fzf#wrap({'source': 'ls'})
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
" and opens fzf according to g:fzf_layout setting.
command! Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
" This extends the above example to open fzf in fullscreen
" when the command is run with ! suffix (Buffers!)
command! -bang Buffers call fzf#run(fzf#wrap(
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
" You can optionally pass the name of the command as the first argument to
" fzf#wrap to make it work with g:fzf_history_dir
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
``` ```
fzf inside terminal buffer After we *"wrap"* our spec, we pass it to `fzf#run`.
--------------------------
```vim
call fzf#run(fzf#wrap({'source': 'ls'}))
```
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf
window according to `g:fzf_layout` setting.
To make it easier to use, let's define `LS` command.
```vim
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
```
Type `:LS` and see how it works.
We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
`:FZF!`. Add `-bang` to command definition, and use `<bang>` value to set
the last `fullscreen` argument of `fzf#wrap` (see `:help <bang>`).
```vim
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
```
Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS /tmp` is possible.
```vim
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
```
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign
a unique name to our command and pass it as the first argument to `fzf#wrap`.
```vim
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
```
Tips
----
### fzf inside terminal buffer
The latest versions of Vim and Neovim include builtin terminal emulator The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases: (`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim - On Neovim
- On GVim - On GVim
- On Terminal Vim with the non-default layout - On Terminal Vim with a non-default layout
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}` - `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
### Hide statusline #### Starting fzf in Neovim floating window
When fzf starts in a terminal buffer, you may want to hide the statusline of
the containing buffer.
```vim ```vim
autocmd! FileType fzf " Using floating windows of Neovim to start fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler if has('nvim')
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2'
function! FloatingFZF()
let width = float2nr(&columns * 0.9)
let height = float2nr(&lines * 0.6)
let opts = { 'relative': 'editor',
\ 'row': (&lines - height) / 2,
\ 'col': (&columns - width) / 2,
\ 'width': width,
\ 'height': height }
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal')
endfunction
let g:fzf_layout = { 'window': 'call FloatingFZF()' }
endif
```
#### Hide statusline
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.
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
might want to temporarily disable the statusline for a cleaner look.
```vim
if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler \| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
``` ```
[License](LICENSE) [License](LICENSE)
@@ -246,4 +323,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi Copyright (c) 2019 Junegunn Choi

203
README.md
View File

@@ -25,13 +25,9 @@ Table of Contents
* [Installation](#installation) * [Installation](#installation)
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew) * [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git) * [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin) * [Using Linux package managers](#using-linux-package-managers)
* [Arch Linux](#arch-linux)
* [Debian](#debian)
* [Fedora](#fedora)
* [openSUSE](#opensuse)
* [FreeBSD](#freebsd)
* [Windows](#windows) * [Windows](#windows)
* [As Vim plugin](#as-vim-plugin)
* [Upgrading fzf](#upgrading-fzf) * [Upgrading fzf](#upgrading-fzf)
* [Building fzf](#building-fzf) * [Building fzf](#building-fzf)
* [Usage](#usage) * [Usage](#usage)
@@ -58,7 +54,6 @@ Table of Contents
* [Preview window](#preview-window) * [Preview window](#preview-window)
* [Tips](#tips) * [Tips](#tips)
* [Respecting .gitignore](#respecting-gitignore) * [Respecting .gitignore](#respecting-gitignore)
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
* [Fish shell](#fish-shell) * [Fish shell](#fish-shell)
* [Related projects](#related-projects) * [Related projects](#related-projects)
* [<a href="LICENSE">License</a>](#license) * [<a href="LICENSE">License</a>](#license)
@@ -106,10 +101,45 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install ~/.fzf/install
``` ```
### Using Linux package managers
| Distro | Command |
| --- | --- |
| Alpine Linux | `sudo apk add fzf` |
| Arch Linux | `sudo pacman -S fzf` |
| Debian | `sudo apt-get install fzf` |
| Fedora | `sudo dnf install fzf` |
| FreeBSD | `pkg install fzf` |
| NixOS | `nix-env -iA nixpkgs.fzf` |
| openSUSE | `sudo zypper install fzf` |
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
plugin may or may not be enabled by default depending on the package manager.
Refer to the package documentation for more information.
### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
available via [Chocolatey][choco] and [Scoop][scoop]:
| Package manager | Command |
| --- | --- |
| Chocolatey | `choco install fzf` |
| Scoop | `scoop install fzf` |
[choco]: https://chocolatey.org/packages/fzf
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
Known issues and limitations on Windows can be found on [the wiki
page][windows-wiki].
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
### As Vim plugin ### As Vim plugin
Once you have fzf installed, you can enable it inside Vim simply by adding the Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to `&runtimepath` in your Vim configuration file as follows: directory to `&runtimepath` in your Vim configuration file. The path may
differ depending on the package manager.
```vim ```vim
" If installed using Homebrew " If installed using Homebrew
@@ -141,77 +171,6 @@ Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" and you don't have to run the install script if you use fzf only in Vim. " and you don't have to run the install script if you use fzf only in Vim.
``` ```
### Arch Linux
```sh
sudo pacman -S fzf
```
### Debian
fzf is available in Debian Buster and above, and can be installed using the usual
method:
```sh
sudo apt-get install fzf
```
Read the documentation (/usr/share/doc/fzf/README.Debian) on how to enable it.
### Fedora
fzf is available in Fedora 26 and above, and can be installed using the usual
method:
```sh
sudo dnf install fzf
```
Shell completion and plugins for vim or neovim are enabled by default. Shell
key bindings are installed but not enabled by default. See Fedora's package
documentation (/usr/share/doc/fzf/README.Fedora) for more information.
### openSUSE
fzf is available in openSUSE Tumbleweed and can be installed via zypper:
```sh
sudo zypper install fzf
```
### FreeBSD
```sh
pkg install fzf
```
### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
available as a [Chocolatey package][choco]:
[choco]: https://chocolatey.org/packages/fzf
```sh
choco install fzf
```
or a [Scoop package][scoop]:
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
```sh
scoop install fzf
```
However, other components of the project may not work on Windows. Known issues
and limitations can be found on [the wiki page][windows-wiki]. You might want
to consider installing fzf on [Windows Subsystem for Linux][wsl] where
everything runs flawlessly.
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
[wsl]: https://blogs.msdn.microsoft.com/wsl/
Upgrading fzf Upgrading fzf
------------- -------------
@@ -474,11 +433,12 @@ _fzf_compgen_dir() {
On bash, fuzzy completion is enabled only for a predefined set of commands On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other (`complete | grep _fzf` to see the list). But you can enable it for other
commands as well as follows. commands as well by using `_fzf_setup_completion` helper function.
```sh ```sh
complete -F _fzf_path_completion -o default -o bashdefault ag # usage: _fzf_setup_completion path|dir COMMANDS...
complete -F _fzf_dir_completion -o default -o bashdefault tree _fzf_setup_completion path ag git kubectl
_fzf_setup_completion dir tree
``` ```
Vim plugin Vim plugin
@@ -539,37 +499,50 @@ important that the command finishes quickly.
fzf --preview 'head -100 {}' fzf --preview 'head -100 {}'
``` ```
Preview window supports ANSI colors, so you can use programs that Preview window supports ANSI colors, so you can use any program that
syntax-highlights the content of a file. syntax-highlights the content of a file.
- Bat: https://github.com/sharkdp/bat - Bat: https://github.com/sharkdp/bat
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php - Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
- CodeRay: http://coderay.rubychan.de/
- Rouge: https://github.com/jneen/rouge
```bash ```bash
# Try bat, highlight, coderay, rougify in turn, then fall back to cat fzf --preview 'bat --style=numbers --color=always {} | head -500'
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
echo {} is a binary file ||
(bat --style=numbers --color=always {} ||
highlight -O ansi -l {} ||
coderay {} ||
rougify {} ||
cat {}) 2> /dev/null | head -500'
``` ```
You can customize the size and position of the preview window using You can customize the size, position, and border of the preview window using
`--preview-window` option. For example, `--preview-window` option, and the foreground and background color of it with
`--color` option. For example,
```bash ```bash
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1 fzf --height 40% --layout reverse --info inline --border \
--preview 'file {}' --preview-window down:1:noborder \
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
``` ```
See the man page (`man fzf`) for the full list of options.
For more advanced examples, see [Key bindings for git with fzf][fzf-git] For more advanced examples, see [Key bindings for git with fzf][fzf-git]
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)). ([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/ [fzf-git]: https://junegunn.kr/2016/07/fzf-git/
----
Since fzf is a general-purpose text filter rather than a file finder, **it is
not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**.
```sh
# *********************
# ** DO NOT DO THIS! **
# *********************
export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always {} | head -500"'
# bat doesn't work with any input other than the list of files
ps -ef | fzf
seq 100 | fzf
history | fzf
```
Tips Tips
---- ----
@@ -602,45 +575,17 @@ hidden files, use the following command:
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
``` ```
#### `git ls-tree` for fast traversal
If you're running fzf in a large git repository, `git ls-tree` can boost up the
speed of the traversal.
```sh
export FZF_DEFAULT_COMMAND='
(git ls-tree -r --name-only HEAD ||
find . -path "*/\.*" -prune -o -type f -print -o -type l -print |
sed s/^..//) 2> /dev/null'
```
#### Fish shell #### Fish shell
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362) `CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
reading from STDIN in command substitution, which means simple `vim (fzf)` token on the command-line as the root directory for the recursive search. For
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use instance, hitting `CTRL-T` at the end of the following command-line
the `read` fish command:
```sh
fzf | read -l result; and vim $result
```
or, for multiple results:
```sh
fzf -m | while read -l r; set result $result $r; end; and vim $result
```
The globbing system is different in fish and thus `**` completion will not work.
However, the `CTRL-T` command will use the last token on the command-line as the
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
of the following command-line
```sh ```sh
ls /var/ ls /var/
``` ```
will list all files and folders under `/var/`. will list all files and directories under `/var/`.
When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to
make use of this feature. `$dir` defaults to `.` when the last token is not a make use of this feature. `$dir` defaults to `.` when the last token is not a
@@ -660,4 +605,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi Copyright (c) 2019 Junegunn Choi

View File

@@ -1,24 +1,47 @@
fzf.txt fzf Last change: June 8 2019 fzf.txt fzf Last change: November 23 2019
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
FZF Vim integration FZF Vim integration
Summary
:FZF[!] :FZF[!]
Configuration Configuration
Examples Examples
fzf#run fzf#run
fzf#wrap fzf#wrap
Tips
fzf inside terminal buffer fzf inside terminal buffer
Starting fzf in Neovim floating window
Hide statusline Hide statusline
License License
FZF VIM INTEGRATION *fzf-vim-integration* FZF VIM INTEGRATION *fzf-vim-integration*
============================================================================== ==============================================================================
This repository only enables basic integration with Vim. If you're looking for
more, check out {fzf.vim}{1} project.
(Note: To use fzf in GVim, an external terminal emulator is required.) SUMMARY *fzf-summary*
==============================================================================
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. `fzf#run([specdict])`
- Starts fzf inside Vim with the given spec
- `:callfzf#run({'source':'ls'})`
2. `fzf#wrap([specdict])->(dict)`
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echofzf#wrap({'source':'ls'})`
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
- `:callfzf#run(fzf#wrap({'source':'ls'}))`
3. `:FZF[fzf_optionsstring][pathstring]`
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript to
implement custom commands
- If you're looking for more such commands, check out {fzf.vim}{1} project.
The most important of all is `fzf#run`, but it would be easier to understand
the whole if we start off with `:FZF` command.
{1} https://github.com/junegunn/fzf.vim {1} https://github.com/junegunn/fzf.vim
@@ -27,8 +50,6 @@ more, check out {fzf.vim}{1} project.
============================================================================== ==============================================================================
*:FZF* *:FZF*
If you have set up fzf for Vim, `:FZF` command will be added.
> >
" Look for files under current directory " Look for files under current directory
:FZF :FZF
@@ -36,8 +57,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
" Look for files under your home directory " Look for files under your home directory
:FZF ~ :FZF ~
" With options " With fzf command-line options
:FZF --no-sort --reverse --inline-info /tmp :FZF --reverse --info=inline /tmp
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
@@ -100,6 +121,7 @@ Examples~
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
" Customize fzf colors to match your color scheme " Customize fzf colors to match your color scheme
" - fzf#wrap translates this to a set of `--color` options
let g:fzf_colors = let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'], \ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'], \ 'bg': ['bg', 'Normal'],
@@ -115,68 +137,64 @@ Examples~
\ 'spinner': ['fg', 'Label'], \ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] } \ 'header': ['fg', 'Comment'] }
" Enable per-command history. " Enable per-command history
" CTRL-N and CTRL-P will be automatically bound to next-history and " - History files will be stored in the specified directory
" previous-history instead of down and up. If you don't like the change, " - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS. " 'previous-history' instead of 'down' and 'up'.
let g:fzf_history_dir = '~/.local/share/fzf-history' let g:fzf_history_dir = '~/.local/share/fzf-history'
< <
FZF#RUN *fzf#run* FZF#RUN
============================================================================== ==============================================================================
For more advanced uses, you can use `fzf#run([options])` function. *fzf#run*
`fzf#run()` function is the core of Vim integration. It takes a single `fzf#run()` function is the core of Vim integration. It takes a single
dictionary argument. At the very least, specify `sink` option to tell what it dictionary argument, a spec, and starts fzf process accordingly. At the very
should do with the selected entry. least, specify `sink` option to tell what it should do with the selected
entry.
> >
call fzf#run({'sink': 'e'}) call fzf#run({'sink': 'e'})
< <
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if We haven't specified the `source`, so this is equivalent to starting fzf on
defined) to list the files under the current directory. When you select one, command line without standard input pipe; fzf will use find command (or
it will open it with `:e` command. If you want to open it in a new tab, you `$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
can pass `:tabedit` command instead as the sink. directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
> >
call fzf#run({'sink': 'tabedit'}) call fzf#run({'sink': 'tabedit'})
< <
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
you can change its bottom-up layout with `--reverse` option. Such options can
be specified as `options`.
>
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
<
Instead of using the default find command, you can use any shell command as Instead of using the default find command, you can use any shell command as
the source. This will list the files managed by git. the source. The following example will list the files managed by git. It's
equivalent to running `gitls-files|fzf` on shell.
> >
call fzf#run({'source': 'git ls-files', 'sink': 'e'}) call fzf#run({'source': 'git ls-files', 'sink': 'e'})
< <
Pass a layout option if you don't want fzf window to take up the entire fzf options can be specified as `options` entry in spec dictionary.
screen. >
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
<
You can also pass a layout option if you don't want fzf window to take up the
entire screen.
> >
" up / down / left / right / window are allowed " up / down / left / right / window are allowed
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'}) call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
< <
`source` doesn't have to be an external shell command, you can pass a Vim `source` doesn't have to be an external shell command, you can pass a Vim
array as the source. In the following example, we use the names of the open array as the source. In the next example, we pass the names of color schemes
buffers as the source. as the source to implement a color scheme selector.
>
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
\ 'bufname(v:val)'),
\ 'sink': 'e', 'down': '30%'})
<
Or the names of color schemes.
> >
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')), call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
\ 'fnamemodify(v:val, ":t:r")'), \ 'fnamemodify(v:val, ":t:r")'),
\ 'sink': 'colo', 'left': '25%'}) \ 'sink': 'colo', 'left': '25%'})
< <
The following table shows the available options. The following table summarizes the available options.
---------------------------+---------------+-------------------------------------------------------------- ---------------------------+---------------+----------------------------------------------------------------------
Option name | Type | Description ~ Option name | Type | Description ~
---------------------------+---------------+-------------------------------------------------------------- ---------------------------+---------------+----------------------------------------------------------------------
`source` | string | External command to generate input to fzf (e.g. `find.` ) `source` | string | External command to generate input to fzf (e.g. `find.` )
`source` | list | Vim list as input to fzf `source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` ) `sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
@@ -184,74 +202,135 @@ The following table shows the available options.
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once `sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
`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 | Use tmux pane with the given size (e.g. `20` , `50%` ) `up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `verticalaboveleft30new` ) `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` )
`launcher` | string | External terminal emulator to start fzf with (GVim only) ---------------------------+---------------+----------------------------------------------------------------------
`launcher` | funcref | Function for generating `launcher` string (GVim only)
---------------------------+---------------+--------------------------------------------------------------
`options` entry can be either a string or a list. For simple cases, string `options` entry can be either a string or a list. For simple cases, string
should suffice, but prefer to use list type if you're concerned about escaping should suffice, but prefer to use list type to avoid escaping issues.
issues on different platforms.
> >
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'}) call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']}) call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
< <
FZF#WRAP *fzf#wrap* FZF#WRAP
============================================================================== ==============================================================================
`:FZF` command provided by default knows how to handle CTRL-T, CTRL-X, and *fzf#wrap*
CTRL-V and opens the selected file in a new tab, in a horizontal split, or in
a vertical split respectively. And these key bindings can be configured via
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
options.
By "wrapping" your options dictionary with `fzf#wrap` before passing it to We have seen that several aspects of `:FZF` command can be configured with a
`fzf#run`, you can make your command also support the options. set of global option variables; different ways to open files (`g:fzf_action`),
window position and size (`g:fzf_layout`), color palette (`g:fzf_colors`),
etc.
So how can we make our custom `fzf#run` calls also respect those variables?
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
`fzf#run`.
- `fzf#wrap([namestring],[specdict],[fullscreenbool])->(dict)`
- All arguments are optional. Usually we only need to pass a spec
dictionary.
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
is not defined.
- `fullscreen` can be either `0` or `1` (default: 0).
`fzf#wrap` takes a spec and returns an extended version of it (also a
dictionary) with additional options for addressing global preferences. You can
examine the return value of it like so:
> >
" Usage: echo fzf#wrap({'source': 'ls'})
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean]) <
After we "wrap" our spec, we pass it to `fzf#run`.
>
call fzf#run(fzf#wrap({'source': 'ls'}))
<
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf
window according to `g:fzf_layout` setting.
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings To make it easier to use, let's define `LS` command.
" and opens fzf according to g:fzf_layout setting. >
command! Buffers call fzf#run(fzf#wrap( command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')})) <
Type `:LS` and see how it works.
" This extends the above example to open fzf in fullscreen We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
" when the command is run with ! suffix (Buffers!) `:FZF!`. Add `-bang` to command definition, and use <bang> value to set the
command! -bang Buffers call fzf#run(fzf#wrap( last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0)) >
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
" You can optionally pass the name of the command as the first argument to command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
" fzf#wrap to make it work with g:fzf_history_dir <
command! -bang Buffers call fzf#run(fzf#wrap('buffers', Our `:LS` command will be much more useful if we can pass a directory argument
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0)) to it, so that something like `:LS/tmp` is possible.
>
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
<
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
unique name to our command and pass it as the first argument to `fzf#wrap`.
>
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
< <
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer* TIPS *fzf-tips*
============================================================================== ==============================================================================
< fzf inside terminal buffer >________________________________________________~
*fzf-inside-terminal-buffer*
The latest versions of Vim and Neovim include builtin terminal emulator The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases: (`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim - On Neovim
- On GVim - On GVim
- On Terminal Vim with the non-default layout - On Terminal Vim with a non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}` - `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
< Hide statusline >___________________________________________________________~ Starting fzf in Neovim floating window~
*fzf-starting-fzf-in-neovim-floating-window*
>
" Using floating windows of Neovim to start fzf
if has('nvim')
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2'
function! FloatingFZF()
let width = float2nr(&columns * 0.9)
let height = float2nr(&lines * 0.6)
let opts = { 'relative': 'editor',
\ 'row': (&lines - height) / 2,
\ 'col': (&columns - width) / 2,
\ 'width': width,
\ 'height': height }
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal')
endfunction
let g:fzf_layout = { 'window': 'call FloatingFZF()' }
endif
<
Hide statusline~
*fzf-hide-statusline* *fzf-hide-statusline*
When fzf starts in a terminal buffer, you may want to hide the statusline of When fzf starts in a terminal buffer, the file type of the buffer is set to
the containing buffer. `fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
the window.
For example, if you use the default layout (`{'down':'~40%'}`) on Neovim, you
might want to temporarily disable the statusline for a cleaner look.
> >
if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler \| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
< <
LICENSE *fzf-license* LICENSE *fzf-license*
@@ -259,7 +338,7 @@ LICENSE *fzf-license*
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi Copyright (c) 2019 Junegunn Choi
============================================================================== ==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.19.0 version=0.20.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -196,6 +196,8 @@ case "$archi" in
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;; MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
Windows*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
Windows*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
*) binary_available=0 binary_error=1 ;; *) binary_available=0 binary_error=1 ;;
esac esac

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Nov 2019" "fzf 0.19.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Dec 2019" "fzf 0.20.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Dec 2019" "fzf 0.20.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -262,11 +262,13 @@ format.
\fBdark \fRColor scheme for dark 256-color terminal \fBdark \fRColor scheme for dark 256-color terminal
\fBlight \fRColor scheme for light 256-color terminal \fBlight \fRColor scheme for light 256-color terminal
\fB16 \fRColor scheme for 16-color terminal \fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors \fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR: .B COLOR:
\fBfg \fRText \fBfg \fRText
\fBbg \fRBackground \fBbg \fRBackground
\fBpreview-fg \fRPreview window text
\fBpreview-bg \fRPreview window background
\fBhl \fRHighlighted substrings \fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line) \fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line) \fBbg+ \fRBackground (current line)
@@ -632,11 +634,13 @@ A key or an event can be bound to one or more of the following actions.
\fBbackward-kill-word\fR \fIalt-bs\fR \fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR \fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR \fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR (clears query string if not empty, aborts fzf otherwise) \fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBclear-screen\fR \fIctrl-l\fR \fBclear-screen\fR \fIctrl-l\fR
\fBclear-selection\fR (clear multi-selection)
\fBclear-query\fR (clear query string)
\fBdelete-char\fR \fIdel\fR \fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR \fBdelete-char/eof\fR \fIctrl-d\fR
\fBdeselect-all\fR \fBdeselect-all\fR (deselect all matches)
\fBdown\fR \fIctrl-j ctrl-n down\fR \fBdown\fR \fIctrl-j ctrl-n down\fR
\fBend-of-line\fR \fIctrl-e end\fR \fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details) \fBexecute(...)\fR (see below for the details)
@@ -662,9 +666,9 @@ A key or an event can be bound to one or more of the following actions.
\fBprint-query\fR (print query and exit) \fBprint-query\fR (print query and exit)
\fBreload(...)\fR (see below for the details) \fBreload(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection) \fBreplace-query\fR (replace query string with the current selection)
\fBselect-all\fR \fBselect-all\fR (select all matches)
\fBtoggle\fR (\fIright-click\fR) \fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR \fBtoggle-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl-i (tab)\fR \fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR) \fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR) \fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)

View File

@@ -49,18 +49,18 @@ if s:is_win
" Use utf-8 for fzf.vim commands " Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe " Return array of shell commands for cmd.exe
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
function! s:enc_to_cp(str)
return iconv(a:str, &encoding, 'cp'.s:codepage)
endfunction
function! s:wrap_cmds(cmds) function! s:wrap_cmds(cmds)
let use_chcp = executable('sed')
return map([ return map([
\ '@echo off', \ '@echo off',
\ 'setlocal enabledelayedexpansion'] \ 'setlocal enabledelayedexpansion']
\ + (use_chcp ? [ \ + (has('gui_running') ? ['set TERM= > nul'] : [])
\ 'for /f "usebackq" %%a in (`chcp ^| sed "s/[^0-9]//gp"`) do set origchcp=%%a',
\ 'chcp 65001 > nul'] : [])
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
\ + (use_chcp ? ['chcp !origchcp! > nul'] : [])
\ + ['endlocal'], \ + ['endlocal'],
\ 'v:val."\r"') \ '<SID>enc_to_cp(v:val."\r")')
endfunction endfunction
else else
let s:term_marker = ";#FZF" let s:term_marker = ";#FZF"
@@ -72,6 +72,10 @@ else
function! s:wrap_cmds(cmds) function! s:wrap_cmds(cmds)
return a:cmds return a:cmds
endfunction endfunction
function! s:enc_to_cp(str)
return a:str
endfunction
endif endif
function! s:shellesc_cmd(arg) function! s:shellesc_cmd(arg)
@@ -83,7 +87,7 @@ function! s:shellesc_cmd(arg)
endfunction endfunction
function! fzf#shellescape(arg, ...) function! fzf#shellescape(arg, ...)
let shell = get(a:000, 0, &shell) let shell = get(a:000, 0, s:is_win ? 'cmd.exe' : 'sh')
if shell =~# 'cmd.exe$' if shell =~# 'cmd.exe$'
return s:shellesc_cmd(a:arg) return s:shellesc_cmd(a:arg)
endif endif
@@ -247,7 +251,7 @@ function! s:common_sink(action, lines) abort
endfunction endfunction
function! s:get_color(attr, ...) function! s:get_color(attr, ...)
let gui = has('termguicolors') && &termguicolors let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
let fam = gui ? 'gui' : 'cterm' let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$' let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000 for group in a:000
@@ -342,19 +346,21 @@ function! fzf#wrap(...)
endfunction endfunction
function! s:use_sh() function! s:use_sh()
let [shell, shellslash] = [&shell, &shellslash] let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]
if s:is_win if s:is_win
set shell=cmd.exe set shell=cmd.exe
set noshellslash set noshellslash
let &shellcmdflag = has('nvim') ? '/s /c' : '/c'
let &shellxquote = has('nvim') ? '"' : '('
else else
set shell=sh set shell=sh
endif endif
return [shell, shellslash] return [shell, shellslash, shellcmdflag, shellxquote]
endfunction endfunction
function! fzf#run(...) abort function! fzf#run(...) abort
try try
let [shell, shellslash] = s:use_sh() let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
let dict = exists('a:1') ? s:upgrade(a:1) : {} let dict = exists('a:1') ? s:upgrade(a:1) : {}
let temps = { 'result': s:fzf_tempname() } let temps = { 'result': s:fzf_tempname() }
@@ -385,7 +391,7 @@ try
let prefix = '( '.source.' )|' let prefix = '( '.source.' )|'
elseif type == 3 elseif type == 3
let temps.input = s:fzf_tempname() let temps.input = s:fzf_tempname()
call writefile(source, temps.input) call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input)
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|' let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
else else
throw 'Invalid source type' throw 'Invalid source type'
@@ -424,7 +430,7 @@ try
call s:callback(dict, lines) call s:callback(dict, lines)
return lines return lines
finally finally
let [&shell, &shellslash] = [shell, shellslash] let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
endtry endtry
endfunction endfunction

View File

@@ -16,7 +16,7 @@ if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
echo "$1" echo "$1"
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -24,7 +24,7 @@ fi
if ! declare -f _fzf_compgen_dir > /dev/null; then if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() { _fzf_compgen_dir() {
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -244,7 +244,7 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u

View File

@@ -16,7 +16,7 @@ if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
echo "$1" echo "$1"
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -24,7 +24,7 @@ fi
if ! declare -f _fzf_compgen_dir > /dev/null; then if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() { _fzf_compgen_dir() {
command find -L "$1" \ command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \ -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} }
fi fi
@@ -115,7 +115,7 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
setopt localoptions nonomatch setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u

View File

@@ -56,6 +56,7 @@ fzf-cd-widget() {
return 0 return 0
fi fi
cd "$dir" cd "$dir"
unset dir # ensure this doesn't end up appearing in prompt expansion
local ret=$? local ret=$?
zle fzf-redraw-prompt zle fzf-redraw-prompt
return $ret return $ret

View File

@@ -10,7 +10,7 @@ import (
const ( const (
// Current version // Current version
version = "0.19.0" version = "0.20.0"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -224,10 +224,14 @@ func Run(opts *Options, revision string) {
// Event coordination // Event coordination
reading := true reading := true
clearCache := util.Once(false)
clearSelection := util.Once(false)
ticks := 0 ticks := 0
var nextCommand *string var nextCommand *string
restart := func(command string) { restart := func(command string) {
reading = true reading = true
clearCache = util.Once(true)
clearSelection = util.Once(true)
chunkList.Clear() chunkList.Clear()
header = make([]string, 0, opts.HeaderLines) header = make([]string, 0, opts.HeaderLines)
go reader.restart(command) go reader.restart(command)
@@ -250,20 +254,20 @@ func Run(opts *Options, revision string) {
switch evt { switch evt {
case EvtReadNew, EvtReadFin: case EvtReadNew, EvtReadFin:
clearCache := false
if evt == EvtReadFin && nextCommand != nil { if evt == EvtReadFin && nextCommand != nil {
clearCache = true
restart(*nextCommand) restart(*nextCommand)
nextCommand = nil nextCommand = nil
break
} else { } else {
reading = reading && evt == EvtReadNew reading = reading && evt == EvtReadNew
} }
snapshot, count := chunkList.Snapshot() snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string)) terminal.UpdateCount(count, !reading, value.(*string))
if opts.Sync { if opts.Sync {
terminal.UpdateList(PassMerger(&snapshot, opts.Tac)) opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
} }
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache) matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
case EvtSearchNew: case EvtSearchNew:
var command *string var command *string
@@ -279,9 +283,10 @@ func Run(opts *Options, revision string) {
} else { } else {
restart(*command) restart(*command)
} }
break
} }
snapshot, _ := chunkList.Snapshot() snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil) matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
delay = false delay = false
case EvtSearchProgress: case EvtSearchProgress:
@@ -291,7 +296,9 @@ func Run(opts *Options, revision string) {
} }
case EvtHeader: case EvtHeader:
terminal.UpdateHeader(value.([]string)) headerPadded := make([]string, opts.HeaderLines)
copy(headerPadded, value.([]string))
terminal.UpdateHeader(headerPadded)
case EvtSearchFin: case EvtSearchFin:
switch val := value.(type) { switch val := value.(type) {
@@ -321,7 +328,7 @@ func Run(opts *Options, revision string) {
terminal.startChan <- true terminal.startChan <- true
} }
} }
terminal.UpdateList(val) terminal.UpdateList(val, clearSelection())
} }
} }
} }

View File

@@ -597,6 +597,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme.Fg = ansi theme.Fg = ansi
case "bg": case "bg":
theme.Bg = ansi theme.Bg = ansi
case "preview-fg":
theme.PreviewFg = ansi
case "preview-bg":
theme.PreviewBg = ansi
case "fg+": case "fg+":
theme.Current = ansi theme.Current = ansi
case "bg+": case "bg+":
@@ -648,14 +652,18 @@ func init() {
// Backreferences are not supported. // Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|') // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si):(execute(?:-multi|-silent)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) `(?si)[:+](execute(?:-multi|-silent)?|reload):.+|[:+](execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
} }
func parseKeymap(keymap map[int][]action, str string) { func parseKeymap(keymap map[int][]action, str string) {
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string { masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
prefix := ":execute" symbol := ":"
if strings.HasPrefix(src, ":reload") { if strings.HasPrefix(src, "+") {
prefix = ":reload" symbol = "+"
}
prefix := symbol + "execute"
if strings.HasPrefix(src[1:], "reload") {
prefix = symbol + "reload"
} else if src[len(prefix)] == '-' { } else if src[len(prefix)] == '-' {
c := src[len(prefix)+1] c := src[len(prefix)+1]
if c == 's' || c == 'S' { if c == 's' || c == 'S' {
@@ -734,6 +742,10 @@ func parseKeymap(keymap map[int][]action, str string) {
appendAction(actEndOfLine) appendAction(actEndOfLine)
case "cancel": case "cancel":
appendAction(actCancel) appendAction(actCancel)
case "clear-query":
appendAction(actClearQuery)
case "clear-selection":
appendAction(actClearSelection)
case "forward-char": case "forward-char":
appendAction(actForwardChar) appendAction(actForwardChar)
case "forward-word": case "forward-word":
@@ -805,7 +817,11 @@ func parseKeymap(keymap map[int][]action, str string) {
default: default:
t := isExecuteAction(specLower) t := isExecuteAction(specLower)
if t == actIgnore { if t == actIgnore {
if specIndex == 0 && specLower == "" {
actions = append(keymap[key], actions...)
} else {
errorExit("unknown action: " + spec) errorExit("unknown action: " + spec)
}
} else { } else {
var offset int var offset int
switch t { switch t {

View File

@@ -243,9 +243,10 @@ func TestBind(t *testing.T) {
check(tui.CtrlA, "", actBeginningOfLine) check(tui.CtrlA, "", actBeginningOfLine)
parseKeymap(keymap, parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+ "ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {})+abort,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+ "f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+ "alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+ "x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+top,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up") ",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA, "", actKillLine) check(tui.CtrlA, "", actKillLine)
check(tui.CtrlB, "", actToggleSort, actUp, actDown) check(tui.CtrlB, "", actToggleSort, actUp, actDown)
@@ -253,7 +254,7 @@ func TestBind(t *testing.T) {
check(tui.AltZ+',', "", actAbort) check(tui.AltZ+',', "", actAbort)
check(tui.AltZ+':', "", actAccept) check(tui.AltZ+':', "", actAccept)
check(tui.AltZ, "", actPageDown) check(tui.AltZ, "", actPageDown)
check(tui.F1, "ls {}", actExecute, actAbort) check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
check(tui.F2, "echo {}, {}, {}", actExecute) check(tui.F2, "echo {}, {}, {}", actExecute)
check(tui.F3, "echo '({})'", actExecute) check(tui.F3, "echo '({})'", actExecute)
check(tui.F4, "less {}", actExecute) check(tui.F4, "less {}", actExecute)

View File

@@ -185,6 +185,8 @@ const (
actBackwardWord actBackwardWord
actCancel actCancel
actClearScreen actClearScreen
actClearQuery
actClearSelection
actDeleteChar actDeleteChar
actDeleteCharEOF actDeleteCharEOF
actEndOfLine actEndOfLine
@@ -493,10 +495,13 @@ func (t *Terminal) UpdateProgress(progress float32) {
} }
// UpdateList updates Merger to display the list // UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) { func (t *Terminal) UpdateList(merger *Merger, reset bool) {
t.mutex.Lock() t.mutex.Lock()
t.progress = 100 t.progress = 100
t.merger = merger t.merger = merger
if reset {
t.selected = make(map[int32]selectedItem)
}
t.mutex.Unlock() t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil) t.reqBox.Set(reqInfo, nil)
t.reqBox.Set(reqList, nil) t.reqBox.Set(reqList, nil)
@@ -619,7 +624,8 @@ func (t *Terminal) resizeWindows() {
marginInt[0]-1, marginInt[0]-1,
marginInt[3], marginInt[3],
width, width,
height+2, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode)) height+2,
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
} }
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if previewVisible {
@@ -628,7 +634,7 @@ func (t *Terminal) resizeWindows() {
if !t.preview.border { if !t.preview.border {
previewBorder = tui.MakeTransparentBorder() previewBorder = tui.MakeTransparentBorder()
} }
t.pborder = t.tui.NewWindow(y, x, w, h, previewBorder) t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
pwidth := w - 4 pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of // ncurses auto-wraps the line when the cursor reaches the right-end of
// the window. To prevent unintended line-wraps, we use the width one // the window. To prevent unintended line-wraps, we use the width one
@@ -636,28 +642,28 @@ func (t *Terminal) resizeWindows() {
if !t.preview.wrap && t.tui.DoesAutoWrap() { if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1 pwidth += 1
} }
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, noBorder) t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, true, noBorder)
} }
switch t.preview.position { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, noBorder) marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, noBorder) marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, noBorder) marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, noBorder) marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
} }
} else { } else {
@@ -665,7 +671,7 @@ func (t *Terminal) resizeWindows() {
marginInt[0], marginInt[0],
marginInt[3], marginInt[3],
width, width,
height, noBorder) height, false, noBorder)
} }
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0) t.window.MoveAndClear(i, 0)
@@ -1066,7 +1072,7 @@ func (t *Terminal) printPreview() {
if t.theme != nil && ansi != nil && ansi.colored() { if t.theme != nil && ansi != nil && ansi.colored() {
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
fillRet = t.pwindow.CFill(tui.ColNormal.Fg(), tui.ColNormal.Bg(), tui.AttrRegular, str) fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
} }
return fillRet == tui.FillContinue return fillRet == tui.FillContinue
}) })
@@ -1237,7 +1243,7 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
return false, matchWithoutFlags, flags return false, matchWithoutFlags, flags
} }
func hasPreviewFlags(template string) (plus bool, query bool) { func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
for _, match := range placeholder.FindAllString(template, -1) { for _, match := range placeholder.FindAllString(template, -1) {
_, _, flags := parsePlaceholder(match) _, _, flags := parsePlaceholder(match)
if flags.plus { if flags.plus {
@@ -1246,6 +1252,7 @@ func hasPreviewFlags(template string) (plus bool, query bool) {
if flags.query { if flags.query {
query = true query = true
} }
slot = true
} }
return return
} }
@@ -1409,7 +1416,7 @@ func (t *Terminal) currentItem() *Item {
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) { func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
current := t.currentItem() current := t.currentItem()
plus, query := hasPreviewFlags(template) _, plus, query := hasPreviewFlags(template)
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) { if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
return current != nil, []*Item{current, current} return current != nil, []*Item{current, current}
} }
@@ -1904,6 +1911,15 @@ func (t *Terminal) Loop() {
} }
case actClearScreen: case actClearScreen:
req(reqRedraw) req(reqRedraw)
case actClearQuery:
t.input = []rune{}
t.cx = 0
case actClearSelection:
if t.multi > 0 {
t.selected = make(map[int32]selectedItem)
t.version++
req(reqList, reqInfo)
}
case actTop: case actTop:
t.vset(0) t.vset(0)
req(reqList) req(reqList)
@@ -2045,17 +2061,17 @@ func (t *Terminal) Loop() {
t.failed = nil t.failed = nil
valid, list := t.buildPlusList(a.a, false) valid, list := t.buildPlusList(a.a, false)
// If the command template has {q}, we run the command even when the
// query string is empty.
if !valid { if !valid {
_, query := hasPreviewFlags(a.a) // We run the command even when there's no match
valid = query // 1. If the template doesn't have any slots
// 2. If the template has {q}
slot, _, query := hasPreviewFlags(a.a)
valid = !slot || query
} }
if valid { if valid {
command := replacePlaceholder(a.a, command := replacePlaceholder(a.a,
t.ansi, t.delimiter, t.printsep, false, string(t.input), list) t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
newCommand = &command newCommand = &command
t.selected = make(map[int32]selectedItem)
} }
} }
return true return true
@@ -2095,7 +2111,7 @@ func (t *Terminal) Loop() {
if queryChanged { if queryChanged {
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, q := hasPreviewFlags(t.preview.command) _, _, q := hasPreviewFlags(t.preview.command)
if q { if q {
t.version++ t.version++
} }

View File

@@ -39,6 +39,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
return nil return nil
} }

View File

@@ -666,7 +666,7 @@ func (r *LightRenderer) DoesAutoWrap() bool {
return false return false
} }
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme != nil, colored: r.theme != nil,
@@ -679,9 +679,14 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
fg: colDefault, fg: colDefault,
bg: colDefault} bg: colDefault}
if r.theme != nil { if r.theme != nil {
if preview {
w.fg = r.theme.PreviewFg
w.bg = r.theme.PreviewBg
} else {
w.fg = r.theme.Fg w.fg = r.theme.Fg
w.bg = r.theme.Bg w.bg = r.theme.Bg
} }
}
w.drawBorder() w.drawBorder()
return w return w
} }
@@ -704,16 +709,16 @@ func (w *LightWindow) drawBorderHorizontal() {
func (w *LightWindow) drawBorderAround() { func (w *LightWindow) drawBorderAround() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, w.CPrint(ColPreviewBorder, AttrRegular,
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight)) string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
w.Move(y, 0) w.Move(y, 0)
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical)) w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2)) w.CPrint(ColPreviewBorder, AttrRegular, repeat(' ', w.width-2))
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical)) w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, w.CPrint(ColPreviewBorder, AttrRegular,
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight)) string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
} }

View File

@@ -32,6 +32,7 @@ type TcellWindow struct {
left int left int
width int width int
height int height int
normal ColorPair
lastX int lastX int
lastY int lastY int
moveCursor bool moveCursor bool
@@ -408,14 +409,18 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
_screen.Show() _screen.Show()
} }
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window { func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
// TODO normal := ColNormal
if preview {
normal = ColPreview
}
return &TcellWindow{ return &TcellWindow{
color: r.theme != nil, color: r.theme != nil,
top: top, top: top,
left: left, left: left,
width: width, width: width,
height: height, height: height,
normal: normal,
borderStyle: borderStyle} borderStyle: borderStyle}
} }
@@ -423,16 +428,16 @@ func (w *TcellWindow) Close() {
// TODO // TODO
} }
func fill(x, y, w, h int, r rune) { func fill(x, y, w, h int, n ColorPair, r rune) {
for ly := 0; ly <= h; ly++ { for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ { for lx := 0; lx <= w; lx++ {
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style()) _screen.SetContent(x+lx, y+ly, r, nil, n.style())
} }
} }
} }
func (w *TcellWindow) Erase() { func (w *TcellWindow) Erase() {
fill(w.left-1, w.top, w.width+1, w.height, ' ') fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
} }
func (w *TcellWindow) Enclose(y int, x int) bool { func (w *TcellWindow) Enclose(y int, x int) bool {
@@ -449,13 +454,13 @@ func (w *TcellWindow) Move(y int, x int) {
func (w *TcellWindow) MoveAndClear(y int, x int) { func (w *TcellWindow) MoveAndClear(y int, x int) {
w.Move(y, x) w.Move(y, x)
for i := w.lastX; i < w.width; i++ { for i := w.lastX; i < w.width; i++ {
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style()) _screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())
} }
w.lastX = x w.lastX = x
} }
func (w *TcellWindow) Print(text string) { func (w *TcellWindow) Print(text string) {
w.printString(text, ColNormal, 0) w.printString(text, w.normal, 0)
} }
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) { func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
@@ -468,7 +473,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0) Underline(a&Attr(tcell.AttrUnderline) != 0)
} else { } else {
style = ColNormal.style(). style = w.normal.style().
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch). Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch) Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
} }
@@ -519,7 +524,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
if w.color { if w.color {
style = pair.style() style = pair.style()
} else { } else {
style = ColNormal.style() style = w.normal.style()
} }
style = style. style = style.
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
@@ -559,15 +564,15 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
} }
func (w *TcellWindow) Fill(str string) FillReturn { func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, ColNormal, 0) return w.fillString(str, w.normal, 0)
} }
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn { func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if fg == colDefault { if fg == colDefault {
fg = ColNormal.Fg() fg = w.normal.Fg()
} }
if bg == colDefault { if bg == colDefault {
bg = ColNormal.Bg() bg = w.normal.Bg()
} }
return w.fillString(str, NewColorPair(fg, bg), a) return w.fillString(str, NewColorPair(fg, bg), a)
} }
@@ -584,9 +589,13 @@ func (w *TcellWindow) drawBorder() {
var style tcell.Style var style tcell.Style
if w.color { if w.color {
style = ColBorder.style() if w.borderStyle.shape == BorderAround {
style = ColPreviewBorder.style()
} else { } else {
style = ColNormal.style() style = ColBorder.style()
}
} else {
style = w.normal.style()
} }
for x := left; x < right; x++ { for x := left; x < right; x++ {

View File

@@ -173,6 +173,8 @@ func (p ColorPair) Bg() Color {
type ColorTheme struct { type ColorTheme struct {
Fg Color Fg Color
Bg Color Bg Color
PreviewFg Color
PreviewBg Color
DarkBg Color DarkBg Color
Gutter Color Gutter Color
Prompt Color Prompt Color
@@ -272,7 +274,7 @@ type Renderer interface {
MaxY() int MaxY() int
DoesAutoWrap() bool DoesAutoWrap() bool
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
} }
type Window interface { type Window interface {
@@ -334,12 +336,16 @@ var (
ColInfo ColorPair ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColBorder ColorPair ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
return &ColorTheme{ return &ColorTheme{
Fg: colUndefined, Fg: colUndefined,
Bg: colUndefined, Bg: colUndefined,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colUndefined, DarkBg: colUndefined,
Gutter: colUndefined, Gutter: colUndefined,
Prompt: colUndefined, Prompt: colUndefined,
@@ -363,8 +369,10 @@ func init() {
Default16 = &ColorTheme{ Default16 = &ColorTheme{
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colBlack, DarkBg: colBlack,
Gutter: colBlack, Gutter: colUndefined,
Prompt: colBlue, Prompt: colBlue,
Match: colGreen, Match: colGreen,
Current: colYellow, Current: colYellow,
@@ -378,6 +386,8 @@ func init() {
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 236, DarkBg: 236,
Gutter: colUndefined, Gutter: colUndefined,
Prompt: 110, Prompt: 110,
@@ -393,6 +403,8 @@ func init() {
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 251, DarkBg: 251,
Gutter: colUndefined, Gutter: colUndefined,
Prompt: 25, Prompt: 25,
@@ -425,6 +437,8 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
} }
theme.Fg = o(baseTheme.Fg, theme.Fg) theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg) theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
theme.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter)) theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
@@ -461,6 +475,8 @@ func initPalette(theme *ColorTheme) {
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg) ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} else { } else {
ColPrompt = pair(colDefault, colDefault) ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault) ColNormal = pair(colDefault, colDefault)
@@ -475,6 +491,8 @@ func initPalette(theme *ColorTheme) {
ColInfo = pair(colDefault, colDefault) ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault) ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault) ColBorder = pair(colDefault, colDefault)
ColPreview = pair(colDefault, colDefault)
ColPreviewBorder = pair(colDefault, colDefault)
} }
} }

View File

@@ -112,3 +112,13 @@ func DurWithin(
func IsTty() bool { func IsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd()) return isatty.IsTerminal(os.Stdin.Fd())
} }
// Once returns a function that returns the specified boolean value only once
func Once(nextResponse bool) func() bool {
state := nextResponse
return func() bool {
prevState := state
state = false
return prevState
}
}

View File

@@ -20,3 +20,21 @@ func TestContrain(t *testing.T) {
t.Error("Expected", 3) t.Error("Expected", 3)
} }
} }
func TestOnce(t *testing.T) {
o := Once(false)
if o() {
t.Error("Expected: false")
}
if o() {
t.Error("Expected: false")
}
o = Once(true)
if !o() {
t.Error("Expected: true")
}
if o() {
t.Error("Expected: false")
}
}

View File

@@ -1640,6 +1640,43 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 } tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
tmux.until { |lines| !lines[-2].include?('(1/2)') } tmux.until { |lines| !lines[-2].include?('(1/2)') }
end end
def test_reload_even_when_theres_no_match
tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter
tmux.until { |lines| lines.item_count.zero? }
tmux.send_keys :Space
tmux.until { |lines| lines.item_count == 10 }
end
def test_clear_list_when_header_lines_changed_due_to_reload
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
tmux.until { |lines| lines.any? { |line| line.include?('9') } }
tmux.send_keys :Space
tmux.until { |lines| lines.none? { |line| line.include?('9') } }
end
def test_clear_query
tmux.send_keys %(: | #{FZF} --query foo --bind space:clear-query), :Enter
tmux.until { |lines| lines.item_count.zero? }
tmux.until { |lines| lines.last.include?('> foo') }
tmux.send_keys 'C-a', 'bar'
tmux.until { |lines| lines.last.include?('> barfoo') }
tmux.send_keys :Space
tmux.until { |lines| lines.last == '>' }
end
def test_clear_selection
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
tmux.until { |lines| lines.match_count == 100 }
tmux.send_keys :Tab
tmux.until { |lines| lines[-2].include?('(1)') }
tmux.send_keys 'foo'
tmux.until { |lines| lines.match_count.zero? }
tmux.until { |lines| lines[-2].include?('(1)') }
tmux.send_keys :Space
tmux.until { |lines| lines.match_count.zero? }
tmux.until { |lines| !lines[-2].include?('(1)') }
end
end end
module TestShell module TestShell