mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 06:13:47 -05:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30577b0c17 | ||
|
|
212de25409 | ||
|
|
5da8bbf45a | ||
|
|
aa0e10ead7 | ||
|
|
a9906c7c29 | ||
|
|
9fefe08b3f | ||
|
|
684bfff713 | ||
|
|
3db6b88d82 | ||
|
|
8ae96774df | ||
|
|
f68017d21e | ||
|
|
2b725a4db5 | ||
|
|
af1a5f130b | ||
|
|
86e3994e87 | ||
|
|
1e6ac5590e | ||
|
|
5e42b1c9f8 | ||
|
|
9d842630c9 | ||
|
|
77cb906dfe | ||
|
|
a59e846f74 | ||
|
|
6e6340a0c9 | ||
|
|
357e82e51b | ||
|
|
394d8cfd18 | ||
|
|
ef80bd401f | ||
|
|
f51d61d57a | ||
|
|
1dd256a68a | ||
|
|
85644aa3fb | ||
|
|
effbc258bb | ||
|
|
e615600ff1 | ||
|
|
60465c4664 | ||
|
|
c03c058bd5 | ||
|
|
7238c8944d | ||
|
|
9a41fd5327 | ||
|
|
b471042037 | ||
|
|
2886f06977 | ||
|
|
d630484eeb | ||
|
|
e24299239e | ||
|
|
d2fa470165 | ||
|
|
168453da71 | ||
|
|
23a06d63ac | ||
|
|
751aa1944a | ||
|
|
05b5f3f845 | ||
|
|
16fc6862a8 | ||
|
|
7e1c0f39e7 | ||
|
|
deccf20a35 | ||
|
|
73c0a645e0 | ||
|
|
e975bd0c8d | ||
|
|
78da928727 | ||
|
|
11962dabba | ||
|
|
dceb5d09cd | ||
|
|
b4cccf23d4 | ||
|
|
b911af200c | ||
|
|
68683c444f | ||
|
|
a185593d65 | ||
|
|
525040238e | ||
|
|
33f89a08f3 | ||
|
|
11645e1fac | ||
|
|
6390140539 | ||
|
|
072066c49c | ||
|
|
a2e9366c84 | ||
|
|
391669a451 | ||
|
|
0c6c76e081 | ||
|
|
f1520bdde6 | ||
|
|
3089880f18 | ||
|
|
ab11b74be4 | ||
|
|
a5a97be017 | ||
|
|
80b5bc1b68 | ||
|
|
5c7dcaffe8 | ||
|
|
5095899245 | ||
|
|
4800e5d2ae | ||
|
|
3b1e37f718 | ||
|
|
6577388250 | ||
|
|
3b9dbd4146 | ||
|
|
a1260feeed | ||
|
|
7322504ad0 | ||
|
|
de569f0052 | ||
|
|
e7097a9d25 | ||
|
|
c1dbc800e5 | ||
|
|
951746297e | ||
|
|
984304568d | ||
|
|
723217bdea | ||
|
|
0fdb71f7e4 | ||
|
|
12ce76b56a | ||
|
|
0030d18448 | ||
|
|
0e3e6ac442 | ||
|
|
430e8193e0 | ||
|
|
03e8ed4d88 | ||
|
|
ef492f6178 | ||
|
|
8eea45ef50 |
30
.github/ISSUE_TEMPLATE.md
vendored
30
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,30 +1,22 @@
|
|||||||
|
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
|
||||||
|
|
||||||
<!-- Check all that apply [x] -->
|
<!-- Check all that apply [x] -->
|
||||||
- Category
|
|
||||||
- [ ] fzf binary
|
- [ ] I have read through the manual page (`man fzf`)
|
||||||
- [ ] fzf-tmux script
|
- [ ] I have the latest version of fzf
|
||||||
- [ ] Key bindings
|
- [ ] I have searched through the existing issues
|
||||||
- [ ] Completion
|
|
||||||
- [ ] Vim
|
## Info
|
||||||
- [ ] Neovim
|
|
||||||
- [ ] Etc.
|
|
||||||
- OS
|
- OS
|
||||||
- [ ] Linux
|
- [ ] Linux
|
||||||
- [ ] Mac OS X
|
- [ ] Mac OS X
|
||||||
- [ ] Windows
|
- [ ] Windows
|
||||||
- [ ] Windows Subsystem for Linux
|
|
||||||
- [ ] Etc.
|
- [ ] Etc.
|
||||||
- Shell
|
- Shell
|
||||||
- [ ] bash
|
- [ ] bash
|
||||||
- [ ] zsh
|
- [ ] zsh
|
||||||
- [ ] fish
|
- [ ] fish
|
||||||
|
|
||||||
<!--
|
## Problem / Steps to reproduce
|
||||||
### Before submitting
|
|
||||||
|
|
||||||
- Make sure that you have the latest version of fzf
|
|
||||||
- If you use tmux, make sure $TERM starts with "screen"
|
|
||||||
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
|
||||||
|
|
||||||
Describe your problem or suggestion from here ...
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ Gemfile.lock
|
|||||||
doc/tags
|
doc/tags
|
||||||
vendor
|
vendor
|
||||||
gopath
|
gopath
|
||||||
|
*.zwc
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- stage: unittest
|
- stage: unittest
|
||||||
go: "1.11.x"
|
go: "1.13.x"
|
||||||
script: make && make test
|
script: make && make test
|
||||||
|
|
||||||
- stage: cli
|
- stage: cli
|
||||||
go: "1.11.x"
|
go: "1.13.x"
|
||||||
rvm: "2.5"
|
rvm: "2.5"
|
||||||
script: |
|
script: |
|
||||||
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
||||||
|
|||||||
88
CHANGELOG.md
88
CHANGELOG.md
@@ -1,6 +1,94 @@
|
|||||||
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
|
||||||
|
------
|
||||||
|
|
||||||
|
- 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
|
||||||
|
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
|
||||||
|
more about it.
|
||||||
|
```sh
|
||||||
|
# Using fzf as the selector interface for ripgrep
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="foo"
|
||||||
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY' || true" \
|
||||||
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
||||||
|
--ansi --phony --query "$INITIAL_QUERY"
|
||||||
|
```
|
||||||
|
- `--multi` now takes an optional integer argument which indicates the maximum
|
||||||
|
number of items that can be selected
|
||||||
|
```sh
|
||||||
|
seq 100 | fzf --multi 3 --reverse --height 50%
|
||||||
|
```
|
||||||
|
- If a placeholder expression for `--preview` and `execute` action (and the
|
||||||
|
new `reload` action) contains `f` flag, it is replaced to the
|
||||||
|
path of a temporary file that holds the evaluated list. This is useful
|
||||||
|
when you multi-select a large number of items and the length of the
|
||||||
|
evaluated string may exceed [`ARG_MAX`][argmax].
|
||||||
|
```sh
|
||||||
|
# Press CTRL-A to select 100K items and see the sum of all the numbers
|
||||||
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \
|
||||||
|
--preview "awk '{sum+=\$1} END {print sum}' {+f}"
|
||||||
|
```
|
||||||
|
- `deselect-all` no longer deselects unmatched items. It is now consistent
|
||||||
|
with `select-all` and `toggle-all` in that it only affects matched items.
|
||||||
|
- Due to the limitation of bash, fuzzy completion is enabled by default for
|
||||||
|
a fixed set of commands. A helper function for easily setting up fuzzy
|
||||||
|
completion for any command is now provided.
|
||||||
|
```sh
|
||||||
|
# usage: _fzf_setup_completion path|dir COMMANDS...
|
||||||
|
_fzf_setup_completion path git kubectl
|
||||||
|
```
|
||||||
|
- Info line style can be changed by `--info=STYLE`
|
||||||
|
- `--info=default`
|
||||||
|
- `--info=inline` (same as old `--inline-info`)
|
||||||
|
- `--info=hidden`
|
||||||
|
- Preview window border can be disabled by adding `noborder` to
|
||||||
|
`--preview-window`.
|
||||||
|
- When you transform the input with `--with-nth`, the trailing white spaces
|
||||||
|
are removed.
|
||||||
|
- `ctrl-\`, `ctrl-]`, `ctrl-^`, and `ctrl-/` can now be used with `--bind`
|
||||||
|
- See https://github.com/junegunn/fzf/milestone/15?closed=1 for more details
|
||||||
|
|
||||||
|
[argmax]: https://unix.stackexchange.com/questions/120642/what-defines-the-maximum-size-for-a-command-single-argument
|
||||||
|
|
||||||
0.18.0
|
0.18.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -42,6 +42,8 @@ else ifeq ($(UNAME_M),armv7l)
|
|||||||
BINARY := $(BINARYARM7)
|
BINARY := $(BINARYARM7)
|
||||||
else ifeq ($(UNAME_M),armv8l)
|
else ifeq ($(UNAME_M),armv8l)
|
||||||
BINARY := $(BINARYARM8)
|
BINARY := $(BINARYARM8)
|
||||||
|
else ifeq ($(UNAME_M),aarch64)
|
||||||
|
BINARY := $(BINARYARM8)
|
||||||
else ifeq ($(UNAME_M),ppc64le)
|
else ifeq ($(UNAME_M),ppc64le)
|
||||||
BINARY := $(BINARYPPC64LE)
|
BINARY := $(BINARYPPC64LE)
|
||||||
else
|
else
|
||||||
|
|||||||
264
README-VIM.md
264
README-VIM.md
@@ -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!
|
||||||
@@ -42,9 +59,6 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
- Customizes fzf colors to match the current color scheme
|
- Customizes fzf colors to match the current color scheme
|
||||||
- `g:fzf_history_dir`
|
- `g:fzf_history_dir`
|
||||||
- Enables history feature
|
- Enables history feature
|
||||||
- `g:fzf_launcher`
|
|
||||||
- (Only in GVim) Terminal emulator to open fzf with
|
|
||||||
- `g:Fzf_launcher` for function reference
|
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
@@ -75,9 +89,10 @@ let g:fzf_layout = { 'down': '~40%' }
|
|||||||
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
||||||
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': '10split enew' }
|
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'],
|
||||||
@@ -93,36 +108,85 @@ 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 with the
|
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||||
following options.
|
dictionary argument, *a spec*, and starts fzf process accordingly. At the very
|
||||||
|
least, specify `sink` option to tell what it should do with the selected
|
||||||
|
entry.
|
||||||
|
|
||||||
| Option name | Type | Description |
|
```vim
|
||||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
call fzf#run({'sink': 'e'})
|
||||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
```
|
||||||
| `source` | list | Vim list as input to fzf |
|
|
||||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||||
| `sink` | funcref | Reference to function to process each selected item |
|
command line without standard input pipe; fzf will use find command (or
|
||||||
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||||
| `options` | string/list | Options to fzf |
|
directory. When you select one, it will open it with the sink, `:e` command.
|
||||||
| `dir` | string | Working directory |
|
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
||||||
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
as the sink.
|
||||||
| `window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
|
||||||
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
|
```vim
|
||||||
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
call fzf#run({'sink': 'tabedit'})
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of using the default find command, you can use any shell command as
|
||||||
|
the source. The following example will list the files managed by git. It's
|
||||||
|
equivalent to running `git ls-files | fzf` on shell.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
" up / down / left / right / window are allowed
|
||||||
|
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
|
||||||
|
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
|
||||||
|
array as the source. In the next example, we pass the names of color
|
||||||
|
schemes as the source to implement a color scheme selector.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||||
|
\ 'fnamemodify(v:val, ":t:r")'),
|
||||||
|
\ 'sink': 'colo', 'left': '25%'})
|
||||||
|
```
|
||||||
|
|
||||||
|
The following table summarizes the available options.
|
||||||
|
|
||||||
|
| Option name | Type | Description |
|
||||||
|
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||||
|
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||||
|
| `source` | list | Vim list as input to fzf |
|
||||||
|
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||||
|
| `sink` | funcref | Reference to function to process each selected item |
|
||||||
|
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||||
|
| `options` | string/list | Options to fzf |
|
||||||
|
| `dir` | string | Working directory |
|
||||||
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
|
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||||
|
|
||||||
`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\\"'})
|
||||||
@@ -132,65 +196,131 @@ call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
|||||||
`fzf#wrap`
|
`fzf#wrap`
|
||||||
----------
|
----------
|
||||||
|
|
||||||
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
We have seen that several aspects of `:FZF` command can be configured with
|
||||||
function that decorates the options dictionary so that it understands
|
a set of global option variables; different ways to open files
|
||||||
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
(`g:fzf_action`), window position and size (`g:fzf_layout`), color palette
|
||||||
`:FZF`.
|
(`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([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
|
||||||
command! -bang MyStuff
|
echo fzf#wrap({'source': 'ls'})
|
||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <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')
|
||||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
GVim
|
#### Hide statusline
|
||||||
----
|
|
||||||
|
|
||||||
With the latest version of GVim, fzf will start inside the builtin terminal
|
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||||
emulator of Vim. Please note that this terminal feature of Vim is still young
|
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||||
and unstable and you may run into some issues.
|
the window.
|
||||||
|
|
||||||
If you have an older version of GVim, you need an external terminal emulator
|
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
|
||||||
to start fzf with. `xterm` command is used by default, but you can customize
|
might want to temporarily disable the statusline for a cleaner look.
|
||||||
it with `g:fzf_launcher`.
|
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" This is the default. %s is replaced with fzf command
|
if has('nvim') && !exists('g:fzf_layout')
|
||||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
autocmd! FileType fzf
|
||||||
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
" Use urxvt instead
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
|
endif
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
|
|
||||||
launcher. Refer to the [this wiki page][macvim-iterm2] to see how to set up.
|
|
||||||
|
|
||||||
[macvim-iterm2]: https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
|
|
||||||
|
|
||||||
[License](LICENSE)
|
[License](LICENSE)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2019 Junegunn Choi
|
||||||
|
|||||||
175
README.md
175
README.md
@@ -25,10 +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)
|
|
||||||
* [Fedora](#fedora)
|
|
||||||
* [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)
|
||||||
@@ -37,6 +36,7 @@ Table of Contents
|
|||||||
* [Search syntax](#search-syntax)
|
* [Search syntax](#search-syntax)
|
||||||
* [Environment variables](#environment-variables)
|
* [Environment variables](#environment-variables)
|
||||||
* [Options](#options)
|
* [Options](#options)
|
||||||
|
* [Demo](#demo)
|
||||||
* [Examples](#examples)
|
* [Examples](#examples)
|
||||||
* [fzf-tmux script](#fzf-tmux-script)
|
* [fzf-tmux script](#fzf-tmux-script)
|
||||||
* [Key bindings for command line](#key-bindings-for-command-line)
|
* [Key bindings for command line](#key-bindings-for-command-line)
|
||||||
@@ -54,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)
|
||||||
@@ -102,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
|
||||||
@@ -137,44 +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
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
### 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
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@@ -283,6 +279,13 @@ or `py`.
|
|||||||
|
|
||||||
See the man page (`man fzf`) for the full list of options.
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
|
#### Demo
|
||||||
|
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
||||||
|
|
||||||
|
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
||||||
|
<img src="https://i.imgur.com/vtG8olE.png" width="640">
|
||||||
|
</a>
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -430,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
|
||||||
@@ -495,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
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -558,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
|
||||||
@@ -616,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
|
||||||
|
|||||||
264
doc/fzf.txt
264
doc/fzf.txt
@@ -1,25 +1,47 @@
|
|||||||
fzf.txt fzf Last change: November 19 2017
|
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
|
||||||
fzf inside terminal buffer
|
Tips
|
||||||
Hide statusline
|
fzf inside terminal buffer
|
||||||
GVim
|
Starting fzf in Neovim floating window
|
||||||
|
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([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}{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
|
||||||
|
|
||||||
@@ -28,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
|
||||||
@@ -37,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!
|
||||||
@@ -56,8 +76,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
< Configuration >_____________________________________________________________~
|
< Configuration >_____________________________________________________________~
|
||||||
*fzf-configuration*
|
*fzf-configuration*
|
||||||
|
|
||||||
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir* *g:fzf_launcher*
|
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir*
|
||||||
*g:Fzf_launcher*
|
|
||||||
|
|
||||||
- `g:fzf_action`
|
- `g:fzf_action`
|
||||||
- Customizable extra key bindings for opening selected files in different
|
- Customizable extra key bindings for opening selected files in different
|
||||||
@@ -68,9 +87,6 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
- Customizes fzf colors to match the current color scheme
|
- Customizes fzf colors to match the current color scheme
|
||||||
- `g:fzf_history_dir`
|
- `g:fzf_history_dir`
|
||||||
- Enables history feature
|
- Enables history feature
|
||||||
- `g:fzf_launcher`
|
|
||||||
- (Only in GVim) Terminal emulator to open fzf with
|
|
||||||
- `g:Fzf_launcher` for function reference
|
|
||||||
|
|
||||||
|
|
||||||
Examples~
|
Examples~
|
||||||
@@ -102,9 +118,10 @@ Examples~
|
|||||||
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
||||||
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': '10split enew' }
|
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'],
|
||||||
@@ -120,22 +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 with the
|
*fzf#run*
|
||||||
following options.
|
|
||||||
|
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||||
Option name | Type | Description ~
|
dictionary argument, a spec, and starts fzf process accordingly. At the very
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
least, specify `sink` option to tell what it should do with the selected
|
||||||
|
entry.
|
||||||
|
>
|
||||||
|
call fzf#run({'sink': 'e'})
|
||||||
|
<
|
||||||
|
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||||
|
command line without standard input pipe; fzf will use find command (or
|
||||||
|
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||||
|
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'})
|
||||||
|
<
|
||||||
|
Instead of using the default find command, you can use any shell command as
|
||||||
|
the source. The following example will list the files managed by git. It's
|
||||||
|
equivalent to running `git ls-files | fzf` on shell.
|
||||||
|
>
|
||||||
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
|
<
|
||||||
|
fzf options can be specified as `options` entry in spec dictionary.
|
||||||
|
>
|
||||||
|
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
|
||||||
|
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})
|
||||||
|
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
|
||||||
|
array as the source. In the next example, we pass the names of color schemes
|
||||||
|
as the source to implement a color scheme selector.
|
||||||
|
>
|
||||||
|
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||||
|
\ 'fnamemodify(v:val, ":t:r")'),
|
||||||
|
\ 'sink': 'colo', 'left': '25%'})
|
||||||
|
<
|
||||||
|
The following table summarizes the available options.
|
||||||
|
|
||||||
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
|
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` )
|
||||||
@@ -143,84 +202,143 @@ following 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.
|
|
||||||
>
|
>
|
||||||
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#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
*fzf#wrap*
|
||||||
function that decorates the options dictionary so that it understands
|
|
||||||
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
We have seen that several aspects of `:FZF` command can be configured with a
|
||||||
`:FZF`.
|
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([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:
|
||||||
>
|
>
|
||||||
command! -bang MyStuff
|
echo fzf#wrap({'source': 'ls'})
|
||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
<
|
||||||
|
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.
|
||||||
|
|
||||||
|
To make it easier to use, let's define `LS` command.
|
||||||
|
>
|
||||||
|
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>).
|
||||||
|
>
|
||||||
|
" 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.
|
||||||
|
>
|
||||||
|
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
|
||||||
- `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~
|
||||||
|
*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 `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.
|
||||||
>
|
>
|
||||||
autocmd! FileType fzf
|
if has('nvim') && !exists('g:fzf_layout')
|
||||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
autocmd! FileType fzf
|
||||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
endif
|
||||||
<
|
<
|
||||||
|
|
||||||
GVIM *fzf-gvim*
|
|
||||||
==============================================================================
|
|
||||||
|
|
||||||
With the latest version of GVim, fzf will start inside the builtin terminal
|
|
||||||
emulator of Vim. Please note that this terminal feature of Vim is still young
|
|
||||||
and unstable and you may run into some issues.
|
|
||||||
|
|
||||||
If you have an older version of GVim, you need an external terminal emulator
|
|
||||||
to start fzf with. `xterm` command is used by default, but you can customize
|
|
||||||
it with `g:fzf_launcher`.
|
|
||||||
>
|
|
||||||
" This is the default. %s is replaced with fzf command
|
|
||||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
|
||||||
|
|
||||||
" Use urxvt instead
|
|
||||||
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
|
|
||||||
<
|
|
||||||
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
|
|
||||||
launcher. Refer to the {this wiki page}{3} to see how to set up.
|
|
||||||
|
|
||||||
{3} https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
|
|
||||||
|
|
||||||
|
|
||||||
LICENSE *fzf-license*
|
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:
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -12,6 +12,8 @@ require (
|
|||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
|
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
|
||||||
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9 // indirect
|
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
|
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -20,7 +20,7 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpke
|
|||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
|
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
|
||||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9 h1:wFe/9vW2TmDagagfMeC56pEcmhyMWEqvuwE9CDAePNo=
|
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
||||||
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
|
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
|
||||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
6
install
6
install
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.18.0
|
version=0.20.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
binary_arch=
|
binary_arch=
|
||||||
allow_legacy=
|
|
||||||
shells="bash zsh fish"
|
shells="bash zsh fish"
|
||||||
prefix='~/.fzf'
|
prefix='~/.fzf'
|
||||||
prefix_expand=~/.fzf
|
prefix_expand=~/.fzf
|
||||||
@@ -45,7 +44,6 @@ for opt in "$@"; do
|
|||||||
auto_completion=1
|
auto_completion=1
|
||||||
key_bindings=1
|
key_bindings=1
|
||||||
update_config=1
|
update_config=1
|
||||||
allow_legacy=1
|
|
||||||
;;
|
;;
|
||||||
--xdg)
|
--xdg)
|
||||||
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||||
@@ -198,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
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2019 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -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 "Mar 2019" "fzf 0.18.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
|
||||||
|
|||||||
313
man/man1/fzf.1
313
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2019 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -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 "Mar 2019" "fzf 0.18.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
|
||||||
@@ -70,6 +70,10 @@ Transform the presentation of each line using field index expressions
|
|||||||
.TP
|
.TP
|
||||||
.BI "-d, --delimiter=" "STR"
|
.BI "-d, --delimiter=" "STR"
|
||||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
||||||
|
.TP
|
||||||
|
.BI "--phony"
|
||||||
|
Do not perform search. With this option, fzf becomes a simple selector
|
||||||
|
interface rather than a "fuzzy finder".
|
||||||
.SS Search result
|
.SS Search result
|
||||||
.TP
|
.TP
|
||||||
.B "+s, --no-sort"
|
.B "+s, --no-sort"
|
||||||
@@ -79,7 +83,8 @@ Do not sort the result
|
|||||||
Reverse the order of the input
|
Reverse the order of the input
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBhistory | fzf --tac --no-sort\fR
|
e.g.
|
||||||
|
\fBhistory | fzf --tac --no-sort\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--tiebreak=" "CRI[,..]"
|
.BI "--tiebreak=" "CRI[,..]"
|
||||||
@@ -109,7 +114,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.SS Interface
|
.SS Interface
|
||||||
.TP
|
.TP
|
||||||
.B "-m, --multi"
|
.B "-m, --multi"
|
||||||
Enable multi-select with tab/shift-tab
|
Enable multi-select with tab/shift-tab. It optionally takes an integer argument
|
||||||
|
which denotes the maximum number of items that can be selected.
|
||||||
.TP
|
.TP
|
||||||
.B "+m, --no-multi"
|
.B "+m, --no-multi"
|
||||||
Disable multi-select
|
Disable multi-select
|
||||||
@@ -118,8 +124,8 @@ Disable multi-select
|
|||||||
Disable mouse
|
Disable mouse
|
||||||
.TP
|
.TP
|
||||||
.BI "--bind=" "KEYBINDS"
|
.BI "--bind=" "KEYBINDS"
|
||||||
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
|
Comma-separated list of custom key bindings. See \fBKEY/EVENT BINDINGS\fR for
|
||||||
details.
|
the details.
|
||||||
.TP
|
.TP
|
||||||
.B "--cycle"
|
.B "--cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
@@ -201,12 +207,26 @@ terminal size with \fB%\fR suffix.
|
|||||||
.br
|
.br
|
||||||
|
|
||||||
.br
|
.br
|
||||||
e.g. \fBfzf --margin 10%\fR
|
e.g.
|
||||||
\fBfzf --margin 1,5%\fR
|
\fBfzf --margin 10%
|
||||||
|
fzf --margin 1,5%\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--inline-info"
|
.BI "--info=" "STYLE"
|
||||||
Display finder info inline with the query
|
Determines the display style of finder info.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Display on the next line to the prompt"
|
||||||
|
.br
|
||||||
|
.BR inline " Display on the same line"
|
||||||
|
.br
|
||||||
|
.BR hidden " Do not display finder info"
|
||||||
|
.br
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "--no-info"
|
||||||
|
A synonym for \fB--info=hidden\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
@@ -235,11 +255,6 @@ color mappings. Ansi color code of -1 denotes terminal default
|
|||||||
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
||||||
format.
|
format.
|
||||||
|
|
||||||
.RS
|
|
||||||
e.g. \fBfzf --color=bg+:24\fR
|
|
||||||
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B BASE SCHEME:
|
.B BASE SCHEME:
|
||||||
(default: dark on 256-color terminal, otherwise 16)
|
(default: dark on 256-color terminal, otherwise 16)
|
||||||
@@ -247,23 +262,38 @@ e.g. \fBfzf --color=bg+:24\fR
|
|||||||
\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
|
||||||
\fBhl \fRHighlighted substrings
|
\fBpreview-fg \fRPreview window text
|
||||||
\fBfg+ \fRText (current line)
|
\fBpreview-bg \fRPreview window background
|
||||||
\fBbg+ \fRBackground (current line)
|
\fBhl \fRHighlighted substrings
|
||||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
\fBfg+ \fRText (current line)
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
\fBinfo \fRInfo
|
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
\fBprompt \fRPrompt
|
\fBinfo \fRInfo
|
||||||
\fBpointer \fRPointer to the current line
|
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||||
\fBmarker \fRMulti-select marker
|
\fBprompt \fRPrompt
|
||||||
\fBspinner \fRStreaming input indicator
|
\fBpointer \fRPointer to the current line
|
||||||
\fBheader \fRHeader
|
\fBmarker \fRMulti-select marker
|
||||||
|
\fBspinner \fRStreaming input indicator
|
||||||
|
\fBheader \fRHeader
|
||||||
|
|
||||||
|
.B EXAMPLES:
|
||||||
|
|
||||||
|
\fB# Seoul256 theme with 8-bit colors
|
||||||
|
# (https://github.com/junegunn/seoul256.vim)
|
||||||
|
fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\
|
||||||
|
--color='hl:65,fg:252,header:65,fg+:252' \\
|
||||||
|
--color='pointer:161,marker:168,prompt:110,hl+:108'
|
||||||
|
|
||||||
|
# Seoul256 theme with 24-bit colors
|
||||||
|
fzf --color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
||||||
|
--color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
||||||
|
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--no-bold"
|
.B "--no-bold"
|
||||||
@@ -291,8 +321,9 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
|
|||||||
EXPRESSION\fR for the details).
|
EXPRESSION\fR for the details).
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview='head -$LINES {}'\fR
|
e.g.
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\fBfzf --preview='head -$LINES {}'
|
||||||
|
ls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||||
|
|
||||||
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
|
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
|
||||||
they represent the exact size of the preview window. (It also overrides
|
they represent the exact size of the preview window. (It also overrides
|
||||||
@@ -304,8 +335,9 @@ A placeholder expression starting with \fB+\fR flag will be replaced to the
|
|||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
|
|
||||||
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
e.g.
|
||||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
\fBfzf --multi --preview='head -10 {+}'
|
||||||
|
git log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
When using a field index expression, leading and trailing whitespace is stripped
|
When using a field index expression, leading and trailing whitespace is stripped
|
||||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||||
@@ -314,14 +346,25 @@ Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
|
|||||||
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
||||||
all index numbers when multiple lines are selected.
|
all index numbers when multiple lines are selected.
|
||||||
|
|
||||||
|
A placeholder expression with \fBf\fR flag is replaced to the path of
|
||||||
|
a temporary file that holds the evaluated list. This is useful when you
|
||||||
|
multi-select a large number of items and the length of the evaluated string may
|
||||||
|
exceed \fBARG_MAX\fR.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
||||||
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||||
|
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
Preview window will be updated even when there is no match for the current
|
Preview window will be updated even when there is no match for the current
|
||||||
query if any of the placeholder expressions evaluates to a non-empty string.
|
query if any of the placeholder expressions evaluates to a non-empty string.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]"
|
||||||
Determine the layout of the preview window. If the argument ends with
|
Determines the layout of the preview window. If the argument contains
|
||||||
\fB:hidden\fR, the preview window will be hidden by default until
|
\fB:hidden\fR, the preview window will be hidden by default until
|
||||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||||
Line wrap can be enabled with \fB:wrap\fR flag.
|
Line wrap can be enabled with \fB:wrap\fR flag.
|
||||||
@@ -338,8 +381,9 @@ execute the command in the background.
|
|||||||
.RE
|
.RE
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
|
e.g.
|
||||||
\fBfzf --preview="file {}" --preview-window=down:1\fR
|
\fBfzf --preview="head {}" --preview-window=up:30%
|
||||||
|
fzf --preview="file {}" --preview-window=down:1\fR
|
||||||
.RE
|
.RE
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
@@ -369,7 +413,8 @@ times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
|
|||||||
list.
|
list.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
e.g.
|
||||||
|
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
@@ -475,56 +520,110 @@ query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
|
|||||||
|
|
||||||
e.g. \fB^core go$ | rb$ | py$\fR
|
e.g. \fB^core go$ | rb$ | py$\fR
|
||||||
|
|
||||||
.SH KEY BINDINGS
|
.SH KEY/EVENT BINDINGS
|
||||||
You can customize key bindings of fzf with \fB--bind\fR option which takes
|
\fB--bind\fR option allows you to bind \fBa key\fR or \fBan event\fR to one or
|
||||||
a comma-separated list of key binding expressions. Each key binding expression
|
more \fBactions\fR. You can use it to customize key bindings or implement
|
||||||
follows the following format: \fBKEY:ACTION\fR
|
dynamic behaviors.
|
||||||
|
|
||||||
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
\fB--bind\fR takes a comma-separated list of binding expressions. Each binding
|
||||||
|
expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR.
|
||||||
|
|
||||||
.B AVAILABLE KEYS: (SYNONYMS)
|
e.g.
|
||||||
\fIctrl-[a-z]\fR
|
\fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||||
\fIctrl-space\fR
|
|
||||||
\fIctrl-alt-[a-z]\fR
|
|
||||||
\fIalt-[a-z]\fR
|
|
||||||
\fIalt-[0-9]\fR
|
|
||||||
\fIf[1-12]\fR
|
|
||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
|
||||||
\fIspace\fR
|
|
||||||
\fIbspace\fR (\fIbs\fR)
|
|
||||||
\fIalt-up\fR
|
|
||||||
\fIalt-down\fR
|
|
||||||
\fIalt-left\fR
|
|
||||||
\fIalt-right\fR
|
|
||||||
\fIalt-enter\fR
|
|
||||||
\fIalt-space\fR
|
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
|
||||||
\fIalt-/\fR
|
|
||||||
\fItab\fR
|
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
|
||||||
\fIesc\fR
|
|
||||||
\fIdel\fR
|
|
||||||
\fIup\fR
|
|
||||||
\fIdown\fR
|
|
||||||
\fIleft\fR
|
|
||||||
\fIright\fR
|
|
||||||
\fIhome\fR
|
|
||||||
\fIend\fR
|
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
|
||||||
\fIshift-up\fR
|
|
||||||
\fIshift-down\fR
|
|
||||||
\fIshift-left\fR
|
|
||||||
\fIshift-right\fR
|
|
||||||
\fIleft-click\fR
|
|
||||||
\fIright-click\fR
|
|
||||||
\fIdouble-click\fR
|
|
||||||
or any single character
|
|
||||||
|
|
||||||
Additionally, a special event named \fIchange\fR is available which is
|
.SS AVAILABLE KEYS: (SYNONYMS)
|
||||||
triggered whenever the query string is changed.
|
\fIctrl-[a-z]\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-space\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-\\\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-]\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||||
|
.br
|
||||||
|
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||||
|
.br
|
||||||
|
\fIctrl-alt-[a-z]\fR
|
||||||
|
.br
|
||||||
|
\fIalt-[a-z]\fR
|
||||||
|
.br
|
||||||
|
\fIalt-[0-9]\fR
|
||||||
|
.br
|
||||||
|
\fIf[1-12]\fR
|
||||||
|
.br
|
||||||
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
|
.br
|
||||||
|
\fIspace\fR
|
||||||
|
.br
|
||||||
|
\fIbspace\fR (\fIbs\fR)
|
||||||
|
.br
|
||||||
|
\fIalt-up\fR
|
||||||
|
.br
|
||||||
|
\fIalt-down\fR
|
||||||
|
.br
|
||||||
|
\fIalt-left\fR
|
||||||
|
.br
|
||||||
|
\fIalt-right\fR
|
||||||
|
.br
|
||||||
|
\fIalt-enter\fR
|
||||||
|
.br
|
||||||
|
\fIalt-space\fR
|
||||||
|
.br
|
||||||
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
|
.br
|
||||||
|
\fIalt-/\fR
|
||||||
|
.br
|
||||||
|
\fItab\fR
|
||||||
|
.br
|
||||||
|
\fIbtab\fR (\fIshift-tab\fR)
|
||||||
|
.br
|
||||||
|
\fIesc\fR
|
||||||
|
.br
|
||||||
|
\fIdel\fR
|
||||||
|
.br
|
||||||
|
\fIup\fR
|
||||||
|
.br
|
||||||
|
\fIdown\fR
|
||||||
|
.br
|
||||||
|
\fIleft\fR
|
||||||
|
.br
|
||||||
|
\fIright\fR
|
||||||
|
.br
|
||||||
|
\fIhome\fR
|
||||||
|
.br
|
||||||
|
\fIend\fR
|
||||||
|
.br
|
||||||
|
\fIpgup\fR (\fIpage-up\fR)
|
||||||
|
.br
|
||||||
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
|
.br
|
||||||
|
\fIshift-up\fR
|
||||||
|
.br
|
||||||
|
\fIshift-down\fR
|
||||||
|
.br
|
||||||
|
\fIshift-left\fR
|
||||||
|
.br
|
||||||
|
\fIshift-right\fR
|
||||||
|
.br
|
||||||
|
\fIleft-click\fR
|
||||||
|
.br
|
||||||
|
\fIright-click\fR
|
||||||
|
.br
|
||||||
|
\fIdouble-click\fR
|
||||||
|
.br
|
||||||
|
or any single character
|
||||||
|
|
||||||
e.g. \fBfzf --bind change:top\fR
|
.SS AVAILABLE EVENTS:
|
||||||
|
\fIchange\fR (triggered whenever the query string is changed)
|
||||||
|
.br
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
|
||||||
|
fzf --bind change:top\fR
|
||||||
|
|
||||||
|
.SS AVAILABLE ACTIONS:
|
||||||
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
@@ -535,11 +634,13 @@ triggered whenever the query string is changed.
|
|||||||
\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)
|
||||||
@@ -563,10 +664,11 @@ triggered whenever the query string is changed.
|
|||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
|
\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)
|
||||||
@@ -580,9 +682,14 @@ triggered whenever the query string is changed.
|
|||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
Multiple actions can be chained using \fB+\fR separator.
|
Multiple actions can be chained using \fB+\fR separator.
|
||||||
|
|
||||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
e.g.
|
||||||
|
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||||
|
|
||||||
|
.SS COMMAND EXECUTION
|
||||||
|
|
||||||
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
||||||
leaving fzf. For example, you can turn fzf into a simple file browser by
|
leaving fzf. For example, you can turn fzf into a simple file browser by
|
||||||
@@ -611,9 +718,9 @@ parse errors.
|
|||||||
\fBexecute|...|\fR
|
\fBexecute|...|\fR
|
||||||
\fBexecute:...\fR
|
\fBexecute:...\fR
|
||||||
.RS
|
.RS
|
||||||
This is the special form that frees you from parse errors as it does not expect
|
The last one is the special form that frees you from parse errors as it does
|
||||||
the closing character. The catch is that it should be the last one in the
|
not expect the closing character. The catch is that it should be the last one
|
||||||
comma-separated list of key-action pairs.
|
in the comma-separated list of key-action pairs.
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
fzf switches to the alternate screen when executing a command. However, if the
|
fzf switches to the alternate screen when executing a command. However, if the
|
||||||
@@ -623,6 +730,26 @@ executes the command without the switching. Note that fzf will not be
|
|||||||
responsive until the command is complete. For asynchronous execution, start
|
responsive until the command is complete. For asynchronous execution, start
|
||||||
your command as a background process (i.e. appending \fB&\fR).
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
|
.SS RELOAD INPUT
|
||||||
|
|
||||||
|
\fBreload(...)\fR action is used to dynamically update the input list
|
||||||
|
without restarting fzf. It takes the same command template with placeholder
|
||||||
|
expressions as \fBexecute(...)\fR.
|
||||||
|
|
||||||
|
See \fIhttps://github.com/junegunn/fzf/issues/1750\fR for more info.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Update the list of processes by pressing CTRL-R
|
||||||
|
ps -ef | fzf --bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \\
|
||||||
|
--header-lines=1 --layout=reverse
|
||||||
|
|
||||||
|
# Integration with ripgrep
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="foobar"
|
||||||
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
|
||||||
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||||
|
--ansi --phony --query "$INITIAL_QUERY"\fR
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +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)
|
||||||
return map(['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
return map([
|
||||||
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
\ '@echo off',
|
||||||
\ ['chcp %origchcp% > nul'], 'v:val."\r"')
|
\ 'setlocal enabledelayedexpansion']
|
||||||
|
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||||
|
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
||||||
|
\ + ['endlocal'],
|
||||||
|
\ '<SID>enc_to_cp(v:val."\r")')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
let s:term_marker = ";#FZF"
|
let s:term_marker = ";#FZF"
|
||||||
@@ -64,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)
|
||||||
@@ -75,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
|
||||||
@@ -239,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
|
||||||
@@ -254,7 +266,7 @@ endfunction
|
|||||||
function! s:defaults()
|
function! s:defaults()
|
||||||
let rules = copy(get(g:, 'fzf_colors', {}))
|
let rules = copy(get(g:, 'fzf_colors', {}))
|
||||||
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
|
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
|
||||||
return empty(colors) ? '' : ('--color='.colors)
|
return empty(colors) ? '' : fzf#shellescape('--color='.colors)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:validate_layout(layout)
|
function! s:validate_layout(layout)
|
||||||
@@ -334,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() }
|
||||||
@@ -377,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'
|
||||||
@@ -416,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
|
||||||
|
|
||||||
@@ -501,12 +515,12 @@ function! s:dopopd()
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:xterm_launcher()
|
function! s:xterm_launcher()
|
||||||
let fmt = 'xterm -T "[fzf]" -bg "\%s" -fg "\%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
|
let fmt = 'xterm -T "[fzf]" -bg "%s" -fg "%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
|
||||||
if has('gui_macvim')
|
if has('gui_macvim')
|
||||||
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
|
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
|
||||||
endif
|
endif
|
||||||
return printf(fmt,
|
return printf(fmt,
|
||||||
\ synIDattr(hlID("Normal"), "bg"), synIDattr(hlID("Normal"), "fg"),
|
\ escape(synIDattr(hlID("Normal"), "bg"), '#'), escape(synIDattr(hlID("Normal"), "fg"), '#'),
|
||||||
\ &columns, &lines/2, getwinposx(), getwinposy())
|
\ &columns, &lines/2, getwinposx(), getwinposy())
|
||||||
endfunction
|
endfunction
|
||||||
unlet! s:launcher
|
unlet! s:launcher
|
||||||
@@ -519,6 +533,10 @@ endif
|
|||||||
function! s:exit_handler(code, command, ...)
|
function! s:exit_handler(code, command, ...)
|
||||||
if a:code == 130
|
if a:code == 130
|
||||||
return 0
|
return 0
|
||||||
|
elseif has('nvim') && a:code == 129
|
||||||
|
" When deleting the terminal buffer while fzf is still running,
|
||||||
|
" Nvim sends SIGHUP.
|
||||||
|
return 0
|
||||||
elseif a:code > 1
|
elseif a:code > 1
|
||||||
call s:error('Error running ' . a:command)
|
call s:error('Error running ' . a:command)
|
||||||
if !empty(a:000)
|
if !empty(a:000)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -123,11 +123,11 @@ _fzf_handle_dynamic_completion() {
|
|||||||
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
||||||
$orig "$@"
|
$orig "$@"
|
||||||
elif [ -n "$_fzf_completion_loader" ]; then
|
elif [ -n "$_fzf_completion_loader" ]; then
|
||||||
orig_complete=$(complete -p "$cmd" 2> /dev/null)
|
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
# _completion_loader may not have updated completion for the command
|
# _completion_loader may not have updated completion for the command
|
||||||
if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||||
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
||||||
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||||
eval "${orig_complete/ -F / -o nospace -F }"
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
@@ -195,12 +195,13 @@ _fzf_complete() {
|
|||||||
|
|
||||||
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
|
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
printf '\e[5n'
|
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
return 0
|
else
|
||||||
|
COMPREPLY=("$cur")
|
||||||
fi
|
fi
|
||||||
|
printf '\e[5n'
|
||||||
|
return 0
|
||||||
else
|
else
|
||||||
shift
|
shift
|
||||||
_fzf_handle_dynamic_completion "$cmd" "$@"
|
_fzf_handle_dynamic_completion "$cmd" "$@"
|
||||||
@@ -243,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
|
||||||
@@ -292,7 +293,7 @@ if type _completion_loader > /dev/null 2>&1; then
|
|||||||
_fzf_completion_loader=1
|
_fzf_completion_loader=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_fzf_defc() {
|
__fzf_defc() {
|
||||||
local cmd func opts orig_var orig def
|
local cmd func opts orig_var orig def
|
||||||
cmd="$1"
|
cmd="$1"
|
||||||
func="$2"
|
func="$2"
|
||||||
@@ -309,16 +310,14 @@ _fzf_defc() {
|
|||||||
|
|
||||||
# Anything
|
# Anything
|
||||||
for cmd in $a_cmds; do
|
for cmd in $a_cmds; do
|
||||||
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
|
__fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Directory
|
# Directory
|
||||||
for cmd in $d_cmds; do
|
for cmd in $d_cmds; do
|
||||||
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
unset _fzf_defc
|
|
||||||
|
|
||||||
# Kill completion
|
# Kill completion
|
||||||
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
|
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
|
||||||
|
|
||||||
@@ -333,4 +332,22 @@ complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
|||||||
|
|
||||||
unset cmd d_cmds a_cmds x_cmds
|
unset cmd d_cmds a_cmds x_cmds
|
||||||
|
|
||||||
|
_fzf_setup_completion() {
|
||||||
|
local kind fn cmd
|
||||||
|
kind=$1
|
||||||
|
fn=_fzf_${1}_completion
|
||||||
|
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
||||||
|
echo "usage: ${FUNCNAME[0]} path|dir COMMANDS..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
for cmd in "$@"; do
|
||||||
|
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||||
|
case "$kind" in
|
||||||
|
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||||
|
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -158,6 +158,12 @@ fzf-completion() {
|
|||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
||||||
|
|
||||||
|
# When the trigger starts with ';', it becomes a separate token
|
||||||
|
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
|
||||||
|
tokens[-2]="${tokens[-2]}${tokens[-1]}"
|
||||||
|
tokens=(${tokens[0,-2]})
|
||||||
|
fi
|
||||||
|
|
||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
# Kill completion (do not require trigger sequence)
|
||||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ __fzf_history__() (
|
|||||||
local line
|
local line
|
||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= builtin history |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||||
command grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function fzf_key_bindings
|
|||||||
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
||||||
# before 2.4.0.
|
# before 2.4.0.
|
||||||
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
||||||
history -z | eval (__fzfcmd) --read0 -q '(commandline)' | perl -pe 'chomp if eof' | read -lz result
|
history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
else
|
else
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ __fsel() {
|
|||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
@@ -49,13 +49,14 @@ zle -N fzf-redraw-prompt
|
|||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
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
|
||||||
@@ -66,7 +67,7 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -rl 1 |
|
selected=( $(fc -rl 1 |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
// The first occurrence of each character in the pattern
|
// The first occurrence of each character in the pattern
|
||||||
offset32, F := alloc32(offset32, slab, M)
|
offset32, F := alloc32(offset32, slab, M)
|
||||||
// Rune array
|
// Rune array
|
||||||
offset32, T := alloc32(offset32, slab, N)
|
_, T := alloc32(offset32, slab, N)
|
||||||
input.CopyRunes(T)
|
input.CopyRunes(T)
|
||||||
|
|
||||||
// Phase 2. Calculate bonus for each point
|
// Phase 2. Calculate bonus for each point
|
||||||
@@ -453,7 +453,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
copy(H, H0[f0:lastIdx+1])
|
copy(H, H0[f0:lastIdx+1])
|
||||||
|
|
||||||
// Possible length of consecutive chunk at each position.
|
// Possible length of consecutive chunk at each position.
|
||||||
offset16, C := alloc16(offset16, slab, width*M)
|
_, C := alloc16(offset16, slab, width*M)
|
||||||
copy(C, C0[f0:lastIdx+1])
|
copy(C, C0[f0:lastIdx+1])
|
||||||
|
|
||||||
Fsub := F[1:]
|
Fsub := F[1:]
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ func (cl *ChunkList) Push(data []byte) bool {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear clears the data
|
||||||
|
func (cl *ChunkList) Clear() {
|
||||||
|
cl.mutex.Lock()
|
||||||
|
cl.chunks = nil
|
||||||
|
cl.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.18.0"
|
version = "0.20.0"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -27,6 +28,7 @@ const (
|
|||||||
spinnerDuration = 200 * time.Millisecond
|
spinnerDuration = 200 * time.Millisecond
|
||||||
previewCancelWait = 500 * time.Millisecond
|
previewCancelWait = 500 * time.Millisecond
|
||||||
maxPatternLength = 300
|
maxPatternLength = 300
|
||||||
|
maxMulti = math.MaxInt32
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
numPartitionsMultiplier = 8
|
numPartitionsMultiplier = 8
|
||||||
|
|||||||
67
src/core.go
67
src/core.go
@@ -126,6 +126,7 @@ func Run(opts *Options, revision string) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||||
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@@ -135,10 +136,11 @@ func Run(opts *Options, revision string) {
|
|||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader := NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero)
|
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource()
|
go reader.ReadSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +184,7 @@ func Run(opts *Options, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero)
|
}, eventBox, opts.ReadZero, false)
|
||||||
reader.ReadSource()
|
reader.ReadSource()
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
@@ -222,11 +224,28 @@ 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
|
||||||
|
restart := func(command string) {
|
||||||
|
reading = true
|
||||||
|
clearCache = util.Once(true)
|
||||||
|
clearSelection = util.Once(true)
|
||||||
|
chunkList.Clear()
|
||||||
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
|
go reader.restart(command)
|
||||||
|
}
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
|
input := func() []rune {
|
||||||
|
if opts.Phony {
|
||||||
|
return []rune{}
|
||||||
|
}
|
||||||
|
return []rune(terminal.Input())
|
||||||
|
}
|
||||||
eventBox.Wait(func(events *util.Events) {
|
eventBox.Wait(func(events *util.Events) {
|
||||||
if _, fin := (*events)[EvtReadFin]; fin {
|
if _, fin := (*events)[EvtReadFin]; fin {
|
||||||
delete(*events, EvtReadNew)
|
delete(*events, EvtReadNew)
|
||||||
@@ -235,21 +254,39 @@ func Run(opts *Options, revision string) {
|
|||||||
switch evt {
|
switch evt {
|
||||||
|
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
reading = reading && evt == EvtReadNew
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
snapshot, count := chunkList.Snapshot()
|
restart(*nextCommand)
|
||||||
terminal.UpdateCount(count, !reading, value.(bool))
|
nextCommand = nil
|
||||||
if opts.Sync {
|
break
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
} else {
|
||||||
|
reading = reading && evt == EvtReadNew
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
snapshot, count := chunkList.Snapshot()
|
||||||
|
terminal.UpdateCount(count, !reading, value.(*string))
|
||||||
|
if opts.Sync {
|
||||||
|
opts.Sync = false
|
||||||
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||||
|
}
|
||||||
|
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
|
var command *string
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case bool:
|
case searchRequest:
|
||||||
sort = val
|
sort = val.sort
|
||||||
|
command = val.command
|
||||||
|
}
|
||||||
|
if command != nil {
|
||||||
|
if reading {
|
||||||
|
reader.terminate()
|
||||||
|
nextCommand = command
|
||||||
|
} else {
|
||||||
|
restart(*command)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
|
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
@@ -259,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) {
|
||||||
@@ -289,7 +328,7 @@ func Run(opts *Options, revision string) {
|
|||||||
terminal.startChan <- true
|
terminal.startChan <- true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val)
|
terminal.UpdateList(val, clearSelection())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (h *History) append(line string) error {
|
|||||||
|
|
||||||
lines := append(h.lines[:len(h.lines)-1], line)
|
lines := append(h.lines[:len(h.lines)-1], line)
|
||||||
if len(lines) > h.maxSize {
|
if len(lines) > h.maxSize {
|
||||||
lines = lines[len(lines)-h.maxSize : len(lines)]
|
lines = lines[len(lines)-h.maxSize:]
|
||||||
}
|
}
|
||||||
h.lines = append(lines, "")
|
h.lines = append(lines, "")
|
||||||
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import (
|
|||||||
|
|
||||||
// MatchRequest represents a search request
|
// MatchRequest represents a search request
|
||||||
type MatchRequest struct {
|
type MatchRequest struct {
|
||||||
chunks []*Chunk
|
chunks []*Chunk
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
|
clearCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
@@ -69,7 +70,7 @@ func (m *Matcher) Loop() {
|
|||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
if request.sort != m.sort {
|
if request.sort != m.sort || request.clearCache {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
@@ -207,13 +208,13 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
return nil, wait()
|
return nil, wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().Sub(startedAt) > progressMinDuration {
|
if time.Since(startedAt) > progressMinDuration {
|
||||||
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
|
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partialResults := make([][]Result, numSlices)
|
partialResults := make([][]Result, numSlices)
|
||||||
for _ = range slices {
|
for range slices {
|
||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
|
||||||
pattern := m.patternBuilder(patternRunes)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@@ -230,5 +231,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
} else {
|
} else {
|
||||||
event = reqRetry
|
event = reqRetry
|
||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
||||||
}
|
}
|
||||||
|
|||||||
121
src/options.go
121
src/options.go
@@ -33,12 +33,13 @@ const usage = `usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--tac Reverse the order of the input
|
--tac Reverse the order of the input
|
||||||
|
--phony Do not perform search
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied [length|begin|end|index]
|
when the scores are tied [length|begin|end|index]
|
||||||
(default: length)
|
(default: length)
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi[=MAX] Enable multi-select with tab/shift-tab
|
||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
@@ -56,7 +57,7 @@ const usage = `usage: fzf [options]
|
|||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border Draw border above and below the finder
|
--border Draw border above and below the finder
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--inline-info Display finder info inline with the query
|
--info=STYLE Finder info style [default|inline|hidden]
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
@@ -141,12 +142,21 @@ const (
|
|||||||
layoutReverseList
|
layoutReverseList
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type infoStyle int
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoDefault infoStyle = iota
|
||||||
|
infoInline
|
||||||
|
infoHidden
|
||||||
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
size sizeSpec
|
size sizeSpec
|
||||||
hidden bool
|
hidden bool
|
||||||
wrap bool
|
wrap bool
|
||||||
|
border bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@@ -154,6 +164,7 @@ type Options struct {
|
|||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
Extended bool
|
Extended bool
|
||||||
|
Phony bool
|
||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
@@ -162,7 +173,7 @@ type Options struct {
|
|||||||
Sort int
|
Sort int
|
||||||
Tac bool
|
Tac bool
|
||||||
Criteria []criterion
|
Criteria []criterion
|
||||||
Multi bool
|
Multi int
|
||||||
Ansi bool
|
Ansi bool
|
||||||
Mouse bool
|
Mouse bool
|
||||||
Theme *tui.ColorTheme
|
Theme *tui.ColorTheme
|
||||||
@@ -175,7 +186,7 @@ type Options struct {
|
|||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InlineInfo bool
|
InfoStyle infoStyle
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
Query string
|
Query string
|
||||||
@@ -189,6 +200,7 @@ type Options struct {
|
|||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
Printer func(string)
|
Printer func(string)
|
||||||
|
PrintSep string
|
||||||
Sync bool
|
Sync bool
|
||||||
History *History
|
History *History
|
||||||
Header []string
|
Header []string
|
||||||
@@ -206,6 +218,7 @@ func defaultOptions() *Options {
|
|||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
Extended: true,
|
Extended: true,
|
||||||
|
Phony: false,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
Normalize: true,
|
Normalize: true,
|
||||||
Nth: make([]Range, 0),
|
Nth: make([]Range, 0),
|
||||||
@@ -214,7 +227,7 @@ func defaultOptions() *Options {
|
|||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Criteria: []criterion{byScore, byLength},
|
Criteria: []criterion{byScore, byLength},
|
||||||
Multi: false,
|
Multi: 0,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Theme: tui.EmptyTheme(),
|
Theme: tui.EmptyTheme(),
|
||||||
@@ -226,7 +239,7 @@ func defaultOptions() *Options {
|
|||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InlineInfo: false,
|
InfoStyle: infoDefault,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
Query: "",
|
Query: "",
|
||||||
@@ -236,10 +249,11 @@ func defaultOptions() *Options {
|
|||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[int]string),
|
||||||
Keymap: make(map[int][]action),
|
Keymap: make(map[int][]action),
|
||||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true},
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
Printer: func(str string) { fmt.Println(str) },
|
Printer: func(str string) { fmt.Println(str) },
|
||||||
|
PrintSep: "\n",
|
||||||
Sync: false,
|
Sync: false,
|
||||||
History: nil,
|
History: nil,
|
||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
@@ -252,7 +266,7 @@ func defaultOptions() *Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func help(code int) {
|
func help(code int) {
|
||||||
os.Stderr.WriteString(usage)
|
os.Stdout.WriteString(usage)
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,13 +326,14 @@ func nextInt(args []string, i *int, message string) int {
|
|||||||
return atoi(args[*i])
|
return atoi(args[*i])
|
||||||
}
|
}
|
||||||
|
|
||||||
func optionalNumeric(args []string, i *int) int {
|
func optionalNumeric(args []string, i *int, defaultValue int) int {
|
||||||
if len(args) > *i+1 {
|
if len(args) > *i+1 {
|
||||||
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
||||||
*i++
|
*i++
|
||||||
|
return atoi(args[*i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 1 // Don't care
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitNth(str string) []Range {
|
func splitNth(str string) []Range {
|
||||||
@@ -383,7 +398,7 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokens := strings.Split(str, ",")
|
tokens := strings.Split(str, ",")
|
||||||
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
|
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
||||||
tokens = append(tokens, ",")
|
tokens = append(tokens, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,6 +426,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.BSpace
|
chord = tui.BSpace
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
chord = tui.CtrlSpace
|
chord = tui.CtrlSpace
|
||||||
|
case "ctrl-^", "ctrl-6":
|
||||||
|
chord = tui.CtrlCaret
|
||||||
|
case "ctrl-/", "ctrl-_":
|
||||||
|
chord = tui.CtrlSlash
|
||||||
|
case "ctrl-\\":
|
||||||
|
chord = tui.CtrlBackSlash
|
||||||
|
case "ctrl-]":
|
||||||
|
chord = tui.CtrlRightBracket
|
||||||
case "change":
|
case "change":
|
||||||
chord = tui.Change
|
chord = tui.Change
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
@@ -574,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+":
|
||||||
@@ -625,13 +652,19 @@ 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)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
`(?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 src[len(prefix)] == '-' {
|
if strings.HasPrefix(src, "+") {
|
||||||
|
symbol = "+"
|
||||||
|
}
|
||||||
|
prefix := symbol + "execute"
|
||||||
|
if strings.HasPrefix(src[1:], "reload") {
|
||||||
|
prefix = symbol + "reload"
|
||||||
|
} 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' {
|
||||||
prefix += "-silent"
|
prefix += "-silent"
|
||||||
@@ -709,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":
|
||||||
@@ -780,10 +817,16 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
errorExit("unknown action: " + spec)
|
if specIndex == 0 && specLower == "" {
|
||||||
|
actions = append(keymap[key], actions...)
|
||||||
|
} else {
|
||||||
|
errorExit("unknown action: " + spec)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
switch t {
|
switch t {
|
||||||
|
case actReload:
|
||||||
|
offset = len("reload")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@@ -819,6 +862,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
prefix = matches[0][2]
|
prefix = matches[0][2]
|
||||||
}
|
}
|
||||||
switch prefix {
|
switch prefix {
|
||||||
|
case "reload":
|
||||||
|
return actReload
|
||||||
case "execute":
|
case "execute":
|
||||||
return actExecute
|
return actExecute
|
||||||
case "execute-silent":
|
case "execute-silent":
|
||||||
@@ -884,6 +929,20 @@ func parseLayout(str string) layoutType {
|
|||||||
return layoutDefault
|
return layoutDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseInfoStyle(str string) infoStyle {
|
||||||
|
switch str {
|
||||||
|
case "default":
|
||||||
|
return infoDefault
|
||||||
|
case "inline":
|
||||||
|
return infoInline
|
||||||
|
case "hidden":
|
||||||
|
return infoHidden
|
||||||
|
default:
|
||||||
|
errorExit("invalid info style (expected: default / inline / hidden)")
|
||||||
|
}
|
||||||
|
return infoDefault
|
||||||
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
// Default
|
// Default
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
@@ -895,6 +954,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
switch token {
|
switch token {
|
||||||
|
case "":
|
||||||
case "hidden":
|
case "hidden":
|
||||||
opts.hidden = true
|
opts.hidden = true
|
||||||
case "wrap":
|
case "wrap":
|
||||||
@@ -907,6 +967,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
opts.position = posLeft
|
opts.position = posLeft
|
||||||
case "right":
|
case "right":
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
|
case "border":
|
||||||
|
opts.border = true
|
||||||
|
case "noborder":
|
||||||
|
opts.border = false
|
||||||
default:
|
default:
|
||||||
if sizeRegex.MatchString(token) {
|
if sizeRegex.MatchString(token) {
|
||||||
opts.size = parseSize(token, 99, "window size")
|
opts.size = parseSize(token, 99, "window size")
|
||||||
@@ -1011,6 +1075,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
case "--no-expect":
|
case "--no-expect":
|
||||||
opts.Expect = make(map[int]string)
|
opts.Expect = make(map[int]string)
|
||||||
|
case "--no-phony":
|
||||||
|
opts.Phony = false
|
||||||
|
case "--phony":
|
||||||
|
opts.Phony = true
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
case "--bind":
|
case "--bind":
|
||||||
@@ -1031,7 +1099,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--with-nth":
|
case "--with-nth":
|
||||||
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
|
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
|
||||||
case "-s", "--sort":
|
case "-s", "--sort":
|
||||||
opts.Sort = optionalNumeric(allArgs, &i)
|
opts.Sort = optionalNumeric(allArgs, &i, 1)
|
||||||
case "+s", "--no-sort":
|
case "+s", "--no-sort":
|
||||||
opts.Sort = 0
|
opts.Sort = 0
|
||||||
case "--tac":
|
case "--tac":
|
||||||
@@ -1043,9 +1111,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "+i":
|
case "+i":
|
||||||
opts.Case = CaseRespect
|
opts.Case = CaseRespect
|
||||||
case "-m", "--multi":
|
case "-m", "--multi":
|
||||||
opts.Multi = true
|
opts.Multi = optionalNumeric(allArgs, &i, maxMulti)
|
||||||
case "+m", "--no-multi":
|
case "+m", "--no-multi":
|
||||||
opts.Multi = false
|
opts.Multi = 0
|
||||||
case "--ansi":
|
case "--ansi":
|
||||||
opts.Ansi = true
|
opts.Ansi = true
|
||||||
case "--no-ansi":
|
case "--no-ansi":
|
||||||
@@ -1085,10 +1153,15 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.FileWord = true
|
opts.FileWord = true
|
||||||
case "--no-filepath-word":
|
case "--no-filepath-word":
|
||||||
opts.FileWord = false
|
opts.FileWord = false
|
||||||
|
case "--info":
|
||||||
|
opts.InfoStyle = parseInfoStyle(
|
||||||
|
nextString(allArgs, &i, "info style required"))
|
||||||
|
case "--no-info":
|
||||||
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
opts.InlineInfo = true
|
opts.InfoStyle = infoInline
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InlineInfo = false
|
opts.InfoStyle = infoDefault
|
||||||
case "--jump-labels":
|
case "--jump-labels":
|
||||||
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
||||||
validateJumpLabels = true
|
validateJumpLabels = true
|
||||||
@@ -1106,8 +1179,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.ReadZero = false
|
opts.ReadZero = false
|
||||||
case "--print0":
|
case "--print0":
|
||||||
opts.Printer = func(str string) { fmt.Print(str, "\x00") }
|
opts.Printer = func(str string) { fmt.Print(str, "\x00") }
|
||||||
|
opts.PrintSep = "\x00"
|
||||||
case "--no-print0":
|
case "--no-print0":
|
||||||
opts.Printer = func(str string) { fmt.Println(str) }
|
opts.Printer = func(str string) { fmt.Println(str) }
|
||||||
|
opts.PrintSep = "\n"
|
||||||
case "--print-query":
|
case "--print-query":
|
||||||
opts.PrintQuery = true
|
opts.PrintQuery = true
|
||||||
case "--no-print-query":
|
case "--no-print-query":
|
||||||
@@ -1141,7 +1216,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Preview.command = ""
|
opts.Preview.command = ""
|
||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden]"))
|
||||||
case "--height":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
@@ -1186,12 +1261,16 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.WithNth = splitNth(value)
|
opts.WithNth = splitNth(value)
|
||||||
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
||||||
opts.Sort = 1 // Don't care
|
opts.Sort = 1 // Don't care
|
||||||
|
} else if match, value := optString(arg, "-m", "--multi="); match {
|
||||||
|
opts.Multi = atoi(value)
|
||||||
} else if match, value := optString(arg, "--height="); match {
|
} else if match, value := optString(arg, "--height="); match {
|
||||||
opts.Height = parseHeight(value)
|
opts.Height = parseHeight(value)
|
||||||
} else if match, value := optString(arg, "--min-height="); match {
|
} else if match, value := optString(arg, "--min-height="); match {
|
||||||
opts.MinHeight = atoi(value)
|
opts.MinHeight = atoi(value)
|
||||||
} else if match, value := optString(arg, "--layout="); match {
|
} else if match, value := optString(arg, "--layout="); match {
|
||||||
opts.Layout = parseLayout(value)
|
opts.Layout = parseLayout(value)
|
||||||
|
} else if match, value := optString(arg, "--info="); match {
|
||||||
|
opts.InfoStyle = parseInfoStyle(value)
|
||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,11 +18,17 @@ type Reader struct {
|
|||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
event int32
|
event int32
|
||||||
|
finChan chan bool
|
||||||
|
mutex sync.Mutex
|
||||||
|
exec *exec.Cmd
|
||||||
|
command *string
|
||||||
|
killed bool
|
||||||
|
wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
|
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -29,9 +37,12 @@ func (r *Reader) startEventPoller() {
|
|||||||
pollInterval := readerPollIntervalMin
|
pollInterval := readerPollIntervalMin
|
||||||
for {
|
for {
|
||||||
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
||||||
r.eventBox.Set(EvtReadNew, true)
|
r.eventBox.Set(EvtReadNew, (*string)(nil))
|
||||||
pollInterval = readerPollIntervalMin
|
pollInterval = readerPollIntervalMin
|
||||||
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
|
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
|
||||||
|
if r.wait {
|
||||||
|
r.finChan <- true
|
||||||
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
pollInterval += readerPollIntervalStep
|
pollInterval += readerPollIntervalStep
|
||||||
@@ -46,7 +57,37 @@ func (r *Reader) startEventPoller() {
|
|||||||
|
|
||||||
func (r *Reader) fin(success bool) {
|
func (r *Reader) fin(success bool) {
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
||||||
r.eventBox.Set(EvtReadFin, success)
|
if r.wait {
|
||||||
|
<-r.finChan
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mutex.Lock()
|
||||||
|
ret := r.command
|
||||||
|
if success || r.killed {
|
||||||
|
ret = nil
|
||||||
|
}
|
||||||
|
r.mutex.Unlock()
|
||||||
|
|
||||||
|
r.eventBox.Set(EvtReadFin, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) terminate() {
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer func() { r.mutex.Unlock() }()
|
||||||
|
|
||||||
|
r.killed = true
|
||||||
|
if r.exec != nil && r.exec.Process != nil {
|
||||||
|
util.KillCommand(r.exec)
|
||||||
|
} else {
|
||||||
|
os.Stdin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) restart(command string) {
|
||||||
|
r.event = int32(EvtReady)
|
||||||
|
r.startEventPoller()
|
||||||
|
success := r.readFromCommand(nil, command)
|
||||||
|
r.fin(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
@@ -54,12 +95,13 @@ func (r *Reader) ReadSource() {
|
|||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
|
// The default command for *nix requires bash
|
||||||
|
shell := "bash"
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
// The default command for *nix requires bash
|
success = r.readFromCommand(&shell, defaultCommand)
|
||||||
success = r.readFromCommand("bash", defaultCommand)
|
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromCommand("sh", cmd)
|
success = r.readFromCommand(nil, cmd)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
@@ -102,16 +144,25 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(shell string, cmd string) bool {
|
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||||
listCommand := util.ExecCommandWith(shell, cmd, false)
|
r.mutex.Lock()
|
||||||
out, err := listCommand.StdoutPipe()
|
r.killed = false
|
||||||
|
r.command = &command
|
||||||
|
if shell != nil {
|
||||||
|
r.exec = util.ExecCommandWith(*shell, command, true)
|
||||||
|
} else {
|
||||||
|
r.exec = util.ExecCommand(command, true)
|
||||||
|
}
|
||||||
|
out, err := r.exec.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
err = listCommand.Start()
|
err = r.exec.Start()
|
||||||
|
r.mutex.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
r.feed(out)
|
r.feed(out)
|
||||||
return listCommand.Wait() == nil
|
return r.exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ import (
|
|||||||
func TestReadFromCommand(t *testing.T) {
|
func TestReadFromCommand(t *testing.T) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
reader := Reader{
|
reader := NewReader(
|
||||||
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eventBox: eb,
|
eb, false, true)
|
||||||
event: int32(EvtReady)}
|
|
||||||
|
|
||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
|
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.fin(reader.readFromCommand("sh", `no-such-command`))
|
reader.fin(reader.readFromCommand(nil, `no-such-command`))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type colorOffset struct {
|
|||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
attr tui.Attr
|
attr tui.Attr
|
||||||
index int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
|
|||||||
327
src/terminal.go
327
src/terminal.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -22,9 +23,11 @@ import (
|
|||||||
// import "github.com/pkg/profile"
|
// import "github.com/pkg/profile"
|
||||||
|
|
||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
|
var activeTempFiles []string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q}|{\\+?n})")
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||||
|
activeTempFiles = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type jumpMode int
|
type jumpMode int
|
||||||
@@ -57,7 +60,7 @@ var emptyLine = itemLine{}
|
|||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
inlineInfo bool
|
infoStyle infoStyle
|
||||||
prompt string
|
prompt string
|
||||||
promptLen int
|
promptLen int
|
||||||
queryLen [2]int
|
queryLen [2]int
|
||||||
@@ -73,7 +76,7 @@ type Terminal struct {
|
|||||||
xoffset int
|
xoffset int
|
||||||
yanked []rune
|
yanked []rune
|
||||||
input []rune
|
input []rune
|
||||||
multi bool
|
multi int
|
||||||
sort bool
|
sort bool
|
||||||
toggleSort bool
|
toggleSort bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
@@ -99,10 +102,11 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
success bool
|
failed *string
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
printer func(string)
|
printer func(string)
|
||||||
|
printsep string
|
||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
version int64
|
version int64
|
||||||
@@ -181,6 +185,8 @@ const (
|
|||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
actClearScreen
|
actClearScreen
|
||||||
|
actClearQuery
|
||||||
|
actClearSelection
|
||||||
actDeleteChar
|
actDeleteChar
|
||||||
actDeleteCharEOF
|
actDeleteCharEOF
|
||||||
actEndOfLine
|
actEndOfLine
|
||||||
@@ -224,6 +230,7 @@ const (
|
|||||||
actExecuteMulti // Deprecated
|
actExecuteMulti // Deprecated
|
||||||
actSigStop
|
actSigStop
|
||||||
actTop
|
actTop
|
||||||
|
actReload
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@@ -231,6 +238,12 @@ type placeholderFlags struct {
|
|||||||
preserveSpace bool
|
preserveSpace bool
|
||||||
number bool
|
number bool
|
||||||
query bool
|
query bool
|
||||||
|
file bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type searchRequest struct {
|
||||||
|
sort bool
|
||||||
|
command *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
@@ -350,7 +363,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
||||||
effectiveMinHeight *= 2
|
effectiveMinHeight *= 2
|
||||||
}
|
}
|
||||||
if opts.InlineInfo {
|
if opts.InfoStyle != infoDefault {
|
||||||
effectiveMinHeight -= 1
|
effectiveMinHeight -= 1
|
||||||
}
|
}
|
||||||
if opts.Bordered {
|
if opts.Bordered {
|
||||||
@@ -369,7 +382,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
}
|
}
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
infoStyle: opts.InfoStyle,
|
||||||
queryLen: [2]int{0, 0},
|
queryLen: [2]int{0, 0},
|
||||||
layout: opts.Layout,
|
layout: opts.Layout,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
@@ -403,10 +416,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
success: true,
|
failed: nil,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
printer: opts.Printer,
|
printer: opts.Printer,
|
||||||
|
printsep: opts.PrintSep,
|
||||||
merger: EmptyMerger,
|
merger: EmptyMerger,
|
||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
@@ -426,6 +440,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) noInfoLine() bool {
|
||||||
|
return t.infoStyle != infoDefault
|
||||||
|
}
|
||||||
|
|
||||||
// Input returns current query string
|
// Input returns current query string
|
||||||
func (t *Terminal) Input() []rune {
|
func (t *Terminal) Input() []rune {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@@ -434,11 +452,11 @@ func (t *Terminal) Input() []rune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCount updates the count information
|
// UpdateCount updates the count information
|
||||||
func (t *Terminal) UpdateCount(cnt int, final bool, success bool) {
|
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.count = cnt
|
t.count = cnt
|
||||||
t.reading = !final
|
t.reading = !final
|
||||||
t.success = success
|
t.failed = failedCommand
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqInfo, nil)
|
t.reqBox.Set(reqInfo, nil)
|
||||||
if final {
|
if final {
|
||||||
@@ -477,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)
|
||||||
@@ -603,12 +624,17 @@ 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 {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, tui.MakeBorderStyle(tui.BorderAround, t.unicode))
|
previewBorder := tui.MakeBorderStyle(tui.BorderAround, t.unicode)
|
||||||
|
if !t.preview.border {
|
||||||
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
|
}
|
||||||
|
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
|
||||||
@@ -616,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 {
|
||||||
@@ -645,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)
|
||||||
@@ -660,7 +686,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
y = h - y - 1
|
y = h - y - 1
|
||||||
case layoutReverseList:
|
case layoutReverseList:
|
||||||
n := 2 + len(t.header)
|
n := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
if y < n {
|
if y < n {
|
||||||
@@ -713,7 +739,17 @@ func (t *Terminal) printPrompt() {
|
|||||||
|
|
||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
pos := 0
|
pos := 0
|
||||||
if t.inlineInfo {
|
switch t.infoStyle {
|
||||||
|
case infoDefault:
|
||||||
|
t.move(1, 0, true)
|
||||||
|
if t.reading {
|
||||||
|
duration := int64(spinnerDuration)
|
||||||
|
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||||
|
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
||||||
|
}
|
||||||
|
t.move(1, 2, false)
|
||||||
|
pos = 2
|
||||||
|
case infoInline:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
if pos+len(" < ") > t.window.Width() {
|
if pos+len(" < ") > t.window.Width() {
|
||||||
return
|
return
|
||||||
@@ -725,18 +761,13 @@ func (t *Terminal) printInfo() {
|
|||||||
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
|
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
|
||||||
}
|
}
|
||||||
pos += len(" < ")
|
pos += len(" < ")
|
||||||
} else {
|
case infoHidden:
|
||||||
t.move(1, 0, true)
|
return
|
||||||
if t.reading {
|
|
||||||
duration := int64(spinnerDuration)
|
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
|
||||||
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
|
||||||
}
|
|
||||||
t.move(1, 2, false)
|
|
||||||
pos = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output := fmt.Sprintf("%d/%d", t.merger.Length(), t.count)
|
found := t.merger.Length()
|
||||||
|
total := util.Max(found, t.count)
|
||||||
|
output := fmt.Sprintf("%d/%d", found, total)
|
||||||
if t.toggleSort {
|
if t.toggleSort {
|
||||||
if t.sort {
|
if t.sort {
|
||||||
output += " +S"
|
output += " +S"
|
||||||
@@ -744,22 +775,25 @@ func (t *Terminal) printInfo() {
|
|||||||
output += " -S"
|
output += " -S"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.multi && len(t.selected) > 0 {
|
if len(t.selected) > 0 {
|
||||||
output += fmt.Sprintf(" (%d)", len(t.selected))
|
if t.multi == maxMulti {
|
||||||
|
output += fmt.Sprintf(" (%d)", len(t.selected))
|
||||||
|
} else {
|
||||||
|
output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if t.progress > 0 && t.progress < 100 {
|
if t.progress > 0 && t.progress < 100 {
|
||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
if !t.success && t.count == 0 {
|
if t.failed != nil && t.count == 0 {
|
||||||
if len(os.Getenv("FZF_DEFAULT_COMMAND")) > 0 {
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
output = "[$FZF_DEFAULT_COMMAND failed]"
|
|
||||||
} else {
|
|
||||||
output = "[default command failed - $FZF_DEFAULT_COMMAND required]"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if pos+len(output) <= t.window.Width() {
|
maxWidth := t.window.Width() - pos
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
if len(output) > maxWidth {
|
||||||
|
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
|
||||||
|
output = string(outputRunes) + ".."
|
||||||
}
|
}
|
||||||
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
@@ -770,7 +804,7 @@ func (t *Terminal) printHeader() {
|
|||||||
var state *ansiState
|
var state *ansiState
|
||||||
for idx, lineStr := range t.header {
|
for idx, lineStr := range t.header {
|
||||||
line := idx + 2
|
line := idx + 2
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
if line >= max {
|
if line >= max {
|
||||||
@@ -799,7 +833,7 @@ func (t *Terminal) printList() {
|
|||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + len(t.header)
|
line := i + 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
if i < count {
|
if i < count {
|
||||||
@@ -1002,19 +1036,6 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
|
|||||||
return displayWidth
|
return displayWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
func numLinesMax(str string, max int) int {
|
|
||||||
lines := 0
|
|
||||||
for lines < max {
|
|
||||||
idx := strings.Index(str, "\n")
|
|
||||||
if idx < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
str = str[idx+1:]
|
|
||||||
lines++
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) printPreview() {
|
func (t *Terminal) printPreview() {
|
||||||
if !t.hasPreviewWindow() {
|
if !t.hasPreviewWindow() {
|
||||||
return
|
return
|
||||||
@@ -1051,11 +1072,11 @@ 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
|
||||||
})
|
})
|
||||||
t.previewer.more = t.previewer.more || t.pwindow.Y() == height-1 && t.pwindow.X() > 0
|
t.previewer.more = t.previewer.more || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
||||||
if fillRet == tui.FillNextLine {
|
if fillRet == tui.FillNextLine {
|
||||||
continue
|
continue
|
||||||
} else if fillRet == tui.FillSuspend {
|
} else if fillRet == tui.FillSuspend {
|
||||||
@@ -1207,6 +1228,9 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
case 'n':
|
case 'n':
|
||||||
flags.number = true
|
flags.number = true
|
||||||
skipChars++
|
skipChars++
|
||||||
|
case 'f':
|
||||||
|
flags.file = true
|
||||||
|
skipChars++
|
||||||
case 'q':
|
case 'q':
|
||||||
flags.query = true
|
flags.query = true
|
||||||
default:
|
default:
|
||||||
@@ -1219,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 {
|
||||||
@@ -1228,11 +1252,32 @@ func hasPreviewFlags(template string) (plus bool, query bool) {
|
|||||||
if flags.query {
|
if flags.query {
|
||||||
query = true
|
query = true
|
||||||
}
|
}
|
||||||
|
slot = true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
func writeTemporaryFile(data []string, printSep string) string {
|
||||||
|
f, err := ioutil.TempFile("", "fzf-preview-*")
|
||||||
|
if err != nil {
|
||||||
|
errorExit("Unable to create temporary file")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
f.WriteString(strings.Join(data, printSep))
|
||||||
|
f.WriteString(printSep)
|
||||||
|
activeTempFiles = append(activeTempFiles, f.Name())
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanTemporaryFiles() {
|
||||||
|
for _, filename := range activeTempFiles {
|
||||||
|
os.Remove(filename)
|
||||||
|
}
|
||||||
|
activeTempFiles = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
current := allItems[:1]
|
current := allItems[:1]
|
||||||
selected := allItems[1:]
|
selected := allItems[1:]
|
||||||
if current[0] == nil {
|
if current[0] == nil {
|
||||||
@@ -1269,10 +1314,15 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
} else {
|
} else {
|
||||||
replacements[idx] = strconv.Itoa(n)
|
replacements[idx] = strconv.Itoa(n)
|
||||||
}
|
}
|
||||||
|
} else if flags.file {
|
||||||
|
replacements[idx] = item.AsString(stripAnsi)
|
||||||
} else {
|
} else {
|
||||||
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if flags.file {
|
||||||
|
return writeTemporaryFile(replacements, printsep)
|
||||||
|
}
|
||||||
return strings.Join(replacements, " ")
|
return strings.Join(replacements, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1290,7 +1340,7 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
trans := Transform(tokens, ranges)
|
trans := Transform(tokens, ranges)
|
||||||
str := string(joinTokens(trans))
|
str := joinTokens(trans)
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
str = strings.TrimSuffix(str, *delimiter.str)
|
str = strings.TrimSuffix(str, *delimiter.str)
|
||||||
} else if delimiter.regex != nil {
|
} else if delimiter.regex != nil {
|
||||||
@@ -1302,7 +1352,13 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
if !flags.preserveSpace {
|
if !flags.preserveSpace {
|
||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
}
|
}
|
||||||
replacements[idx] = quoteEntry(str)
|
if !flags.file {
|
||||||
|
str = quoteEntry(str)
|
||||||
|
}
|
||||||
|
replacements[idx] = str
|
||||||
|
}
|
||||||
|
if flags.file {
|
||||||
|
return writeTemporaryFile(replacements, printsep)
|
||||||
}
|
}
|
||||||
return strings.Join(replacements, " ")
|
return strings.Join(replacements, " ")
|
||||||
})
|
})
|
||||||
@@ -1319,7 +1375,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
if !valid {
|
if !valid {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
|
command := replacePlaceholder(template, t.ansi, t.delimiter, t.printsep, forcePlus, string(t.input), list)
|
||||||
cmd := util.ExecCommand(command, false)
|
cmd := util.ExecCommand(command, false)
|
||||||
if !background {
|
if !background {
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
@@ -1335,6 +1391,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
cmd.Run()
|
cmd.Run()
|
||||||
t.tui.Resume(false)
|
t.tui.Resume(false)
|
||||||
}
|
}
|
||||||
|
cleanTemporaryFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewer() bool {
|
func (t *Terminal) hasPreviewer() bool {
|
||||||
@@ -1351,7 +1408,7 @@ func (t *Terminal) hasPreviewWindow() bool {
|
|||||||
|
|
||||||
func (t *Terminal) currentItem() *Item {
|
func (t *Terminal) currentItem() *Item {
|
||||||
cnt := t.merger.Length()
|
cnt := t.merger.Length()
|
||||||
if cnt > 0 && cnt > t.cy {
|
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
|
||||||
return t.merger.Get(t.cy).item
|
return t.merger.Get(t.cy).item
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -1359,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}
|
||||||
}
|
}
|
||||||
@@ -1385,9 +1442,18 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
|
|||||||
return true, sels
|
return true, sels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) selectItem(item *Item) {
|
func (t *Terminal) selectItem(item *Item) bool {
|
||||||
|
if len(t.selected) >= t.multi {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, found := t.selected[item.Index()]; found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
||||||
t.version++
|
t.version++
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) deselectItem(item *Item) {
|
func (t *Terminal) deselectItem(item *Item) {
|
||||||
@@ -1395,12 +1461,12 @@ func (t *Terminal) deselectItem(item *Item) {
|
|||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) toggleItem(item *Item) {
|
func (t *Terminal) toggleItem(item *Item) bool {
|
||||||
if _, found := t.selected[item.Index()]; !found {
|
if _, found := t.selected[item.Index()]; !found {
|
||||||
t.selectItem(item)
|
return t.selectItem(item)
|
||||||
} else {
|
|
||||||
t.deselectItem(item)
|
|
||||||
}
|
}
|
||||||
|
t.deselectItem(item)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) killPreview(code int) {
|
func (t *Terminal) killPreview(code int) {
|
||||||
@@ -1423,7 +1489,7 @@ func (t *Terminal) Loop() {
|
|||||||
<-t.startChan
|
<-t.startChan
|
||||||
{ // Late initialization
|
{ // Late initialization
|
||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(intChan, os.Interrupt, os.Kill, syscall.SIGTERM)
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
<-intChan
|
<-intChan
|
||||||
t.reqBox.Set(reqQuit, nil)
|
t.reqBox.Set(reqQuit, nil)
|
||||||
@@ -1467,11 +1533,10 @@ func (t *Terminal) Loop() {
|
|||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
reading := t.reading
|
reading := t.reading
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
if !reading {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(spinnerDuration)
|
time.Sleep(spinnerDuration)
|
||||||
t.reqBox.Set(reqInfo, nil)
|
if reading {
|
||||||
|
t.reqBox.Set(reqInfo, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -1492,7 +1557,7 @@ func (t *Terminal) Loop() {
|
|||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if request[0] != nil {
|
if request[0] != nil {
|
||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(t.preview.command,
|
||||||
t.ansi, t.delimiter, false, string(t.input), request)
|
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), request)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if t.pwindow != nil {
|
if t.pwindow != nil {
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
@@ -1532,8 +1597,9 @@ func (t *Terminal) Loop() {
|
|||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
finishChan <- true
|
finishChan <- true
|
||||||
if out.Len() > 0 || !<-updateChan {
|
if out.Len() > 0 || !<-updateChan {
|
||||||
t.reqBox.Set(reqPreviewDisplay, string(out.Bytes()))
|
t.reqBox.Set(reqPreviewDisplay, out.String())
|
||||||
}
|
}
|
||||||
|
cleanTemporaryFiles()
|
||||||
} else {
|
} else {
|
||||||
t.reqBox.Set(reqPreviewDisplay, "")
|
t.reqBox.Set(reqPreviewDisplay, "")
|
||||||
}
|
}
|
||||||
@@ -1542,9 +1608,6 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exit := func(getCode func() int) {
|
exit := func(getCode func() int) {
|
||||||
if !t.cleanExit && t.fullscreen && t.inlineInfo {
|
|
||||||
t.placeCursor()
|
|
||||||
}
|
|
||||||
t.tui.Close()
|
t.tui.Close()
|
||||||
code := getCode()
|
code := getCode()
|
||||||
if code <= exitNoMatch && t.history != nil {
|
if code <= exitNoMatch && t.history != nil {
|
||||||
@@ -1565,7 +1628,7 @@ func (t *Terminal) Loop() {
|
|||||||
switch req {
|
switch req {
|
||||||
case reqPrompt:
|
case reqPrompt:
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
}
|
}
|
||||||
case reqInfo:
|
case reqInfo:
|
||||||
@@ -1631,6 +1694,10 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
for looping {
|
for looping {
|
||||||
|
var newCommand *string
|
||||||
|
changed := false
|
||||||
|
queryChanged := false
|
||||||
|
|
||||||
event := t.tui.GetChar()
|
event := t.tui.GetChar()
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@@ -1645,11 +1712,12 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggle := func() {
|
toggle := func() bool {
|
||||||
if t.cy < t.merger.Length() {
|
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
|
||||||
t.toggleItem(t.merger.Get(t.cy).item)
|
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
scrollPreview := func(amount int) {
|
scrollPreview := func(amount int) {
|
||||||
if !t.previewer.more {
|
if !t.previewer.more {
|
||||||
@@ -1711,9 +1779,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
t.sort = !t.sort
|
t.sort = !t.sort
|
||||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
changed = true
|
||||||
t.mutex.Unlock()
|
|
||||||
return false
|
|
||||||
case actPreviewUp:
|
case actPreviewUp:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(-1)
|
scrollPreview(-1)
|
||||||
@@ -1771,27 +1837,43 @@ func (t *Terminal) Loop() {
|
|||||||
t.cx--
|
t.cx--
|
||||||
}
|
}
|
||||||
case actSelectAll:
|
case actSelectAll:
|
||||||
if t.multi {
|
if t.multi > 0 {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
t.selectItem(t.merger.Get(i).item)
|
if !t.selectItem(t.merger.Get(i).item) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actDeselectAll:
|
case actDeselectAll:
|
||||||
if t.multi {
|
if t.multi > 0 {
|
||||||
t.selected = make(map[int32]selectedItem)
|
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
|
||||||
t.version++
|
t.deselectItem(t.merger.Get(i).item)
|
||||||
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actToggle:
|
case actToggle:
|
||||||
if t.multi && t.merger.Length() > 0 {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
toggle()
|
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
case actToggleAll:
|
case actToggleAll:
|
||||||
if t.multi {
|
if t.multi > 0 {
|
||||||
|
prevIndexes := make(map[int]struct{})
|
||||||
|
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
|
||||||
|
item := t.merger.Get(i).item
|
||||||
|
if _, found := t.selected[item.Index()]; found {
|
||||||
|
prevIndexes[i] = struct{}{}
|
||||||
|
t.deselectItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
t.toggleItem(t.merger.Get(i).item)
|
if _, found := prevIndexes[i]; !found {
|
||||||
|
item := t.merger.Get(i).item
|
||||||
|
if !t.selectItem(item) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
@@ -1806,14 +1888,12 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
case actToggleDown:
|
case actToggleDown:
|
||||||
if t.multi && t.merger.Length() > 0 {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
toggle()
|
|
||||||
t.vmove(-1, true)
|
t.vmove(-1, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
case actToggleUp:
|
case actToggleUp:
|
||||||
if t.multi && t.merger.Length() > 0 {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
toggle()
|
|
||||||
t.vmove(1, true)
|
t.vmove(1, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
@@ -1831,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)
|
||||||
@@ -1917,7 +2006,7 @@ func (t *Terminal) Loop() {
|
|||||||
if me.S != 0 {
|
if me.S != 0 {
|
||||||
// Scroll
|
// Scroll
|
||||||
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
||||||
if t.multi && me.Mod {
|
if t.multi > 0 && me.Mod {
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
t.vmove(me.S, true)
|
t.vmove(me.S, true)
|
||||||
@@ -1930,7 +2019,7 @@ func (t *Terminal) Loop() {
|
|||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
h := t.window.Height()
|
h := t.window.Height()
|
||||||
@@ -1957,7 +2046,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.cx = mx + t.xoffset
|
t.cx = mx + t.xoffset
|
||||||
} else if my >= min {
|
} else if my >= min {
|
||||||
// List
|
// List
|
||||||
if t.vset(t.offset+my-min) && t.multi && me.Mod {
|
if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
@@ -1968,10 +2057,25 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case actReload:
|
||||||
|
t.failed = nil
|
||||||
|
|
||||||
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
|
if !valid {
|
||||||
|
// We run the command even when there's no match
|
||||||
|
// 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 {
|
||||||
|
command := replacePlaceholder(a.a,
|
||||||
|
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
|
||||||
|
newCommand = &command
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
changed := false
|
|
||||||
mapkey := event.Type
|
mapkey := event.Type
|
||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
actions := t.keymap[mapkey]
|
actions := t.keymap[mapkey]
|
||||||
@@ -1985,8 +2089,9 @@ func (t *Terminal) Loop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
changed = string(previousInput) != string(t.input)
|
queryChanged = string(previousInput) != string(t.input)
|
||||||
if onChanges, prs := t.keymap[tui.Change]; changed && prs {
|
changed = changed || queryChanged
|
||||||
|
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs {
|
||||||
if !doActions(onChanges, tui.Change) {
|
if !doActions(onChanges, tui.Change) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2004,23 +2109,23 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
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++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed || t.cx != previousCx {
|
if queryChanged || t.cx != previousCx {
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if changed {
|
if changed || newCommand != nil {
|
||||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, command: newCommand})
|
||||||
}
|
}
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
t.reqBox.Set(event, nil)
|
t.reqBox.Set(event, nil)
|
||||||
@@ -2070,7 +2175,7 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.window.Height() - 2 - len(t.header)
|
max := t.window.Height() - 2 - len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
return util.Max(max, 0)
|
return util.Max(max, 0)
|
||||||
|
|||||||
@@ -30,92 +30,92 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
t.Errorf("expected: %s, actual: %s", expected, result)
|
t.Errorf("expected: %s, actual: %s", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
printsep := "\n"
|
||||||
// {}, preserve ansi
|
// {}, preserve ansi
|
||||||
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar baz'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, strip leading whitespaces, preserve ansi
|
||||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, strip leading whitespaces, strip ansi
|
||||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar baz'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {q}
|
// {q}
|
||||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz' 'query'")
|
check("echo ' foo'\\''bar baz' 'query'")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {q}, multiple items
|
||||||
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
|
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
|
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
|
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
// forcePlus
|
// forcePlus
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
// Whitespace preserving flag with "'" delimiter
|
// Whitespace preserving flag with "'" delimiter
|
||||||
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'")
|
check("echo ' foo'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo 'bar baz'")
|
check("echo 'bar baz'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
// Whitespace preserving flag with regex delimiter
|
// Whitespace preserving flag with regex delimiter
|
||||||
regex = regexp.MustCompile("\\w+")
|
regex = regexp.MustCompile(`\w+`)
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' '")
|
check("echo ' '")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ''\\'''")
|
check("echo ''\\'''")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' '")
|
check("echo ' '")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
||||||
check("echo /")
|
check("echo /")
|
||||||
|
|
||||||
// No match, but with selections
|
// No match, but with selections
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
||||||
check("echo /' foo'\\''bar baz'")
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex = regexp.MustCompile("[oa]+")
|
regex = regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
output.WriteString(part.ToString())
|
output.WriteString(part.ToString())
|
||||||
}
|
}
|
||||||
merged = util.ToChars([]byte(output.String()))
|
merged = util.ToChars(output.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixLength int32
|
var prefixLength int32
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: " {
|
if joinTokens(tx) != "abc: def: ghi: " {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
||||||
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,14 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
case 0:
|
case 0:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
|
case 28:
|
||||||
|
return Event{CtrlBackSlash, 0, nil}
|
||||||
|
case 29:
|
||||||
|
return Event{CtrlRightBracket, 0, nil}
|
||||||
|
case 30:
|
||||||
|
return Event{CtrlCaret, 0, nil}
|
||||||
|
case 31:
|
||||||
|
return Event{CtrlSlash, 0, nil}
|
||||||
case ESC:
|
case ESC:
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
@@ -518,6 +526,9 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
||||||
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
||||||
}
|
}
|
||||||
|
if r.buffer[1] >= '0' && r.buffer[1] <= '9' {
|
||||||
|
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil}
|
||||||
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +558,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
r.prevDownTime = now
|
r.prevDownTime = now
|
||||||
} else {
|
} else {
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
time.Since(r.prevDownTime) < doubleClickDuration {
|
||||||
double = true
|
double = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,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,
|
||||||
@@ -668,8 +679,13 @@ 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 {
|
||||||
w.fg = r.theme.Fg
|
if preview {
|
||||||
w.bg = r.theme.Bg
|
w.fg = r.theme.PreviewFg
|
||||||
|
w.bg = r.theme.PreviewBg
|
||||||
|
} else {
|
||||||
|
w.fg = r.theme.Fg
|
||||||
|
w.bg = r.theme.Bg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.drawBorder()
|
w.drawBorder()
|
||||||
return w
|
return w
|
||||||
@@ -693,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,10 +726,6 @@ func (w *LightWindow) csi(code string) {
|
|||||||
w.renderer.csi(code)
|
w.renderer.csi(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) stderr(str string) {
|
|
||||||
w.renderer.stderr(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
||||||
w.renderer.stderrInternal(str, allowNLCR)
|
w.renderer.stderrInternal(str, allowNLCR)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -284,6 +285,12 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{keyfn('z'), 0, nil}
|
return Event{keyfn('z'), 0, nil}
|
||||||
case tcell.KeyCtrlSpace:
|
case tcell.KeyCtrlSpace:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
|
case tcell.KeyCtrlBackslash:
|
||||||
|
return Event{CtrlBackSlash, 0, nil}
|
||||||
|
case tcell.KeyCtrlRightSq:
|
||||||
|
return Event{CtrlRightBracket, 0, nil}
|
||||||
|
case tcell.KeyCtrlUnderscore:
|
||||||
|
return Event{CtrlSlash, 0, nil}
|
||||||
case tcell.KeyBackspace2:
|
case tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
@@ -402,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}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,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 {
|
||||||
@@ -443,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) {
|
||||||
@@ -462,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)
|
||||||
}
|
}
|
||||||
@@ -513,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).
|
||||||
@@ -553,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)
|
||||||
}
|
}
|
||||||
@@ -578,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 {
|
||||||
|
style = ColBorder.style()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
style = ColNormal.style()
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ const (
|
|||||||
ESC
|
ESC
|
||||||
CtrlSpace
|
CtrlSpace
|
||||||
|
|
||||||
|
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
||||||
|
CtrlBackSlash
|
||||||
|
CtrlRightBracket
|
||||||
|
CtrlCaret
|
||||||
|
CtrlSlash
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Resize
|
Resize
|
||||||
Mouse
|
Mouse
|
||||||
@@ -117,7 +123,7 @@ func (c Color) is24() bool {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
colUndefined Color = -2
|
colUndefined Color = -2
|
||||||
colDefault = -1
|
colDefault Color = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -164,13 +170,11 @@ func (p ColorPair) Bg() Color {
|
|||||||
return p.bg
|
return p.bg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) is24() bool {
|
|
||||||
return p.fg.is24() || p.bg.is24()
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
@@ -219,6 +223,8 @@ type BorderStyle struct {
|
|||||||
bottomRight rune
|
bottomRight rune
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BorderCharacter int
|
||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if unicode {
|
if unicode {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
@@ -242,6 +248,17 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeTransparentBorder() BorderStyle {
|
||||||
|
return BorderStyle{
|
||||||
|
shape: BorderAround,
|
||||||
|
horizontal: ' ',
|
||||||
|
vertical: ' ',
|
||||||
|
topLeft: ' ',
|
||||||
|
topRight: ' ',
|
||||||
|
bottomLeft: ' ',
|
||||||
|
bottomRight: ' '}
|
||||||
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
@@ -257,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 {
|
||||||
@@ -319,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,
|
||||||
@@ -348,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,
|
||||||
@@ -363,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,
|
||||||
@@ -378,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,
|
||||||
@@ -410,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)
|
||||||
@@ -446,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)
|
||||||
@@ -460,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
return whitespaces
|
return whitespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimTrailingWhitespaces() {
|
||||||
|
whitespaces := chars.TrailingWhitespaces()
|
||||||
|
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToString() string {
|
func (chars *Chars) ToString() string {
|
||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(runes)
|
return string(runes)
|
||||||
@@ -169,7 +174,6 @@ func (chars *Chars) CopyRunes(dest []rune) {
|
|||||||
for idx, b := range chars.slice[:len(dest)] {
|
for idx, b := range chars.slice[:len(dest)] {
|
||||||
dest[idx] = rune(b)
|
dest[idx] = rune(b)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Prepend(prefix string) {
|
func (chars *Chars) Prepend(prefix string) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
161
test/test_go.rb
161
test/test_go.rb
@@ -256,12 +256,12 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
# Testing basic key bindings
|
# Testing basic key bindings
|
||||||
tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'
|
tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'
|
||||||
tmux.until { |lines| lines[-2] == ' 856/100000' }
|
tmux.until do |lines|
|
||||||
lines = tmux.capture
|
'> 3910' == lines[-4] &&
|
||||||
assert_equal '> 3910', lines[-4]
|
' 391' == lines[-3] &&
|
||||||
assert_equal ' 391', lines[-3]
|
' 856/100000' == lines[-2] &&
|
||||||
assert_equal ' 856/100000', lines[-2]
|
'> 391' == lines[-1]
|
||||||
assert_equal '> 391', lines[-1]
|
end
|
||||||
|
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '3910', readonce.chomp
|
assert_equal '3910', readonce.chomp
|
||||||
@@ -277,7 +277,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_fzf_default_command_failure
|
def test_fzf_default_command_failure
|
||||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
||||||
tmux.until { |lines| lines[-2].include?('FZF_DEFAULT_COMMAND failed') }
|
tmux.until { |lines| lines[-2].include?('Command failed: false') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -371,6 +371,65 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal %w[3 2 5 6 8 7], readonce.split($INPUT_RECORD_SEPARATOR)
|
assert_equal %w[3 2 5 6 8 7], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_multi_max
|
||||||
|
tmux.send_keys "seq 1 10 | #{FZF} -m 3 --bind A:select-all,T:toggle-all --preview 'echo [{+}]/{}'", :Enter
|
||||||
|
|
||||||
|
tmux.until { |lines| lines.item_count == 10 }
|
||||||
|
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[1]/1') && lines[-2].include?('2/10')
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys 'A'
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[1 10]/1') && lines[-2].include?('2/10 (2/3)')
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-2].include?('10/10 (2/3)') }
|
||||||
|
|
||||||
|
tmux.send_keys 'T'
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[2 3 4]/1') && lines[-2].include?('10/10 (3/3)')
|
||||||
|
end
|
||||||
|
|
||||||
|
%w[T A].each do |key|
|
||||||
|
tmux.send_keys key
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[1 5 6]/1') && lines[-2].include?('10/10 (3/3)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[5 6]/2') && lines[-2].include?('10/10 (2/3)')
|
||||||
|
end
|
||||||
|
|
||||||
|
[:BTab, :BTab, 'A'].each do |key|
|
||||||
|
tmux.send_keys key
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[5 6 2]/3') && lines[-2].include?('10/10 (3/3)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| lines[-2].include?('1/10 (3/3)') }
|
||||||
|
|
||||||
|
tmux.send_keys 'T'
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[5 6]/2') && lines[-2].include?('1/10 (2/3)')
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| lines[-2].include?('10/10 (2/3)') }
|
||||||
|
|
||||||
|
tmux.send_keys 'A'
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[1].include?('[5 6 1]/1') && lines[-2].include?('10/10 (3/3)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_with_nth
|
def test_with_nth
|
||||||
[true, false].each do |multi|
|
[true, false].each do |multi|
|
||||||
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
||||||
@@ -806,16 +865,22 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-2].include? '(100)' }
|
tmux.until { |lines| lines[-2].include? '(100)' }
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :Tab, :Tab
|
||||||
tmux.until { |lines| lines[-2].include? '(98)' }
|
tmux.until { |lines| lines[-2].include? '(98)' }
|
||||||
|
tmux.send_keys '100'
|
||||||
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
|
tmux.send_keys 'C-d'
|
||||||
|
tmux.until { |lines| lines[-2].include? '(97)' }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| lines.match_count == 100 }
|
||||||
tmux.send_keys 'C-d'
|
tmux.send_keys 'C-d'
|
||||||
tmux.until { |lines| !lines[-2].include? '(' }
|
tmux.until { |lines| !lines[-2].include? '(' }
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :BTab, :BTab
|
||||||
tmux.until { |lines| lines[-2].include? '(2)' }
|
tmux.until { |lines| lines[-2].include? '(2)' }
|
||||||
tmux.send_keys 0
|
tmux.send_keys 0
|
||||||
tmux.until { |lines| lines[-2].include? '10/100' }
|
tmux.until { |lines| lines[-2].include? '10/100' }
|
||||||
tmux.send_keys 'C-a'
|
tmux.send_keys 'C-a'
|
||||||
tmux.until { |lines| lines[-2].include? '(12)' }
|
tmux.until { |lines| lines[-2].include? '(12)' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[2 1 10 20 30 40 50 60 70 80 90 100],
|
assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],
|
||||||
readonce.split($INPUT_RECORD_SEPARATOR)
|
readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -873,7 +938,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute
|
def test_execute
|
||||||
output = '/tmp/fzf-test-execute'
|
output = '/tmp/fzf-test-execute'
|
||||||
opts = %[--bind \\"alt-a:execute(echo [{}] >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
|
opts = %[--bind \\"alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
|
||||||
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
||||||
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
||||||
@@ -898,7 +963,7 @@ class TestGoFZF < TestBase
|
|||||||
wait['/3']
|
wait['/3']
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
readonce
|
readonce
|
||||||
assert_equal %w[[foo'bar] [foo'bar]
|
assert_equal %w[/foo'bar/ /foo'bar/
|
||||||
/foo"barfoo"bar/ /foo"barfoo"bar/
|
/foo"barfoo"bar/ /foo"barfoo"bar/
|
||||||
/foo$barfoo$barfoo$bar/],
|
/foo$barfoo$barfoo$bar/],
|
||||||
File.readlines(output).map(&:chomp)
|
File.readlines(output).map(&:chomp)
|
||||||
@@ -1417,6 +1482,15 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
|
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_file
|
||||||
|
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
||||||
|
tmux.until { |lines| lines[1].include?('foo barbar00') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?('foo barbar00') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?('foo barbar foobarfoo0101') }
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_q_no_match
|
def test_preview_q_no_match
|
||||||
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter
|
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter
|
||||||
tmux.until { |lines| lines.match_count == 0 }
|
tmux.until { |lines| lines.match_count == 0 }
|
||||||
@@ -1442,6 +1516,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-1] == prompt }
|
tmux.until { |lines| lines[-1] == prompt }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_hidden
|
||||||
|
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == '> 1' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_top
|
def test_change_top
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
||||||
tmux.until { |lines| lines.match_count == 1000 }
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
@@ -1538,6 +1617,66 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
||||||
tmux.until { |lines| lines[1].include?('+ green') }
|
tmux.until { |lines| lines[1].include?('+ green') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_phony
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.until { |lines| lines[1].include?('1 333') }
|
||||||
|
tmux.send_keys 'foo'
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.until { |lines| lines[1].include?('1 333foo') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq {q}),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 998 }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines.item_count == 98 && lines.match_count == 98 }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| lines.item_count == 198 && lines.match_count == 198 }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-2].include?('(1/2)') }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
|
||||||
|
tmux.until { |lines| !lines[-2].include?('(1/2)') }
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user