mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 23:03:47 -05:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0fe79dd3b | ||
|
|
daa1958f86 | ||
|
|
2c26f02f5c | ||
|
|
af87650bc4 | ||
|
|
2b19c0bc68 | ||
|
|
76a2dcb5a9 | ||
|
|
68ec3d1c10 | ||
|
|
2ff19084ca | ||
|
|
62f062ecfa | ||
|
|
cce17ad0a0 | ||
|
|
b8296a91b9 | ||
|
|
6e9452b06e | ||
|
|
888fd35689 | ||
|
|
1fb0fbca58 | ||
|
|
ddd2a109e4 | ||
|
|
87504a528e | ||
|
|
6eac4af7db | ||
|
|
89de1340af | ||
|
|
9e753a0d44 | ||
|
|
f57920ad90 | ||
|
|
7dbbbef51a | ||
|
|
7add75126d | ||
|
|
d207672bd5 | ||
|
|
851fa38251 | ||
|
|
43345fb642 | ||
|
|
9ff33814ea | ||
|
|
21b94d2de5 | ||
|
|
24236860c8 | ||
|
|
3f868fd792 | ||
|
|
417bca03df | ||
|
|
cce6aef557 | ||
|
|
eb3afc03b5 | ||
|
|
7f0caf0683 | ||
|
|
7f606665cb | ||
|
|
202872c2dc | ||
|
|
93aeae1985 | ||
|
|
5c34ab6692 | ||
|
|
390b49653b | ||
|
|
b877c385f0 | ||
|
|
9c47739c0e | ||
|
|
04aa2992e7 | ||
|
|
2f1edeff78 | ||
|
|
306d51cdcf | ||
|
|
54a026525a | ||
|
|
d6588fc835 | ||
|
|
5a7b41a2cf | ||
|
|
338a73d764 | ||
|
|
c20954f020 | ||
|
|
1e8e1d3c9d | ||
|
|
f6b1962056 | ||
|
|
b3b101a89c | ||
|
|
9615c4edf1 | ||
|
|
85a75ee035 | ||
|
|
1e5bd55672 | ||
|
|
37d4015d56 | ||
|
|
6b27554cdb | ||
|
|
fc1b119159 | ||
|
|
2cd0d4a9f7 | ||
|
|
fd03aabeb2 | ||
|
|
8068c975c2 | ||
|
|
a6d2ab3360 | ||
|
|
fe7b91dfd9 | ||
|
|
5784101bea | ||
|
|
eaf6eb8978 | ||
|
|
3af63bcf1f | ||
|
|
80a21f7a75 | ||
|
|
0b33dc6ce1 | ||
|
|
64a6ced62e | ||
|
|
438f6c96cd | ||
|
|
6ae085f974 | ||
|
|
cb8e97274e | ||
|
|
c4185e81e8 | ||
|
|
0580fe9046 | ||
|
|
1b1bc9ea36 | ||
|
|
c2614467cf | ||
|
|
077ae51f05 | ||
|
|
ee40212e97 | ||
|
|
7f5f6efbac | ||
|
|
45d4c57d91 | ||
|
|
41e0208335 | ||
|
|
2f8238342b | ||
|
|
e1582b8323 | ||
|
|
7cfa6f0265 | ||
|
|
e3973c74e7 | ||
|
|
a8deca2dd9 | ||
|
|
a78ade1771 | ||
|
|
79d2ef4616 | ||
|
|
5edc3f755c | ||
|
|
288976310b | ||
|
|
58b5be8ab6 | ||
|
|
26d7896877 | ||
|
|
fd6bc7308f | ||
|
|
6c41c95f28 | ||
|
|
446e04469d | ||
|
|
5097e563df | ||
|
|
c7ad97c641 | ||
|
|
9516fe3324 | ||
|
|
20cdbac8c3 | ||
|
|
e3e7b3360c | ||
|
|
655dfb8328 | ||
|
|
9b9c67b768 | ||
|
|
5b7457ff08 | ||
|
|
48adad5454 | ||
|
|
b27dc3eb17 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -22,7 +22,7 @@
|
|||||||
### Before submitting
|
### Before submitting
|
||||||
|
|
||||||
- Make sure that you have the latest version of fzf
|
- Make sure that you have the latest version of fzf
|
||||||
- If you use tmux, make sure $TERM is set to screen or screen-256color
|
- If you use tmux, make sure $TERM starts with "screen"
|
||||||
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
||||||
|
|
||||||
Describe your problem or suggestion from here ...
|
Describe your problem or suggestion from here ...
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
bin/fzf
|
bin/fzf
|
||||||
|
bin/fzf.exe
|
||||||
target
|
target
|
||||||
pkg
|
pkg
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
|||||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,6 +1,64 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.17.4
|
||||||
|
------
|
||||||
|
|
||||||
|
- Added `--layout` option with a new layout called `reverse-list`.
|
||||||
|
- `--layout=reverse` is a synonym for `--reverse`
|
||||||
|
- `--layout=default` is a synonym for `--no-reverse`
|
||||||
|
- Preview window will be updated even when there is no match for the query
|
||||||
|
if any of the placeholder expressions (e.g. `{q}`, `{+}`) evaluates to
|
||||||
|
a non-empty string.
|
||||||
|
- More keys for binding: `shift-{up,down}`, `alt-{up,down,left,right}`
|
||||||
|
- fzf can now start even when `/dev/tty` is not available by making an
|
||||||
|
educated guess.
|
||||||
|
- Updated the default command for Windows.
|
||||||
|
- Fixes and improvements on bash/zsh completion
|
||||||
|
- install and uninstall scripts now supports generating files under
|
||||||
|
`XDG_CONFIG_HOME` on `--xdg` flag.
|
||||||
|
|
||||||
|
See https://github.com/junegunn/fzf/milestone/12?closed=1 for the full list of
|
||||||
|
changes.
|
||||||
|
|
||||||
|
0.17.3
|
||||||
|
------
|
||||||
|
- `$LINES` and `$COLUMNS` are exported to preview command so that the command
|
||||||
|
knows the exact size of the preview window.
|
||||||
|
- Better error messages when the default command or `$FZF_DEFAULT_COMMAND`
|
||||||
|
fails.
|
||||||
|
- Reverted #1061 to avoid having duplicate entries in the list when find
|
||||||
|
command detected a file system loop (#1120). The default command now
|
||||||
|
requires that find supports `-fstype` option.
|
||||||
|
- fzf now distinguishes mouse left click and right click (#1130)
|
||||||
|
- Right click is now bound to `toggle` action by default
|
||||||
|
- `--bind` understands `left-click` and `right-click`
|
||||||
|
- Added `replace-query` action (#1137)
|
||||||
|
- Replaces query string with the current selection
|
||||||
|
- Added `accept-non-empty` action (#1162)
|
||||||
|
- Same as accept, except that it prevents fzf from exiting without any
|
||||||
|
selection
|
||||||
|
|
||||||
|
0.17.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- Fixed custom background color of preview window (#1046)
|
||||||
|
- Fixed background color issues of Windows binary
|
||||||
|
- Fixed Windows binary to execute command using cmd.exe with no parsing and
|
||||||
|
escaping (#1072)
|
||||||
|
- Added support for `window` layout on Vim 8 using Vim 8 terminal (#1055)
|
||||||
|
|
||||||
|
0.17.0-2
|
||||||
|
--------
|
||||||
|
|
||||||
|
A maintenance release for auxiliary scripts. fzf binaries are not updated.
|
||||||
|
|
||||||
|
- Experimental support for the builtin terminal of Vim 8
|
||||||
|
- fzf can now run inside GVim
|
||||||
|
- Updated Vim plugin to better handle `&shell` issue on fish
|
||||||
|
- Fixed a bug of fzf-tmux where invalid output is generated
|
||||||
|
- Fixed fzf-tmux to work even when `tput` does not work
|
||||||
|
|
||||||
0.17.0
|
0.17.0
|
||||||
------
|
------
|
||||||
- Performance optimization
|
- Performance optimization
|
||||||
|
|||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM archlinux/base:latest
|
||||||
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
||||||
|
RUN gem install --no-ri --no-rdoc minitest
|
||||||
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|
||||||
|
# Do not set default PS1
|
||||||
|
RUN rm -f /etc/bash.bashrc
|
||||||
|
COPY . /fzf
|
||||||
|
RUN cd /fzf && make install && ./install --all
|
||||||
|
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
||||||
19
Makefile
19
Makefile
@@ -1,12 +1,5 @@
|
|||||||
ifndef GOOS
|
ifndef GOOS
|
||||||
UNAME_S := $(shell uname -s)
|
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||||
ifeq ($(UNAME_S),Darwin)
|
|
||||||
GOOS := darwin
|
|
||||||
else ifeq ($(UNAME_S),Linux)
|
|
||||||
GOOS := linux
|
|
||||||
else
|
|
||||||
$(error "$$GOOS is not defined.")
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
@@ -135,4 +128,12 @@ target/$(BINARYARM8): $(SOURCES) vendor
|
|||||||
bin/fzf: target/$(BINARY) | bin
|
bin/fzf: target/$(BINARY) | bin
|
||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
.PHONY: all release release-all test install clean
|
docker:
|
||||||
|
docker build -t fzf-arch .
|
||||||
|
docker run -it fzf-arch tmux
|
||||||
|
|
||||||
|
docker-test:
|
||||||
|
docker build -t fzf-arch .
|
||||||
|
docker run -it fzf-arch
|
||||||
|
|
||||||
|
.PHONY: all release release-all test install clean docker docker-test
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
- `g:fzf_action`
|
- `g:fzf_action`
|
||||||
- Customizable extra key bindings for opening selected files in different ways
|
- Customizable extra key bindings for opening selected files in different ways
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- Determines the size and position of fzf window (tmux pane or Neovim split)
|
- Determines the size and position of fzf window
|
||||||
- `g:fzf_colors`
|
- `g:fzf_colors`
|
||||||
- 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`
|
||||||
@@ -72,7 +72,7 @@ let g:fzf_action = {
|
|||||||
" - down / up / left / right
|
" - down / up / left / right
|
||||||
let g:fzf_layout = { 'down': '~40%' }
|
let g:fzf_layout = { 'down': '~40%' }
|
||||||
|
|
||||||
" In Neovim, you can set up fzf window using a Vim command
|
" 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': '10split enew' }
|
||||||
@@ -86,6 +86,7 @@ let g:fzf_colors =
|
|||||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
\ 'hl+': ['fg', 'Statement'],
|
\ 'hl+': ['fg', 'Statement'],
|
||||||
\ 'info': ['fg', 'PreProc'],
|
\ 'info': ['fg', 'PreProc'],
|
||||||
|
\ 'border': ['fg', 'Ignore'],
|
||||||
\ 'prompt': ['fg', 'Conditional'],
|
\ 'prompt': ['fg', 'Conditional'],
|
||||||
\ 'pointer': ['fg', 'Exception'],
|
\ 'pointer': ['fg', 'Exception'],
|
||||||
\ 'marker': ['fg', 'Keyword'],
|
\ 'marker': ['fg', 'Keyword'],
|
||||||
@@ -115,7 +116,7 @@ following options.
|
|||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
||||||
| `window` (*Neovim only*) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
| `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) |
|
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
|
||||||
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
||||||
|
|
||||||
@@ -141,11 +142,38 @@ command! -bang MyStuff
|
|||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
fzf inside terminal buffer
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||||
|
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||||
|
|
||||||
|
- On Neovim
|
||||||
|
- On GVim
|
||||||
|
- On Terminal Vim with the non-default layout
|
||||||
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
|
### Hide statusline
|
||||||
|
|
||||||
|
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
||||||
|
the containing buffer.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
autocmd! FileType fzf
|
||||||
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
```
|
||||||
|
|
||||||
GVim
|
GVim
|
||||||
----
|
----
|
||||||
|
|
||||||
In GVim, you need an external terminal emulator to start fzf with. `xterm`
|
With the latest version of GVim, fzf will start inside the builtin terminal
|
||||||
command is used by default, but you can customize it with `g:fzf_launcher`.
|
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`.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" This is the default. %s is replaced with fzf command
|
" This is the default. %s is replaced with fzf command
|
||||||
|
|||||||
144
README.md
144
README.md
@@ -1,4 +1,4 @@
|
|||||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EKYAW9PGKPD2N)
|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf)
|
||||||
===
|
===
|
||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder.
|
||||||
@@ -23,9 +23,11 @@ Table of Contents
|
|||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Using git](#using-git)
|
|
||||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||||
|
* [Using git](#using-git)
|
||||||
* [As Vim plugin](#as-vim-plugin)
|
* [As Vim plugin](#as-vim-plugin)
|
||||||
|
* [Arch Linux](#arch-linux)
|
||||||
|
* [Fedora](#fedora)
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
* [Upgrading fzf](#upgrading-fzf)
|
* [Upgrading fzf](#upgrading-fzf)
|
||||||
* [Building fzf](#building-fzf)
|
* [Building fzf](#building-fzf)
|
||||||
@@ -51,7 +53,7 @@ Table of Contents
|
|||||||
* [Executing external programs](#executing-external-programs)
|
* [Executing external programs](#executing-external-programs)
|
||||||
* [Preview window](#preview-window)
|
* [Preview window](#preview-window)
|
||||||
* [Tips](#tips)
|
* [Tips](#tips)
|
||||||
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore)
|
* [Respecting .gitignore](#respecting-gitignore)
|
||||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||||
* [Fish shell](#fish-shell)
|
* [Fish shell](#fish-shell)
|
||||||
* [<a href="LICENSE">License</a>](#license)
|
* [<a href="LICENSE">License</a>](#license)
|
||||||
@@ -73,9 +75,21 @@ stuff.
|
|||||||
|
|
||||||
[bin]: https://github.com/junegunn/fzf-bin/releases
|
[bin]: https://github.com/junegunn/fzf-bin/releases
|
||||||
|
|
||||||
|
### Using Homebrew or Linuxbrew
|
||||||
|
|
||||||
|
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
||||||
|
to install fzf.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install fzf
|
||||||
|
|
||||||
|
# To install useful key bindings and fuzzy completion:
|
||||||
|
$(brew --prefix)/opt/fzf/install
|
||||||
|
```
|
||||||
|
|
||||||
### Using git
|
### Using git
|
||||||
|
|
||||||
Clone this repository and run
|
Alternatively, you can "git clone" this repository to any directory and run
|
||||||
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -83,37 +97,60 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
~/.fzf/install
|
~/.fzf/install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Homebrew or Linuxbrew
|
|
||||||
|
|
||||||
Alternatively, you can use [Homebrew](http://brew.sh/) or
|
|
||||||
[Linuxbrew](http://linuxbrew.sh/) to install fzf.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
brew install fzf
|
|
||||||
|
|
||||||
# Install shell extensions
|
|
||||||
/usr/local/opt/fzf/install
|
|
||||||
```
|
|
||||||
|
|
||||||
### As Vim plugin
|
### As Vim plugin
|
||||||
|
|
||||||
You can manually add the directory to `&runtimepath` as follows,
|
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:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" If installed using git
|
|
||||||
set rtp+=~/.fzf
|
|
||||||
|
|
||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
set rtp+=/usr/local/opt/fzf
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
set rtp+=~/.fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
But it's recommended that you use a plugin manager like
|
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
||||||
[vim-plug](https://github.com/junegunn/vim-plug).
|
written as:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
" If installed using Homebrew
|
||||||
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using git
|
||||||
|
Plug '~/.fzf'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
But instead of separately installing fzf on your system (using Homebrew or
|
||||||
|
"git clone") and enabling it on Vim (adding it to `&runtimepath`), you can use
|
||||||
|
vim-plug to do both.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run install script
|
||||||
|
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
||||||
|
" Both options are optional. You don't have to install fzf in ~/.fzf
|
||||||
|
" and you don't have to run 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
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
@@ -125,10 +162,12 @@ available as a [Chocolatey package][choco].
|
|||||||
choco install fzf
|
choco install fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
However, other components of the project may not work on Windows. You might
|
However, other components of the project may not work on Windows. Known issues
|
||||||
want to consider installing fzf on [Windows Subsystem for Linux][wsl] where
|
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.
|
everything runs flawlessly.
|
||||||
|
|
||||||
|
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
|
||||||
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
||||||
|
|
||||||
Upgrading fzf
|
Upgrading fzf
|
||||||
@@ -184,8 +223,8 @@ cursor with `--height` option.
|
|||||||
vim $(fzf --height 40%)
|
vim $(fzf --height 40%)
|
||||||
```
|
```
|
||||||
|
|
||||||
Also check out `--reverse` option if you prefer "top-down" layout instead of
|
Also check out `--reverse` and `--layout` options if you prefer
|
||||||
the default "bottom-up" layout.
|
"top-down" layout instead of the default "bottom-up" layout.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
vim $(fzf --height 40% --reverse)
|
vim $(fzf --height 40% --reverse)
|
||||||
@@ -195,7 +234,7 @@ You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
|||||||
default. For example,
|
default. For example,
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
|
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Search syntax
|
#### Search syntax
|
||||||
@@ -205,12 +244,13 @@ type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
|||||||
!fire`
|
!fire`
|
||||||
|
|
||||||
| Token | Match type | Description |
|
| Token | Match type | Description |
|
||||||
| -------- | -------------------------- | --------------------------------- |
|
| --------- | -------------------------- | ------------------------------------ |
|
||||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||||
|
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
||||||
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
|
||||||
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
||||||
|
| `!^music` | inverse-prefix-exact-match | Items that do not start with `music` |
|
||||||
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
||||||
|
|
||||||
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
||||||
@@ -229,10 +269,10 @@ or `py`.
|
|||||||
|
|
||||||
- `FZF_DEFAULT_COMMAND`
|
- `FZF_DEFAULT_COMMAND`
|
||||||
- Default command to use when input is tty
|
- Default command to use when input is tty
|
||||||
- e.g. `export FZF_DEFAULT_COMMAND='ag -g ""'`
|
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
||||||
- `FZF_DEFAULT_OPTS`
|
- `FZF_DEFAULT_OPTS`
|
||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||||
|
|
||||||
#### Options
|
#### Options
|
||||||
|
|
||||||
@@ -367,12 +407,17 @@ export FZF_COMPLETION_TRIGGER='~~'
|
|||||||
# Options to fzf command
|
# Options to fzf command
|
||||||
export FZF_COMPLETION_OPTS='+c -x'
|
export FZF_COMPLETION_OPTS='+c -x'
|
||||||
|
|
||||||
# Use ag instead of the default find command for listing candidates.
|
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||||
# - The first argument to the function is the base path to start traversal
|
# command for listing path candidates.
|
||||||
# - Note that ag only lists files not directories
|
# - The first argument to the function ($1) is the base path to start traversal
|
||||||
# - See the source code (completion.{bash,zsh}) for the details.
|
# - See the source code (completion.{bash,zsh}) for the details.
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
ag -g "" "$1"
|
fd --hidden --follow --exclude ".git" . "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use fd to generate the list for directory completion
|
||||||
|
_fzf_compgen_dir() {
|
||||||
|
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -412,7 +457,7 @@ options that affect the performance.
|
|||||||
algorithm. However, this algorithm is not guaranteed to find the optimal
|
algorithm. However, this algorithm is not guaranteed to find the optimal
|
||||||
ordering of the matches and is not recommended.
|
ordering of the matches and is not recommended.
|
||||||
|
|
||||||
[perf]: https://junegunn.kr/images/fzf-0.16.11.png
|
[perf]: https://junegunn.kr/images/fzf-0.17.0.png
|
||||||
|
|
||||||
### Executing external programs
|
### Executing external programs
|
||||||
|
|
||||||
@@ -476,30 +521,33 @@ For more advanced examples, see [Key bindings for git with fzf][fzf-git].
|
|||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
#### Respecting `.gitignore`
|
||||||
|
|
||||||
[ag](https://github.com/ggreer/the_silver_searcher) or
|
You can use [fd](https://github.com/sharkdp/fd),
|
||||||
[rg](https://github.com/BurntSushi/ripgrep) will do the
|
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
|
||||||
filtering:
|
searcher](https://github.com/ggreer/the_silver_searcher) instead of the
|
||||||
|
default find command to traverse the file system while respecting
|
||||||
|
`.gitignore`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Feed the output of ag into fzf
|
# Feed the output of fd into fzf
|
||||||
ag -g "" | fzf
|
fd --type f | fzf
|
||||||
|
|
||||||
# Setting ag as the default source for fzf
|
# Setting fd as the default source for fzf
|
||||||
export FZF_DEFAULT_COMMAND='ag -g ""'
|
export FZF_DEFAULT_COMMAND='fd --type f'
|
||||||
|
|
||||||
# Now fzf (w/o pipe) will use ag instead of find
|
# Now fzf (w/o pipe) will use fd instead of find
|
||||||
fzf
|
fzf
|
||||||
|
|
||||||
# To apply the command to CTRL-T as well
|
# To apply the command to CTRL-T as well
|
||||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want to exclude hidden files, use the following command:
|
If you want the command to follow symbolic links, and don't want it to exclude
|
||||||
|
hidden files, use the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_COMMAND='ag --hidden --ignore .git -g ""'
|
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `git ls-tree` for fast traversal
|
#### `git ls-tree` for fast traversal
|
||||||
|
|||||||
16
bin/fzf-tmux
16
bin/fzf-tmux
@@ -16,8 +16,8 @@ skip=""
|
|||||||
swap=""
|
swap=""
|
||||||
close=""
|
close=""
|
||||||
term=""
|
term=""
|
||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols)
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
@@ -136,6 +136,11 @@ fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
|||||||
cleanup() {
|
cleanup() {
|
||||||
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
|
# Restore tmux window options
|
||||||
|
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
|
||||||
|
eval "tmux ${tmux_win_opts[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove temp window if we were zoomed
|
# Remove temp window if we were zoomed
|
||||||
if [[ -n "$zoomed" ]]; then
|
if [[ -n "$zoomed" ]]; then
|
||||||
tmux display-message -p "#{window_id}" > /dev/null
|
tmux display-message -p "#{window_id}" > /dev/null
|
||||||
@@ -174,19 +179,20 @@ pppid=$$
|
|||||||
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
|
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
|
||||||
close="; trap - EXIT SIGINT SIGTERM $close"
|
close="; trap - EXIT SIGINT SIGTERM $close"
|
||||||
|
|
||||||
|
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
||||||
|
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
cat $argsf
|
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap \
|
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1
|
||||||
else
|
else
|
||||||
mkfifo $fifo1
|
mkfifo $fifo1
|
||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "$envs bash $argsf" $swap \
|
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
|
|||||||
43
doc/fzf.txt
43
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: August 14 2017
|
fzf.txt fzf Last change: November 19 2017
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
|
|||||||
Examples
|
Examples
|
||||||
fzf#run
|
fzf#run
|
||||||
fzf#wrap
|
fzf#wrap
|
||||||
|
fzf inside terminal buffer
|
||||||
|
Hide statusline
|
||||||
GVim
|
GVim
|
||||||
License
|
License
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
|||||||
- Customizable extra key bindings for opening selected files in different
|
- Customizable extra key bindings for opening selected files in different
|
||||||
ways
|
ways
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- Determines the size and position of fzf window (tmux pane or Neovim split)
|
- Determines the size and position of fzf window
|
||||||
- `g:fzf_colors`
|
- `g:fzf_colors`
|
||||||
- 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`
|
||||||
@@ -97,7 +99,7 @@ Examples~
|
|||||||
" - down / up / left / right
|
" - down / up / left / right
|
||||||
let g:fzf_layout = { 'down': '~40%' }
|
let g:fzf_layout = { 'down': '~40%' }
|
||||||
|
|
||||||
" In Neovim, you can set up fzf window using a Vim command
|
" 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': '10split enew' }
|
||||||
@@ -111,6 +113,7 @@ Examples~
|
|||||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
\ 'hl+': ['fg', 'Statement'],
|
\ 'hl+': ['fg', 'Statement'],
|
||||||
\ 'info': ['fg', 'PreProc'],
|
\ 'info': ['fg', 'PreProc'],
|
||||||
|
\ 'border': ['fg', 'Ignore'],
|
||||||
\ 'prompt': ['fg', 'Conditional'],
|
\ 'prompt': ['fg', 'Conditional'],
|
||||||
\ 'pointer': ['fg', 'Exception'],
|
\ 'pointer': ['fg', 'Exception'],
|
||||||
\ 'marker': ['fg', 'Keyword'],
|
\ 'marker': ['fg', 'Keyword'],
|
||||||
@@ -141,7 +144,7 @@ following options.
|
|||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
||||||
`window` (Neovim only) | string | Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`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)
|
`launcher` | string | External terminal emulator to start fzf with (GVim only)
|
||||||
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
||||||
---------------------------+---------------+--------------------------------------------------------------
|
---------------------------+---------------+--------------------------------------------------------------
|
||||||
@@ -166,11 +169,39 @@ function that decorates the options dictionary so that it understands
|
|||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||||
<
|
<
|
||||||
|
|
||||||
|
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||||
|
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||||
|
|
||||||
|
- On Neovim
|
||||||
|
- On GVim
|
||||||
|
- On Terminal Vim with the non-default layout
|
||||||
|
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||||
|
|
||||||
|
|
||||||
|
< Hide statusline >___________________________________________________________~
|
||||||
|
*fzf-hide-statusline*
|
||||||
|
|
||||||
|
When fzf starts in a terminal buffer, you may want to hide the statusline of
|
||||||
|
the containing buffer.
|
||||||
|
>
|
||||||
|
autocmd! FileType fzf
|
||||||
|
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||||
|
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||||
|
<
|
||||||
|
|
||||||
GVIM *fzf-gvim*
|
GVIM *fzf-gvim*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
In GVim, you need an external terminal emulator to start fzf with. `xterm`
|
With the latest version of GVim, fzf will start inside the builtin terminal
|
||||||
command is used by default, but you can customize it with `g:fzf_launcher`.
|
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
|
" This is the default. %s is replaced with fzf command
|
||||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
||||||
|
|||||||
104
glide.lock
generated
104
glide.lock
generated
@@ -1,24 +1,68 @@
|
|||||||
hash: d68dd0bd779ac4ffca1e0c49ca38d85f90d5d68fa8e2d5d7db70a8ce8c662ec1
|
hash: 92a208bfbaecdf8d1ccaf99a465884c49f9cd91f44f1756d7bbf3290795c781b
|
||||||
updated: 2017-06-01T15:48:41.653745249-07:00
|
updated: 2017-12-03T13:37:23.420874333+09:00
|
||||||
imports:
|
imports:
|
||||||
|
- name: github.com/bjwbell/gensimd
|
||||||
|
version: 06eb18285485c0d572cc7f024050fc6cb652ed4c
|
||||||
|
subpackages:
|
||||||
|
- simd
|
||||||
|
- name: github.com/codegangsta/cli
|
||||||
|
version: c6af8847eb2b7b297d07c3ede98903e95e680ef9
|
||||||
- name: github.com/gdamore/encoding
|
- name: github.com/gdamore/encoding
|
||||||
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
||||||
- name: github.com/gdamore/tcell
|
- name: github.com/gdamore/tcell
|
||||||
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
version: 0a0db94084dfe181108c18508ebd312f12d331fb
|
||||||
subpackages:
|
subpackages:
|
||||||
- encoding
|
- encoding
|
||||||
- name: github.com/lucasb-eyer/go-colorful
|
- name: github.com/lucasb-eyer/go-colorful
|
||||||
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
||||||
|
- name: github.com/Masterminds/semver
|
||||||
|
version: 15d8430ab86497c5c0da827b748823945e1cf1e1
|
||||||
|
- name: github.com/Masterminds/vcs
|
||||||
|
version: 6f1c6d150500e452704e9863f68c2559f58616bf
|
||||||
- name: github.com/mattn/go-isatty
|
- name: github.com/mattn/go-isatty
|
||||||
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||||
- name: github.com/mattn/go-runewidth
|
- name: github.com/mattn/go-runewidth
|
||||||
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
- name: github.com/mattn/go-shellwords
|
- name: github.com/mattn/go-shellwords
|
||||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||||
|
- name: github.com/mengzhuo/intrinsic
|
||||||
|
version: 34b800838e0bcd9c5b6abd414e3ad03dc1a686b8
|
||||||
|
subpackages:
|
||||||
|
- sse2
|
||||||
|
- name: github.com/mitchellh/go-homedir
|
||||||
|
version: b8bc1bf767474819792c23f32d8286a45736f1c6
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- acme
|
||||||
|
- blowfish
|
||||||
|
- cast5
|
||||||
|
- chacha20poly1305/internal/chacha20
|
||||||
|
- curve25519
|
||||||
|
- ed25519
|
||||||
|
- ed25519/internal/edwards25519
|
||||||
|
- hkdf
|
||||||
|
- nacl/secretbox
|
||||||
|
- openpgp
|
||||||
|
- openpgp/armor
|
||||||
|
- openpgp/elgamal
|
||||||
|
- openpgp/errors
|
||||||
|
- openpgp/packet
|
||||||
|
- openpgp/s2k
|
||||||
|
- pbkdf2
|
||||||
|
- pkcs12/internal/rc2
|
||||||
|
- poly1305
|
||||||
|
- ripemd160
|
||||||
|
- salsa20/salsa
|
||||||
|
- ssh
|
||||||
|
- ssh/agent
|
||||||
- ssh/terminal
|
- ssh/terminal
|
||||||
|
- ssh/testdata
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: a8b9294777976932365dabb6640cf1468d95c70f
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- context/ctxhttp
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
||||||
subpackages:
|
subpackages:
|
||||||
@@ -26,13 +70,65 @@ imports:
|
|||||||
- name: golang.org/x/text
|
- name: golang.org/x/text
|
||||||
version: 4ee4af566555f5fbe026368b75596286a312663a
|
version: 4ee4af566555f5fbe026368b75596286a312663a
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- cases
|
||||||
|
- collate
|
||||||
|
- collate/build
|
||||||
|
- currency
|
||||||
- encoding
|
- encoding
|
||||||
- encoding/charmap
|
- encoding/charmap
|
||||||
|
- encoding/ianaindex
|
||||||
- encoding/internal
|
- encoding/internal
|
||||||
- encoding/internal/identifier
|
- encoding/internal/identifier
|
||||||
- encoding/japanese
|
- encoding/japanese
|
||||||
- encoding/korean
|
- encoding/korean
|
||||||
- encoding/simplifiedchinese
|
- encoding/simplifiedchinese
|
||||||
- encoding/traditionalchinese
|
- encoding/traditionalchinese
|
||||||
|
- encoding/unicode
|
||||||
|
- encoding/unicode/utf32
|
||||||
|
- internal
|
||||||
|
- internal/colltab
|
||||||
|
- internal/format
|
||||||
|
- internal/gen
|
||||||
|
- internal/stringset
|
||||||
|
- internal/tag
|
||||||
|
- internal/testtext
|
||||||
|
- internal/triegen
|
||||||
|
- internal/ucd
|
||||||
|
- internal/utf8internal
|
||||||
|
- language
|
||||||
|
- language/display
|
||||||
|
- message
|
||||||
|
- runes
|
||||||
|
- secure/bidirule
|
||||||
- transform
|
- transform
|
||||||
testImports: []
|
- unicode/bidi
|
||||||
|
- unicode/cldr
|
||||||
|
- unicode/norm
|
||||||
|
- unicode/rangetable
|
||||||
|
- width
|
||||||
|
- name: golang.org/x/tools
|
||||||
|
version: 04447353bc504b9a5c02eb227b9ecd252e64ea20
|
||||||
|
subpackages:
|
||||||
|
- go/ast/astutil
|
||||||
|
- go/buildutil
|
||||||
|
- go/loader
|
||||||
|
- name: gopkg.in/yaml.v2
|
||||||
|
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
|
||||||
|
testImports:
|
||||||
|
- name: github.com/gopherjs/gopherjs
|
||||||
|
version: 444abdf920945de5d4a977b572bcc6c674d1e4eb
|
||||||
|
subpackages:
|
||||||
|
- js
|
||||||
|
- name: github.com/jtolds/gls
|
||||||
|
version: 77f18212c9c7edc9bd6a33d383a7b545ce62f064
|
||||||
|
- name: github.com/smartystreets/assertions
|
||||||
|
version: 0b37b35ec7434b77e77a4bb29b79677cced992ea
|
||||||
|
subpackages:
|
||||||
|
- internal/go-render/render
|
||||||
|
- internal/oglematchers
|
||||||
|
- name: github.com/smartystreets/goconvey
|
||||||
|
version: e5b2b7c9111590d019a696c7800593f666e1a7f4
|
||||||
|
subpackages:
|
||||||
|
- convey
|
||||||
|
- convey/gotest
|
||||||
|
- convey/reporting
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import:
|
|||||||
- package: github.com/mattn/go-shellwords
|
- package: github.com/mattn/go-shellwords
|
||||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||||
- package: github.com/gdamore/tcell
|
- package: github.com/gdamore/tcell
|
||||||
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
version: 0a0db94084dfe181108c18508ebd312f12d331fb
|
||||||
subpackages:
|
subpackages:
|
||||||
- encoding
|
- encoding
|
||||||
- package: golang.org/x/crypto
|
- package: golang.org/x/crypto
|
||||||
|
|||||||
95
install
95
install
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.17.0
|
version=0.17.4
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
binary_arch=
|
binary_arch=
|
||||||
allow_legacy=
|
allow_legacy=
|
||||||
|
shells="bash zsh fish"
|
||||||
|
prefix='~/.fzf'
|
||||||
|
prefix_expand=~/.fzf
|
||||||
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -17,10 +21,15 @@ usage: $0 [OPTIONS]
|
|||||||
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
||||||
--all Download fzf binary and update configuration files
|
--all Download fzf binary and update configuration files
|
||||||
to enable key bindings and fuzzy completion
|
to enable key bindings and fuzzy completion
|
||||||
|
--xdg Generate files under \$XDG_CONFIG_HOME/fzf
|
||||||
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
||||||
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
||||||
--[no-]update-rc Whether or not to update shell configuration files
|
--[no-]update-rc Whether or not to update shell configuration files
|
||||||
|
|
||||||
|
--no-bash Do not set up bash configuration
|
||||||
|
--no-zsh Do not set up zsh configuration
|
||||||
|
--no-fish Do not set up fish configuration
|
||||||
|
|
||||||
--32 Download 32-bit binary
|
--32 Download 32-bit binary
|
||||||
--64 Download 64-bit binary
|
--64 Download 64-bit binary
|
||||||
EOF
|
EOF
|
||||||
@@ -38,6 +47,11 @@ for opt in "$@"; do
|
|||||||
update_config=1
|
update_config=1
|
||||||
allow_legacy=1
|
allow_legacy=1
|
||||||
;;
|
;;
|
||||||
|
--xdg)
|
||||||
|
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||||
|
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||||
|
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf"
|
||||||
|
;;
|
||||||
--key-bindings) key_bindings=1 ;;
|
--key-bindings) key_bindings=1 ;;
|
||||||
--no-key-bindings) key_bindings=0 ;;
|
--no-key-bindings) key_bindings=0 ;;
|
||||||
--completion) auto_completion=1 ;;
|
--completion) auto_completion=1 ;;
|
||||||
@@ -47,6 +61,9 @@ for opt in "$@"; do
|
|||||||
--32) binary_arch=386 ;;
|
--32) binary_arch=386 ;;
|
||||||
--64) binary_arch=amd64 ;;
|
--64) binary_arch=amd64 ;;
|
||||||
--bin) ;;
|
--bin) ;;
|
||||||
|
--no-bash) shells=${shells/bash/} ;;
|
||||||
|
--no-zsh) shells=${shells/zsh/} ;;
|
||||||
|
--no-fish) shells=${shells/fish/} ;;
|
||||||
*)
|
*)
|
||||||
echo "unknown option: $opt"
|
echo "unknown option: $opt"
|
||||||
help
|
help
|
||||||
@@ -59,14 +76,15 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
fzf_base="$(pwd)"
|
fzf_base="$(pwd)"
|
||||||
|
|
||||||
ask() {
|
ask() {
|
||||||
# If stdin is a tty, we are "interactive".
|
while true; do
|
||||||
# non-interactive shell: wait for a linefeed
|
read -p "$1 ([y]/n) " -r
|
||||||
# interactive shell: continue after a single keypress
|
REPLY=${REPLY:-"y"}
|
||||||
read_n=$([ -t 0 ] && echo "-n 1")
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
return 1
|
||||||
read -p "$1 ([y]/n) " $read_n -r
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
echo
|
return 0
|
||||||
[[ $REPLY =~ ^[Nn]$ ]]
|
fi
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
check_binary() {
|
check_binary() {
|
||||||
@@ -160,12 +178,13 @@ binary_error=""
|
|||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
||||||
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
||||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
|
||||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
|
||||||
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
||||||
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
|
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
|
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
||||||
|
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
||||||
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
||||||
@@ -204,6 +223,17 @@ fi
|
|||||||
|
|
||||||
[[ "$*" =~ "--bin" ]] && exit 0
|
[[ "$*" =~ "--bin" ]] && exit 0
|
||||||
|
|
||||||
|
for s in $shells; do
|
||||||
|
if ! command -v "$s" > /dev/null; then
|
||||||
|
shells=${shells/$s/}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#shells} -lt 3 ]]; then
|
||||||
|
echo "No shell configuration to be updated."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
if [ -z "$auto_completion" ]; then
|
if [ -z "$auto_completion" ]; then
|
||||||
ask "Do you want to enable fuzzy auto-completion?"
|
ask "Do you want to enable fuzzy auto-completion?"
|
||||||
@@ -217,11 +247,10 @@ if [ -z "$key_bindings" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
has_zsh=$(command -v zsh > /dev/null && echo 1 || echo 0)
|
|
||||||
shells=$([ $has_zsh -eq 1 ] && echo "bash zsh" || echo "bash")
|
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
echo -n "Generate ~/.fzf.$shell ... "
|
[[ "$shell" = fish ]] && continue
|
||||||
src=~/.fzf.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
||||||
if [ $auto_completion -eq 0 ]; then
|
if [ $auto_completion -eq 0 ]; then
|
||||||
@@ -233,7 +262,7 @@ for shell in $shells; do
|
|||||||
fzf_key_bindings="# $fzf_key_bindings"
|
fzf_key_bindings="# $fzf_key_bindings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > $src << EOF
|
cat > "$src" << EOF
|
||||||
# Setup fzf
|
# Setup fzf
|
||||||
# ---------
|
# ---------
|
||||||
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
|
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
|
||||||
@@ -253,22 +282,21 @@ EOF
|
|||||||
done
|
done
|
||||||
|
|
||||||
# fish
|
# fish
|
||||||
has_fish=$(command -v fish > /dev/null && echo 1 || echo 0)
|
if [[ "$shells" =~ fish ]]; then
|
||||||
if [ $has_fish -eq 1 ]; then
|
|
||||||
echo -n "Update fish_user_paths ... "
|
echo -n "Update fish_user_paths ... "
|
||||||
fish << EOF
|
fish << EOF
|
||||||
echo \$fish_user_paths | grep $fzf_base/bin > /dev/null
|
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null
|
||||||
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
|
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
|
||||||
EOF
|
EOF
|
||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
|
|
||||||
mkdir -p ~/.config/fish/functions
|
mkdir -p "${fish_dir}/functions"
|
||||||
if [ -e ~/.config/fish/functions/fzf.fish ]; then
|
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
||||||
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
|
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
||||||
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
|
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
|
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
if [ $key_bindings -ne 0 ]; then
|
if [ $key_bindings -ne 0 ]; then
|
||||||
echo -n "Symlink $fish_binding ... "
|
echo -n "Symlink $fish_binding ... "
|
||||||
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
||||||
@@ -288,20 +316,22 @@ append_line() {
|
|||||||
line="$2"
|
line="$2"
|
||||||
file="$3"
|
file="$3"
|
||||||
pat="${4:-}"
|
pat="${4:-}"
|
||||||
|
lno=""
|
||||||
|
|
||||||
echo "Update $file:"
|
echo "Update $file:"
|
||||||
echo " - $line"
|
echo " - $line"
|
||||||
[ -f "$file" ] || touch "$file"
|
if [ -f "$file" ]; then
|
||||||
if [ $# -lt 4 ]; then
|
if [ $# -lt 4 ]; then
|
||||||
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
else
|
else
|
||||||
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
if [ -n "$lno" ]; then
|
if [ -n "$lno" ]; then
|
||||||
echo " - Already exists: line #$lno"
|
echo " - Already exists: line #$lno"
|
||||||
else
|
else
|
||||||
if [ $update -eq 1 ]; then
|
if [ $update -eq 1 ]; then
|
||||||
echo >> "$file"
|
[ -f "$file" ] && echo >> "$file"
|
||||||
echo "$line" >> "$file"
|
echo "$line" >> "$file"
|
||||||
echo " + Added"
|
echo " + Added"
|
||||||
else
|
else
|
||||||
@@ -330,12 +360,13 @@ if [ $update_config -eq 2 ]; then
|
|||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
|
[[ "$shell" = fish ]] && continue
|
||||||
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
||||||
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
|
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ $key_bindings -eq 1 ] && [ $has_fish -eq 1 ]; then
|
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ ! -e "$bind_file" ]; then
|
if [ ! -e "$bind_file" ]; then
|
||||||
create_file "$bind_file" \
|
create_file "$bind_file" \
|
||||||
'function fish_user_key_bindings' \
|
'function fish_user_key_bindings' \
|
||||||
@@ -348,9 +379,9 @@ fi
|
|||||||
|
|
||||||
if [ $update_config -eq 1 ]; then
|
if [ $update_config -eq 1 ]; then
|
||||||
echo 'Finished. Restart your shell or reload config file.'
|
echo 'Finished. Restart your shell or reload config file.'
|
||||||
echo ' source ~/.bashrc # bash'
|
[[ "$shells" =~ bash ]] && echo ' source ~/.bashrc # bash'
|
||||||
[ $has_zsh -eq 1 ] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
||||||
[ $has_fish -eq 1 ] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
|
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
|
||||||
echo
|
echo
|
||||||
echo 'Use uninstall script to remove fzf.'
|
echo 'Use uninstall script to remove fzf.'
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -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 "Aug 2017" "fzf 0.17.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jun 2018" "fzf 0.17.4" "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
|
||||||
|
|||||||
@@ -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 "Aug 2017" "fzf 0.17.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jun 2018" "fzf 0.17.4" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -155,9 +155,22 @@ the full screen.
|
|||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
Ignored when \fB--height\fR is not specified.
|
Ignored when \fB--height\fR is not specified.
|
||||||
|
.TP
|
||||||
|
.BI "--layout=" "LAYOUT"
|
||||||
|
Choose the layout (default: default)
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Display from the bottom of the screen"
|
||||||
|
.br
|
||||||
|
.BR reverse " Display from the top of the screen"
|
||||||
|
.br
|
||||||
|
.BR reverse-list " Display from the top of the screen, prompt at the bottom"
|
||||||
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--reverse"
|
.B "--reverse"
|
||||||
Reverse orientation
|
A synonym for \fB--layout=reverse\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--border"
|
.B "--border"
|
||||||
Draw border above and below the finder
|
Draw border above and below the finder
|
||||||
@@ -195,7 +208,7 @@ Input prompt (default: '> ')
|
|||||||
.TP
|
.TP
|
||||||
.BI "--header=" "STR"
|
.BI "--header=" "STR"
|
||||||
The given string will be printed as the sticky header. The lines are displayed
|
The given string will be printed as the sticky header. The lines are displayed
|
||||||
in the given order from top to bottom regardless of \fB--reverse\fR option, and
|
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||||
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
||||||
\fB--ansi\fR is not set.
|
\fB--ansi\fR is not set.
|
||||||
.TP
|
.TP
|
||||||
@@ -272,19 +285,28 @@ 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. \fBfzf --preview='head -$LINES {}'\fR
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||||
|
|
||||||
|
fzf overrides \fB$LINES\fR and \fB$COLUMNS\fR so that they represent the exact
|
||||||
|
size of the preview window.
|
||||||
|
|
||||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
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. \fBfzf --multi --preview='head -10 {+}'\fR
|
||||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
Also, \fB{q}\fR is replaced to the current query string.
|
Also, \fB{q}\fR is replaced to the current query string.
|
||||||
|
|
||||||
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
|
||||||
|
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[%]][:wrap][:hidden]"
|
||||||
@@ -458,6 +480,10 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
\fIspace\fR
|
\fIspace\fR
|
||||||
\fIbspace\fR (\fIbs\fR)
|
\fIbspace\fR (\fIbs\fR)
|
||||||
|
\fIalt-up\fR
|
||||||
|
\fIalt-down\fR
|
||||||
|
\fIalt-left\fR
|
||||||
|
\fIalt-right\fR
|
||||||
\fIalt-enter\fR
|
\fIalt-enter\fR
|
||||||
\fIalt-space\fR
|
\fIalt-space\fR
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
@@ -474,8 +500,12 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fIend\fR
|
\fIend\fR
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
\fIpgup\fR (\fIpage-up\fR)
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
|
\fIshift-up\fR
|
||||||
|
\fIshift-down\fR
|
||||||
\fIshift-left\fR
|
\fIshift-left\fR
|
||||||
\fIshift-right\fR
|
\fIshift-right\fR
|
||||||
|
\fIleft-click\fR
|
||||||
|
\fIright-click\fR
|
||||||
\fIdouble-click\fR
|
\fIdouble-click\fR
|
||||||
or any single character
|
or any single character
|
||||||
|
|
||||||
@@ -487,12 +517,13 @@ triggered whenever the query string is changed.
|
|||||||
\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
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\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
|
\fBcancel\fR (clears query string if not empty, aborts fzf otherwise)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR
|
\fBdelete-char/eof\fR \fIctrl-d\fR
|
||||||
@@ -514,18 +545,19 @@ triggered whenever the query string is changed.
|
|||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\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)
|
||||||
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect-all\fR
|
\fBselect-all\fR
|
||||||
\fBtoggle\fR
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR
|
\fBtoggle-all\fR
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--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--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
@@ -576,7 +608,7 @@ fzf switches to the alternate screen when executing a command. However, if the
|
|||||||
command is expected to complete quickly, and you are not interested in its
|
command is expected to complete quickly, and you are not interested in its
|
||||||
output, you might want to use \fBexecute-silent\fR instead, which silently
|
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||||
executes the command without the switching. Note that fzf will not be
|
executes the command without the switching. Note that fzf will not be
|
||||||
responsible 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).
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ else
|
|||||||
let s:base_dir = expand('<sfile>:h:h')
|
let s:base_dir = expand('<sfile>:h:h')
|
||||||
endif
|
endif
|
||||||
if s:is_win
|
if s:is_win
|
||||||
|
let s:term_marker = '&::FZF'
|
||||||
|
|
||||||
function! s:fzf_call(fn, ...)
|
function! s:fzf_call(fn, ...)
|
||||||
let shellslash = &shellslash
|
let shellslash = &shellslash
|
||||||
try
|
try
|
||||||
@@ -48,11 +50,13 @@ 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
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
return map(['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
||||||
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
||||||
\ ['chcp %origchcp% > nul']
|
\ ['chcp %origchcp% > nul'], 'v:val."\r"')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
|
let s:term_marker = ";#FZF"
|
||||||
|
|
||||||
function! s:fzf_call(fn, ...)
|
function! s:fzf_call(fn, ...)
|
||||||
return call(a:fn, a:000)
|
return call(a:fn, a:000)
|
||||||
endfunction
|
endfunction
|
||||||
@@ -227,6 +231,7 @@ function! s:common_sink(action, lines) abort
|
|||||||
doautocmd BufEnter
|
doautocmd BufEnter
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
|
catch /^Vim:Interrupt$/
|
||||||
finally
|
finally
|
||||||
let &autochdir = autochdir
|
let &autochdir = autochdir
|
||||||
silent! autocmd! fzf_swap
|
silent! autocmd! fzf_swap
|
||||||
@@ -328,17 +333,20 @@ function! fzf#wrap(...)
|
|||||||
return opts
|
return opts
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! s:use_sh()
|
||||||
try
|
let [shell, shellslash] = [&shell, &shellslash]
|
||||||
let oshell = &shell
|
|
||||||
let useshellslash = &shellslash
|
|
||||||
|
|
||||||
if s:is_win
|
if s:is_win
|
||||||
set shell=cmd.exe
|
set shell=cmd.exe
|
||||||
set noshellslash
|
set noshellslash
|
||||||
else
|
else
|
||||||
set shell=sh
|
set shell=sh
|
||||||
endif
|
endif
|
||||||
|
return [shell, shellslash]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! fzf#run(...) abort
|
||||||
|
try
|
||||||
|
let [shell, shellslash] = 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() }
|
||||||
@@ -366,7 +374,7 @@ try
|
|||||||
let source = dict.source
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
if type == 1
|
if type == 1
|
||||||
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(source, temps.input)
|
||||||
@@ -379,10 +387,13 @@ try
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
||||||
let use_height = has_key(dict, 'down') &&
|
let use_height = has_key(dict, 'down') && !has('gui_running') &&
|
||||||
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right')) &&
|
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&
|
||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let use_term = has('nvim') && !s:is_win
|
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||||
|
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||||
|
let use_term = has_nvim_term ||
|
||||||
|
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||||
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
|
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
|
||||||
if prefer_tmux && use_tmux
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
@@ -405,8 +416,7 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
let &shell = oshell
|
let [&shell, &shellslash] = [shell, shellslash]
|
||||||
let &shellslash = useshellslash
|
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -577,7 +587,7 @@ function! s:calc_size(max, val, dict)
|
|||||||
let srcsz = len(a:dict.source)
|
let srcsz = len(a:dict.source)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
|
let opts = s:evaluate_opts(get(a:dict, 'options', '')).$FZF_DEFAULT_OPTS
|
||||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
||||||
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||||
@@ -625,6 +635,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
let [ppos, winopts] = s:split(a:dict)
|
let [ppos, winopts] = s:split(a:dict)
|
||||||
|
call s:use_sh()
|
||||||
let b:fzf = a:dict
|
let b:fzf = a:dict
|
||||||
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
||||||
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
||||||
@@ -640,7 +651,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
function! fzf.on_exit(id, code, _event)
|
function! fzf.on_exit(id, code, ...)
|
||||||
if s:getpos() == self.ppos " {'window': 'enew'}
|
if s:getpos() == self.ppos " {'window': 'enew'}
|
||||||
for [opt, val] in items(self.winopts)
|
for [opt, val] in items(self.winopts)
|
||||||
execute 'let' opt '=' val
|
execute 'let' opt '=' val
|
||||||
@@ -678,7 +689,22 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
if s:present(a:dict, 'dir')
|
if s:present(a:dict, 'dir')
|
||||||
execute 'lcd' s:escape(a:dict.dir)
|
execute 'lcd' s:escape(a:dict.dir)
|
||||||
endif
|
endif
|
||||||
call termopen(a:command . ';#FZF', fzf)
|
if s:is_win
|
||||||
|
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||||
|
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||||
|
let command = fzf.temps.batchfile
|
||||||
|
else
|
||||||
|
let command = a:command
|
||||||
|
endif
|
||||||
|
let command .= s:term_marker
|
||||||
|
if has('nvim')
|
||||||
|
call termopen(command, fzf)
|
||||||
|
else
|
||||||
|
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
||||||
|
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
||||||
|
call term_wait(fzf.buf, 20)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
finally
|
finally
|
||||||
if s:present(a:dict, 'dir')
|
if s:present(a:dict, 'dir')
|
||||||
lcd -
|
lcd -
|
||||||
@@ -750,7 +776,10 @@ let s:default_action = {
|
|||||||
\ 'ctrl-v': 'vsplit' }
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
function! s:shortpath()
|
function! s:shortpath()
|
||||||
let short = pathshorten(fnamemodify(getcwd(), ':~:.'))
|
let short = fnamemodify(getcwd(), ':~:.')
|
||||||
|
if !has('win32unix')
|
||||||
|
let short = pathshorten(short)
|
||||||
|
endif
|
||||||
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
||||||
return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\').'$' ? '' : slash)
|
return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\').'$' ? '' : slash)
|
||||||
endfunction
|
endfunction
|
||||||
@@ -767,6 +796,7 @@ function! s:cmd(bang, ...) abort
|
|||||||
else
|
else
|
||||||
let prompt = s:shortpath()
|
let prompt = s:shortpath()
|
||||||
endif
|
endif
|
||||||
|
let prompt = strwidth(prompt) < &columns - 20 ? prompt : '> '
|
||||||
call extend(opts.options, ['--prompt', prompt])
|
call extend(opts.options, ['--prompt', prompt])
|
||||||
call extend(opts.options, args)
|
call extend(opts.options, args)
|
||||||
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/bin/bash
|
|
||||||
# ____ ____
|
# ____ ____
|
||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
@@ -38,9 +37,9 @@ __fzfcmd_complete() {
|
|||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_orig_completion_filter() {
|
__fzf_orig_completion_filter() {
|
||||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
|
||||||
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_opts_completion() {
|
_fzf_opts_completion() {
|
||||||
@@ -113,7 +112,7 @@ _fzf_opts_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_handle_dynamic_completion() {
|
_fzf_handle_dynamic_completion() {
|
||||||
local cmd orig_var orig ret orig_cmd
|
local cmd orig_var orig ret orig_cmd orig_complete
|
||||||
cmd="$1"
|
cmd="$1"
|
||||||
shift
|
shift
|
||||||
orig_cmd="$1"
|
orig_cmd="$1"
|
||||||
@@ -122,10 +121,18 @@ _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)
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
# _completion_loader may not have updated completion for the command
|
||||||
source "${BASH_SOURCE[0]}"
|
if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||||
|
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
||||||
|
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||||
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
|
else
|
||||||
|
eval "$orig_complete"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
return $ret
|
return $ret
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -141,7 +148,7 @@ __fzf_generic_path_completion() {
|
|||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base"
|
||||||
|
|
||||||
dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while true; do
|
while true; do
|
||||||
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
@@ -152,6 +159,7 @@ __fzf_generic_path_completion() {
|
|||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
|
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
COMPREPLY=( "$matches" )
|
COMPREPLY=( "$matches" )
|
||||||
else
|
else
|
||||||
@@ -215,7 +223,7 @@ _fzf_complete_kill() {
|
|||||||
|
|
||||||
local selected fzf
|
local selected fzf
|
||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
selected=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -233,8 +241,8 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
cat <(cat ~/.ssh/config /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 grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | 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
|
||||||
)
|
)
|
||||||
@@ -274,9 +282,9 @@ a_cmds="
|
|||||||
x_cmds="kill ssh telnet unset unalias export"
|
x_cmds="kill ssh telnet unset unalias export"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
eval $(complete |
|
eval "$(complete |
|
||||||
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
||||||
_fzf_orig_completion_filter)
|
__fzf_orig_completion_filter)"
|
||||||
|
|
||||||
if type _completion_loader > /dev/null 2>&1; then
|
if type _completion_loader > /dev/null 2>&1; then
|
||||||
_fzf_completion_loader=1
|
_fzf_completion_loader=1
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/bin/zsh
|
|
||||||
# ____ ____
|
# ____ ____
|
||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
@@ -37,8 +36,7 @@ __fzfcmd_complete() {
|
|||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
||||||
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
base=$1
|
||||||
base=${(Q)1}
|
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
@@ -47,14 +45,14 @@ __fzf_generic_path_completion() {
|
|||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
dir="$base"
|
eval "base=$base"
|
||||||
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [[ -z "$dir" || -d ${~dir} ]]; then
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
dir=${~dir}
|
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
@@ -116,8 +114,8 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
command cat <(cat ~/.ssh/config /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 grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | 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
|
||||||
)
|
)
|
||||||
@@ -163,7 +161,7 @@ fzf-completion() {
|
|||||||
# 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
|
||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
matches=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ __fzf_history__() (
|
|||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= history |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -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
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
@@ -80,7 +80,7 @@ if [[ ! -o vi ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
|
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
||||||
@@ -110,7 +110,7 @@ else
|
|||||||
bind -m vi-command '"\C-t": "i\C-t"'
|
bind -m vi-command '"\C-t": "i\C-t"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
|
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
|
||||||
bind -m vi-command '"\C-r": "i\C-r"'
|
bind -m vi-command '"\C-r": "i\C-r"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ fzf-file-widget() {
|
|||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey '^T' fzf-file-widget
|
||||||
|
|
||||||
|
# Ensure precmds are run after cd
|
||||||
|
fzf-redraw-prompt() {
|
||||||
|
local precmd
|
||||||
|
for precmd in $precmd_functions; do
|
||||||
|
$precmd
|
||||||
|
done
|
||||||
|
zle reset-prompt
|
||||||
|
}
|
||||||
|
zle -N fzf-redraw-prompt
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
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 \
|
||||||
@@ -48,7 +58,7 @@ fzf-cd-widget() {
|
|||||||
fi
|
fi
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle fzf-redraw-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
@@ -59,8 +69,8 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
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 2> /dev/null
|
||||||
selected=( $(fc -l 1 |
|
selected=( $(fc -rl 1 |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(q)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=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func TestExtractColor(t *testing.T) {
|
|||||||
output, ansiOffsets, newState := extractColor(src, state, nil)
|
output, ansiOffsets, newState := extractColor(src, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
if output != "hello world" {
|
if output != "hello world" {
|
||||||
t.Errorf("Invalid output: %s %s", output, []rune(output))
|
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
||||||
}
|
}
|
||||||
fmt.Println(src, ansiOffsets, clean)
|
fmt.Println(src, ansiOffsets, clean)
|
||||||
assertion(ansiOffsets, state)
|
assertion(ansiOffsets, state)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.17.0"
|
version = "0.17.4"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -55,11 +55,11 @@ var defaultCommand string
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if !util.IsWindows() {
|
if !util.IsWindows() {
|
||||||
defaultCommand = `command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
} else {
|
} else {
|
||||||
defaultCommand = `dir /s/b`
|
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const usage = `usage: fzf [options]
|
|||||||
height instead of using fullscreen
|
height instead of using fullscreen
|
||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--reverse Reverse orientation
|
--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
|
--inline-info Display finder info inline with the query
|
||||||
@@ -90,7 +90,8 @@ const usage = `usage: fzf [options]
|
|||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info')
|
FZF_DEFAULT_OPTS Default options
|
||||||
|
(e.g. '--layout=reverse --inline-info')
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -132,6 +133,14 @@ const (
|
|||||||
posRight
|
posRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type layoutType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
layoutDefault layoutType = iota
|
||||||
|
layoutReverse
|
||||||
|
layoutReverseList
|
||||||
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
@@ -161,7 +170,7 @@ type Options struct {
|
|||||||
Bold bool
|
Bold bool
|
||||||
Height sizeSpec
|
Height sizeSpec
|
||||||
MinHeight int
|
MinHeight int
|
||||||
Reverse bool
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
@@ -211,7 +220,7 @@ func defaultOptions() *Options {
|
|||||||
Black: false,
|
Black: false,
|
||||||
Bold: true,
|
Bold: true,
|
||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Reverse: false,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
@@ -410,6 +419,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.AltSlash
|
chord = tui.AltSlash
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = tui.AltBS
|
chord = tui.AltBS
|
||||||
|
case "alt-up":
|
||||||
|
chord = tui.AltUp
|
||||||
|
case "alt-down":
|
||||||
|
chord = tui.AltDown
|
||||||
|
case "alt-left":
|
||||||
|
chord = tui.AltLeft
|
||||||
|
case "alt-right":
|
||||||
|
chord = tui.AltRight
|
||||||
case "tab":
|
case "tab":
|
||||||
chord = tui.Tab
|
chord = tui.Tab
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
@@ -426,10 +443,18 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.PgUp
|
chord = tui.PgUp
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
chord = tui.PgDn
|
chord = tui.PgDn
|
||||||
|
case "shift-up":
|
||||||
|
chord = tui.SUp
|
||||||
|
case "shift-down":
|
||||||
|
chord = tui.SDown
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
chord = tui.SLeft
|
chord = tui.SLeft
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
chord = tui.SRight
|
chord = tui.SRight
|
||||||
|
case "left-click":
|
||||||
|
chord = tui.LeftClick
|
||||||
|
case "right-click":
|
||||||
|
chord = tui.RightClick
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = tui.DoubleClick
|
chord = tui.DoubleClick
|
||||||
case "f10":
|
case "f10":
|
||||||
@@ -658,8 +683,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actAbort)
|
appendAction(actAbort)
|
||||||
case "accept":
|
case "accept":
|
||||||
appendAction(actAccept)
|
appendAction(actAccept)
|
||||||
|
case "accept-non-empty":
|
||||||
|
appendAction(actAcceptNonEmpty)
|
||||||
case "print-query":
|
case "print-query":
|
||||||
appendAction(actPrintQuery)
|
appendAction(actPrintQuery)
|
||||||
|
case "replace-query":
|
||||||
|
appendAction(actReplaceQuery)
|
||||||
case "backward-char":
|
case "backward-char":
|
||||||
appendAction(actBackwardChar)
|
appendAction(actBackwardChar)
|
||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
@@ -833,13 +862,24 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseHeight(str string) sizeSpec {
|
func parseHeight(str string) sizeSpec {
|
||||||
if util.IsWindows() {
|
|
||||||
errorExit("--height options is currently not supported on Windows")
|
|
||||||
}
|
|
||||||
size := parseSize(str, 100, "height")
|
size := parseSize(str, 100, "height")
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseLayout(str string) layoutType {
|
||||||
|
switch str {
|
||||||
|
case "default":
|
||||||
|
return layoutDefault
|
||||||
|
case "reverse":
|
||||||
|
return layoutReverse
|
||||||
|
case "reverse-list":
|
||||||
|
return layoutReverseList
|
||||||
|
default:
|
||||||
|
errorExit("invalid layout (expected: default / reverse / reverse-list)")
|
||||||
|
}
|
||||||
|
return layoutDefault
|
||||||
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
// Default
|
// Default
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
@@ -1020,10 +1060,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Bold = true
|
opts.Bold = true
|
||||||
case "--no-bold":
|
case "--no-bold":
|
||||||
opts.Bold = false
|
opts.Bold = false
|
||||||
|
case "--layout":
|
||||||
|
opts.Layout = parseLayout(
|
||||||
|
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
|
||||||
case "--reverse":
|
case "--reverse":
|
||||||
opts.Reverse = true
|
opts.Layout = layoutReverse
|
||||||
case "--no-reverse":
|
case "--no-reverse":
|
||||||
opts.Reverse = false
|
opts.Layout = layoutDefault
|
||||||
case "--cycle":
|
case "--cycle":
|
||||||
opts.Cycle = true
|
opts.Cycle = true
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
@@ -1139,6 +1182,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
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 {
|
||||||
|
opts.Layout = parseLayout(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 {
|
||||||
@@ -1203,6 +1248,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
|
if util.IsWindows() && opts.Height.size > 0 {
|
||||||
|
errorExit("--height option is currently not supported on Windows")
|
||||||
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestDelimiterRegexString(t *testing.T) {
|
|||||||
tokens[2].text.ToString() != "---*" ||
|
tokens[2].text.ToString() != "---*" ||
|
||||||
tokens[3].text.ToString() != "*" ||
|
tokens[3].text.ToString() != "*" ||
|
||||||
tokens[4].text.ToString() != "---" {
|
tokens[4].text.ToString() != "---" {
|
||||||
t.Errorf("%s %s %d", delim, tokens, len(tokens))
|
t.Errorf("%s %v %d", delim, tokens, len(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
if len(ranges) != 1 ||
|
if len(ranges) != 1 ||
|
||||||
ranges[0].begin != rangeEllipsis ||
|
ranges[0].begin != rangeEllipsis ||
|
||||||
ranges[0].end != rangeEllipsis {
|
ranges[0].end != rangeEllipsis {
|
||||||
t.Errorf("%s", ranges)
|
t.Errorf("%v", ranges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
||||||
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
||||||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
||||||
t.Errorf("%s", ranges)
|
t.Errorf("%v", ranges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
||||||
@@ -108,7 +108,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -117,7 +117,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 2 {
|
if len(opts.Nth) != 2 {
|
||||||
t.Errorf("nth should not be empty: %s", opts.Nth)
|
t.Errorf("nth should not be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -34,6 +35,11 @@ type term struct {
|
|||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a term.
|
||||||
|
func (t term) String() string {
|
||||||
|
return fmt.Sprintf("term{typ: %d, inv: %v, text: []rune(%q), caseSensitive: %v}", t.typ, t.inv, string(t.text), t.caseSensitive)
|
||||||
|
}
|
||||||
|
|
||||||
type termSet []term
|
type termSet []term
|
||||||
|
|
||||||
// Pattern represents search pattern
|
// Pattern represents search pattern
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ func TestParseTermsExtended(t *testing.T) {
|
|||||||
terms[8][1].typ != termExact || terms[8][1].inv ||
|
terms[8][1].typ != termExact || terms[8][1].inv ||
|
||||||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
||||||
terms[8][3].typ != termExact || !terms[8][3].inv {
|
terms[8][3].typ != termExact || !terms[8][3].inv {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
for _, termSet := range terms[:8] {
|
for _, termSet := range terms[:8] {
|
||||||
term := termSet[0]
|
term := termSet[0]
|
||||||
if len(term.text) != 3 {
|
if len(term.text) != 3 {
|
||||||
t.Errorf("%s", term)
|
t.Errorf("%v", term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,14 +53,14 @@ func TestParseTermsExtendedExact(t *testing.T) {
|
|||||||
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
||||||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
||||||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsEmpty(t *testing.T) {
|
func TestParseTermsEmpty(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
||||||
if len(terms) != 0 {
|
if len(terms) != 0 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ func TestExact(t *testing.T) {
|
|||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
t.Errorf("pos is expected to be nil")
|
t.Errorf("pos is expected to be nil")
|
||||||
@@ -90,7 +90,7 @@ func TestEqual(t *testing.T) {
|
|||||||
res, pos := algo.EqualMatch(
|
res, pos := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
t.Errorf("pos is expected to be nil")
|
t.Errorf("pos is expected to be nil")
|
||||||
|
|||||||
@@ -56,9 +56,11 @@ func (r *Reader) ReadSource() {
|
|||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
cmd = defaultCommand
|
// The default command for *nix requires bash
|
||||||
|
success = r.readFromCommand("bash", defaultCommand)
|
||||||
|
} else {
|
||||||
|
success = r.readFromCommand("sh", cmd)
|
||||||
}
|
}
|
||||||
success = r.readFromCommand(cmd)
|
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
}
|
}
|
||||||
@@ -100,8 +102,8 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(cmd string) bool {
|
func (r *Reader) readFromCommand(shell string, cmd string) bool {
|
||||||
listCommand := util.ExecCommand(cmd)
|
listCommand := util.ExecCommandWith(shell, cmd)
|
||||||
out, err := listCommand.StdoutPipe()
|
out, err := listCommand.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand(`echo abc && echo def`))
|
reader.fin(reader.readFromCommand("sh", `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 +48,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.fin(reader.readFromCommand(`no-such-command`))
|
reader.fin(reader.readFromCommand("sh", `no-such-command`))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
|
|||||||
195
src/terminal.go
195
src/terminal.go
@@ -24,7 +24,7 @@ import (
|
|||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[0-9,-.]*}|{q})")
|
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q})")
|
||||||
}
|
}
|
||||||
|
|
||||||
type jumpMode int
|
type jumpMode int
|
||||||
@@ -59,7 +59,7 @@ type Terminal struct {
|
|||||||
inlineInfo bool
|
inlineInfo bool
|
||||||
prompt string
|
prompt string
|
||||||
promptLen int
|
promptLen int
|
||||||
reverse bool
|
layout layoutType
|
||||||
fullscreen bool
|
fullscreen bool
|
||||||
hscroll bool
|
hscroll bool
|
||||||
hscrollOff int
|
hscrollOff int
|
||||||
@@ -170,6 +170,7 @@ const (
|
|||||||
actBeginningOfLine
|
actBeginningOfLine
|
||||||
actAbort
|
actAbort
|
||||||
actAccept
|
actAccept
|
||||||
|
actAcceptNonEmpty
|
||||||
actBackwardChar
|
actBackwardChar
|
||||||
actBackwardDeleteChar
|
actBackwardDeleteChar
|
||||||
actBackwardWord
|
actBackwardWord
|
||||||
@@ -203,6 +204,7 @@ const (
|
|||||||
actJump
|
actJump
|
||||||
actJumpAccept
|
actJumpAccept
|
||||||
actPrintQuery
|
actPrintQuery
|
||||||
|
actReplaceQuery
|
||||||
actToggleSort
|
actToggleSort
|
||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
@@ -219,6 +221,12 @@ const (
|
|||||||
actTop
|
actTop
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type placeholderFlags struct {
|
||||||
|
plus bool
|
||||||
|
preserveSpace bool
|
||||||
|
query bool
|
||||||
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
actions := make([]action, len(types))
|
actions := make([]action, len(types))
|
||||||
for idx, t := range types {
|
for idx, t := range types {
|
||||||
@@ -275,9 +283,14 @@ func defaultKeymap() map[int][]action {
|
|||||||
keymap[tui.PgUp] = toActions(actPageUp)
|
keymap[tui.PgUp] = toActions(actPageUp)
|
||||||
keymap[tui.PgDn] = toActions(actPageDown)
|
keymap[tui.PgDn] = toActions(actPageDown)
|
||||||
|
|
||||||
|
keymap[tui.SUp] = toActions(actPreviewUp)
|
||||||
|
keymap[tui.SDown] = toActions(actPreviewDown)
|
||||||
|
|
||||||
keymap[tui.Rune] = toActions(actRune)
|
keymap[tui.Rune] = toActions(actRune)
|
||||||
keymap[tui.Mouse] = toActions(actMouse)
|
keymap[tui.Mouse] = toActions(actMouse)
|
||||||
keymap[tui.DoubleClick] = toActions(actAccept)
|
keymap[tui.DoubleClick] = toActions(actAccept)
|
||||||
|
keymap[tui.LeftClick] = toActions(actIgnore)
|
||||||
|
keymap[tui.RightClick] = toActions(actToggle)
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,10 +302,11 @@ func trimQuery(query string) []rune {
|
|||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var header []string
|
var header []string
|
||||||
if opts.Reverse {
|
switch opts.Layout {
|
||||||
header = opts.Header
|
case layoutDefault, layoutReverseList:
|
||||||
} else {
|
|
||||||
header = reverseStringArray(opts.Header)
|
header = reverseStringArray(opts.Header)
|
||||||
|
default:
|
||||||
|
header = opts.Header
|
||||||
}
|
}
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
@@ -350,7 +364,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
inlineInfo: opts.InlineInfo,
|
||||||
reverse: opts.Reverse,
|
layout: opts.Layout,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
hscroll: opts.Hscroll,
|
hscroll: opts.Hscroll,
|
||||||
hscrollOff: opts.HscrollOff,
|
hscrollOff: opts.HscrollOff,
|
||||||
@@ -623,17 +637,28 @@ func (t *Terminal) resizeWindows() {
|
|||||||
width,
|
width,
|
||||||
height, tui.BorderNone)
|
height, tui.BorderNone)
|
||||||
}
|
}
|
||||||
if !t.tui.IsOptimized() {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) move(y int, x int, clear bool) {
|
func (t *Terminal) move(y int, x int, clear bool) {
|
||||||
if !t.reverse {
|
h := t.window.Height()
|
||||||
y = t.window.Height() - y - 1
|
|
||||||
|
switch t.layout {
|
||||||
|
case layoutDefault:
|
||||||
|
y = h - y - 1
|
||||||
|
case layoutReverseList:
|
||||||
|
n := 2 + len(t.header)
|
||||||
|
if t.inlineInfo {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
if y < n {
|
||||||
|
y = h - y - 1
|
||||||
|
} else {
|
||||||
|
y -= n
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if clear {
|
if clear {
|
||||||
@@ -693,7 +718,11 @@ func (t *Terminal) printInfo() {
|
|||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
if !t.success && t.count == 0 {
|
if !t.success && t.count == 0 {
|
||||||
output += " [ERROR]"
|
if len(os.Getenv("FZF_DEFAULT_COMMAND")) > 0 {
|
||||||
|
output = "[$FZF_DEFAULT_COMMAND failed]"
|
||||||
|
} else {
|
||||||
|
output = "[default command failed - $FZF_DEFAULT_COMMAND required]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if pos+len(output) <= t.window.Width() {
|
if pos+len(output) <= t.window.Width() {
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
@@ -722,7 +751,7 @@ func (t *Terminal) printHeader() {
|
|||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(Result{item: item},
|
t.printHighlighted(Result{item: item},
|
||||||
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false, false)
|
tui.AttrRegular, tui.ColHeader, tui.ColHeader, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,7 +762,7 @@ func (t *Terminal) printList() {
|
|||||||
count := t.merger.Length() - t.offset
|
count := t.merger.Length() - t.offset
|
||||||
for j := 0; j < maxy; j++ {
|
for j := 0; j < maxy; j++ {
|
||||||
i := j
|
i := j
|
||||||
if !t.reverse {
|
if t.layout == layoutDefault {
|
||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + len(t.header)
|
line := i + 2 + len(t.header)
|
||||||
@@ -775,8 +804,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized renderer can simply erase to the end of the window
|
t.move(line, 0, false)
|
||||||
t.move(line, 0, t.tui.IsOptimized())
|
|
||||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||||
if current {
|
if current {
|
||||||
if selected {
|
if selected {
|
||||||
@@ -793,12 +821,10 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
|||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
|
||||||
}
|
}
|
||||||
if !t.tui.IsOptimized() {
|
|
||||||
fillSpaces := prevLine.width - newLine.width
|
fillSpaces := prevLine.width - newLine.width
|
||||||
if fillSpaces > 0 {
|
if fillSpaces > 0 {
|
||||||
t.window.Print(strings.Repeat(" ", fillSpaces))
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
t.prevLines[i] = newLine
|
t.prevLines[i] = newLine
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -990,7 +1016,7 @@ func (t *Terminal) printPreview() {
|
|||||||
if t.theme != nil && ansi != nil && ansi.colored() {
|
if t.theme != nil && ansi != nil && ansi.colored() {
|
||||||
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
||||||
} else {
|
} else {
|
||||||
fillRet = t.pwindow.Fill(str)
|
fillRet = t.pwindow.CFill(tui.ColNormal.Fg(), tui.ColNormal.Bg(), tui.AttrRegular, str)
|
||||||
}
|
}
|
||||||
return fillRet == tui.FillContinue
|
return fillRet == tui.FillContinue
|
||||||
})
|
})
|
||||||
@@ -1108,23 +1134,62 @@ func keyMatch(key int, event tui.Event) bool {
|
|||||||
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func quoteEntryCmd(entry string) string {
|
||||||
|
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
||||||
|
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
||||||
|
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
||||||
|
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
||||||
|
return "^" + match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
func quoteEntry(entry string) string {
|
||||||
if util.IsWindows() {
|
if util.IsWindows() {
|
||||||
return strconv.Quote(strings.Replace(entry, "\"", "\\\"", -1))
|
return quoteEntryCmd(entry)
|
||||||
}
|
}
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPlusFlag(template string) bool {
|
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||||
for _, match := range placeholder.FindAllString(template, -1) {
|
flags := placeholderFlags{}
|
||||||
|
|
||||||
if match[0] == '\\' {
|
if match[0] == '\\' {
|
||||||
continue
|
// Escaped placeholder pattern
|
||||||
|
return true, match[1:], flags
|
||||||
}
|
}
|
||||||
if match[1] == '+' {
|
|
||||||
return true
|
skipChars := 1
|
||||||
|
for _, char := range match[1:] {
|
||||||
|
switch char {
|
||||||
|
case '+':
|
||||||
|
flags.plus = true
|
||||||
|
skipChars++
|
||||||
|
case 's':
|
||||||
|
flags.preserveSpace = true
|
||||||
|
skipChars++
|
||||||
|
case 'q':
|
||||||
|
flags.query = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
matchWithoutFlags := "{" + match[skipChars:]
|
||||||
|
|
||||||
|
return false, matchWithoutFlags, flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPreviewFlags(template string) (plus bool, query bool) {
|
||||||
|
for _, match := range placeholder.FindAllString(template, -1) {
|
||||||
|
_, _, flags := parsePlaceholder(match)
|
||||||
|
if flags.plus {
|
||||||
|
plus = true
|
||||||
|
}
|
||||||
|
if flags.query {
|
||||||
|
query = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
||||||
@@ -1137,9 +1202,10 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
selected = []*Item{}
|
selected = []*Item{}
|
||||||
}
|
}
|
||||||
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
||||||
// Escaped pattern
|
escaped, match, flags := parsePlaceholder(match)
|
||||||
if match[0] == '\\' {
|
|
||||||
return match[1:]
|
if escaped {
|
||||||
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current query
|
// Current query
|
||||||
@@ -1147,13 +1213,8 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
return quoteEntry(query)
|
return quoteEntry(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
plusFlag := forcePlus
|
|
||||||
if match[1] == '+' {
|
|
||||||
match = "{" + match[2:]
|
|
||||||
plusFlag = true
|
|
||||||
}
|
|
||||||
items := current
|
items := current
|
||||||
if plusFlag {
|
if flags.plus || forcePlus {
|
||||||
items = selected
|
items = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1189,7 +1250,9 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
str = str[:delims[len(delims)-1][0]]
|
str = str[:delims[len(delims)-1][0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !flags.preserveSpace {
|
||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
|
}
|
||||||
replacements[idx] = quoteEntry(str)
|
replacements[idx] = quoteEntry(str)
|
||||||
}
|
}
|
||||||
return strings.Join(replacements, " ")
|
return strings.Join(replacements, " ")
|
||||||
@@ -1245,14 +1308,29 @@ 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()
|
||||||
if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 {
|
plus, query := hasPreviewFlags(template)
|
||||||
|
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
|
||||||
return current != nil, []*Item{current, current}
|
return current != nil, []*Item{current, current}
|
||||||
}
|
}
|
||||||
sels := make([]*Item, len(t.selected)+1)
|
|
||||||
|
// We would still want to update preview window even if there is no match if
|
||||||
|
// 1. command template contains {q} and the query string is not empty
|
||||||
|
// 2. or it contains {+} and we have more than one item already selected.
|
||||||
|
// To do so, we pass an empty Item instead of nil to trigger an update.
|
||||||
|
if current == nil {
|
||||||
|
current = &Item{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sels []*Item
|
||||||
|
if len(t.selected) == 0 {
|
||||||
|
sels = []*Item{current, current}
|
||||||
|
} else {
|
||||||
|
sels = make([]*Item, len(t.selected)+1)
|
||||||
sels[0] = current
|
sels[0] = current
|
||||||
for i, sel := range t.sortSelected() {
|
for i, sel := range t.sortSelected() {
|
||||||
sels[i+1] = sel.item
|
sels[i+1] = sel.item
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true, sels
|
return true, sels
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1358,6 +1436,12 @@ func (t *Terminal) Loop() {
|
|||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(t.preview.command,
|
||||||
t.ansi, t.delimiter, false, string(t.input), request)
|
t.ansi, t.delimiter, false, string(t.input), request)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
|
if t.pwindow != nil {
|
||||||
|
env := os.Environ()
|
||||||
|
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height()))
|
||||||
|
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()))
|
||||||
|
cmd.Env = env
|
||||||
|
}
|
||||||
out, _ := cmd.CombinedOutput()
|
out, _ := cmd.CombinedOutput()
|
||||||
t.reqBox.Set(reqPreviewDisplay, string(out))
|
t.reqBox.Set(reqPreviewDisplay, string(out))
|
||||||
} else {
|
} else {
|
||||||
@@ -1552,6 +1636,11 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actPrintQuery:
|
case actPrintQuery:
|
||||||
req(reqPrintQuery)
|
req(reqPrintQuery)
|
||||||
|
case actReplaceQuery:
|
||||||
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
|
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
||||||
|
t.cx = len(t.input)
|
||||||
|
}
|
||||||
case actAbort:
|
case actAbort:
|
||||||
req(reqQuit)
|
req(reqQuit)
|
||||||
case actDeleteChar:
|
case actDeleteChar:
|
||||||
@@ -1605,12 +1694,12 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actToggleIn:
|
case actToggleIn:
|
||||||
if t.reverse {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown}, mapkey)
|
||||||
case actToggleOut:
|
case actToggleOut:
|
||||||
if t.reverse {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown}, mapkey)
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
@@ -1634,6 +1723,10 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList)
|
req(reqList)
|
||||||
case actAccept:
|
case actAccept:
|
||||||
req(reqClose)
|
req(reqClose)
|
||||||
|
case actAcceptNonEmpty:
|
||||||
|
if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
|
||||||
|
req(reqClose)
|
||||||
|
}
|
||||||
case actClearScreen:
|
case actClearScreen:
|
||||||
req(reqRedraw)
|
req(reqRedraw)
|
||||||
case actTop:
|
case actTop:
|
||||||
@@ -1734,13 +1827,21 @@ func (t *Terminal) Loop() {
|
|||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
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))
|
||||||
if !t.reverse {
|
|
||||||
my = t.window.Height() - my - 1
|
|
||||||
}
|
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
|
h := t.window.Height()
|
||||||
|
switch t.layout {
|
||||||
|
case layoutDefault:
|
||||||
|
my = h - my - 1
|
||||||
|
case layoutReverseList:
|
||||||
|
if my < h-min {
|
||||||
|
my += min
|
||||||
|
} else {
|
||||||
|
my = h - my - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
if me.Double {
|
if me.Double {
|
||||||
// Double-click
|
// Double-click
|
||||||
if my >= min {
|
if my >= min {
|
||||||
@@ -1758,6 +1859,10 @@ func (t *Terminal) Loop() {
|
|||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
|
if me.Left {
|
||||||
|
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
|
||||||
|
}
|
||||||
|
return doActions(t.keymap[tui.RightClick], tui.RightClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1799,6 +1904,12 @@ func (t *Terminal) Loop() {
|
|||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
_, q := hasPreviewFlags(t.preview.command)
|
||||||
|
if q {
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
}
|
||||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
t.eventBox.Set(EvtSearchNew, t.sort)
|
||||||
}
|
}
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
@@ -1823,7 +1934,7 @@ func (t *Terminal) constrain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) vmove(o int, allowCycle bool) {
|
func (t *Terminal) vmove(o int, allowCycle bool) {
|
||||||
if t.reverse {
|
if t.layout != layoutDefault {
|
||||||
o *= -1
|
o *= -1
|
||||||
}
|
}
|
||||||
dest := t.cy + o
|
dest := t.cy + o
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||||
|
|
||||||
|
delim := "'"
|
||||||
|
var regex *regexp.Regexp
|
||||||
|
|
||||||
var result string
|
var result string
|
||||||
check := func(expected string) {
|
check := func(expected string) {
|
||||||
if result != expected {
|
if result != expected {
|
||||||
@@ -72,6 +75,31 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
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{}, 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
|
||||||
|
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo ' foo'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo 'bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
|
// Whitespace preserving flag with regex delimiter
|
||||||
|
regex = regexp.MustCompile("\\w+")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
|
check("echo ' '")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
|
check("echo ''\\'''")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
|
check("echo ' '")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
||||||
check("echo /")
|
check("echo /")
|
||||||
@@ -81,13 +109,31 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
check("echo /' foo'\\''bar baz'")
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
delim := "'"
|
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, 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}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQuoteEntryCmd(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
`"`: `^"\^"^"`,
|
||||||
|
`\`: `^"\\^"`,
|
||||||
|
`\"`: `^"\\\^"^"`,
|
||||||
|
`"\\\"`: `^"\^"\\\\\\\^"^"`,
|
||||||
|
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
|
||||||
|
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
|
||||||
|
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for input, expected := range tests {
|
||||||
|
escaped := quoteEntryCmd(input)
|
||||||
|
if escaped != expected {
|
||||||
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,12 +24,22 @@ type Token struct {
|
|||||||
prefixLength int32
|
prefixLength int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Token.
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("Token{text: %s, prefixLength: %d}", t.text, t.prefixLength)
|
||||||
|
}
|
||||||
|
|
||||||
// Delimiter for tokenizing the input
|
// Delimiter for tokenizing the input
|
||||||
type Delimiter struct {
|
type Delimiter struct {
|
||||||
regex *regexp.Regexp
|
regex *regexp.Regexp
|
||||||
str *string
|
str *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Delimeter.
|
||||||
|
func (d Delimiter) String() string {
|
||||||
|
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||||
|
}
|
||||||
|
|
||||||
func newRange(begin int, end int) Range {
|
func newRange(begin int, end int) Range {
|
||||||
if begin == 1 {
|
if begin == 1 {
|
||||||
begin = rangeEllipsis
|
begin = rangeEllipsis
|
||||||
|
|||||||
@@ -9,35 +9,35 @@ func TestParseRange(t *testing.T) {
|
|||||||
i := ".."
|
i := ".."
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3.."
|
i := "3.."
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != rangeEllipsis {
|
if r.begin != 3 || r.end != rangeEllipsis {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3..5"
|
i := "3..5"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != 5 {
|
if r.begin != 3 || r.end != 5 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "-3..-5"
|
i := "-3..-5"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != -3 || r.end != -5 {
|
if r.begin != -3 || r.end != -5 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3"
|
i := "3"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != 3 {
|
if r.begin != 3 || r.end != 3 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ func (r *FullscreenRenderer) Refresh() {}
|
|||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool { return false }
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R"
|
|||||||
func openTtyIn() *os.File {
|
func openTtyIn() *os.File {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
tty := ttyname()
|
||||||
|
if len(tty) > 0 {
|
||||||
|
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
}
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
@@ -48,12 +54,14 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
|||||||
runes := []rune{}
|
runes := []rune{}
|
||||||
for len(bytes) > 0 {
|
for len(bytes) > 0 {
|
||||||
r, sz := utf8.DecodeRune(bytes)
|
r, sz := utf8.DecodeRune(bytes)
|
||||||
if r == utf8.RuneError || r < 32 &&
|
nlcr := r == '\n' || r == '\r'
|
||||||
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
|
if r >= 32 || r == '\x1b' || nlcr {
|
||||||
runes = append(runes, '?')
|
if r == utf8.RuneError || nlcr && !allowNLCR {
|
||||||
|
runes = append(runes, ' ')
|
||||||
} else {
|
} else {
|
||||||
runes = append(runes, r)
|
runes = append(runes, r)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bytes = bytes[sz:]
|
bytes = bytes[sz:]
|
||||||
}
|
}
|
||||||
r.queued += string(runes)
|
r.queued += string(runes)
|
||||||
@@ -105,6 +113,7 @@ type LightWindow struct {
|
|||||||
posx int
|
posx int
|
||||||
posy int
|
posy int
|
||||||
tabstop int
|
tabstop int
|
||||||
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,6 +368,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
||||||
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
||||||
}
|
}
|
||||||
|
alt := false
|
||||||
|
if len(r.buffer) > 2 && r.buffer[1] == ESC {
|
||||||
|
r.buffer = r.buffer[1:]
|
||||||
|
alt = true
|
||||||
|
}
|
||||||
switch r.buffer[1] {
|
switch r.buffer[1] {
|
||||||
case 32:
|
case 32:
|
||||||
return Event{AltSpace, 0, nil}
|
return Event{AltSpace, 0, nil}
|
||||||
@@ -379,12 +393,25 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
*sz = 3
|
*sz = 3
|
||||||
switch r.buffer[2] {
|
switch r.buffer[2] {
|
||||||
case 68:
|
case 68:
|
||||||
|
if alt {
|
||||||
|
return Event{AltLeft, 0, nil}
|
||||||
|
}
|
||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case 67:
|
case 67:
|
||||||
|
if alt {
|
||||||
|
// Ugh..
|
||||||
|
return Event{AltRight, 0, nil}
|
||||||
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
case 66:
|
case 66:
|
||||||
|
if alt {
|
||||||
|
return Event{AltDown, 0, nil}
|
||||||
|
}
|
||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case 65:
|
case 65:
|
||||||
|
if alt {
|
||||||
|
return Event{AltUp, 0, nil}
|
||||||
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case 90:
|
case 90:
|
||||||
return Event{BTab, 0, nil}
|
return Event{BTab, 0, nil}
|
||||||
@@ -457,25 +484,22 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
case 59:
|
case ';':
|
||||||
if len(r.buffer) != 6 {
|
if len(r.buffer) != 6 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[4] {
|
switch r.buffer[4] {
|
||||||
case 50:
|
case '2', '5':
|
||||||
switch r.buffer[5] {
|
switch r.buffer[5] {
|
||||||
case 68:
|
case 'A':
|
||||||
return Event{Home, 0, nil}
|
return Event{SUp, 0, nil}
|
||||||
case 67:
|
case 'B':
|
||||||
return Event{End, 0, nil}
|
return Event{SDown, 0, nil}
|
||||||
}
|
case 'C':
|
||||||
case 53:
|
|
||||||
switch r.buffer[5] {
|
|
||||||
case 68:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case 67:
|
|
||||||
return Event{SRight, 0, nil}
|
return Event{SRight, 0, nil}
|
||||||
|
case 'D':
|
||||||
|
return Event{SLeft, 0, nil}
|
||||||
}
|
}
|
||||||
} // r.buffer[4]
|
} // r.buffer[4]
|
||||||
} // r.buffer[3]
|
} // r.buffer[3]
|
||||||
@@ -494,16 +518,19 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||||
mod := r.buffer[3] >= 36
|
mod := r.buffer[3] >= 36
|
||||||
|
left := r.buffer[3] == 32
|
||||||
down := r.buffer[3]%2 == 0
|
down := r.buffer[3]%2 == 0
|
||||||
x := int(r.buffer[4] - 33)
|
x := int(r.buffer[4] - 33)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
if !left { // Right double click is not allowed
|
||||||
|
r.clickY = []int{}
|
||||||
|
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
r.clickY = append(r.clickY, y)
|
r.clickY = append(r.clickY, y)
|
||||||
} else {
|
} else {
|
||||||
r.clickY = []int{y}
|
r.clickY = []int{y}
|
||||||
@@ -516,14 +543,14 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||||
mod := r.buffer[3] >= 100
|
mod := r.buffer[3] >= 100
|
||||||
s := 1 - int(r.buffer[3]%2)*2
|
s := 1 - int(r.buffer[3]%2)*2
|
||||||
x := int(r.buffer[4] - 33)
|
x := int(r.buffer[4] - 33)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@@ -619,10 +646,6 @@ func (r *LightRenderer) DoesAutoWrap() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) IsOptimized() bool {
|
|
||||||
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, borderStyle BorderStyle) Window {
|
||||||
w := &LightWindow{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
@@ -633,8 +656,10 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
tabstop: r.tabstop,
|
tabstop: r.tabstop,
|
||||||
|
fg: colDefault,
|
||||||
bg: colDefault}
|
bg: colDefault}
|
||||||
if r.theme != nil {
|
if r.theme != nil {
|
||||||
|
w.fg = r.theme.Fg
|
||||||
w.bg = r.theme.Bg
|
w.bg = r.theme.Bg
|
||||||
}
|
}
|
||||||
w.drawBorder()
|
w.drawBorder()
|
||||||
@@ -790,7 +815,7 @@ func (w *LightWindow) Print(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanse(str string) string {
|
func cleanse(str string) string {
|
||||||
return strings.Replace(str, "\x1b", "?", -1)
|
return strings.Replace(str, "\x1b", "", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||||
@@ -881,6 +906,9 @@ func (w *LightWindow) Fill(text string) FillReturn {
|
|||||||
|
|
||||||
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
|
if fg == colDefault {
|
||||||
|
fg = w.fg
|
||||||
|
}
|
||||||
if bg == colDefault {
|
if bg == colDefault {
|
||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,505 +0,0 @@
|
|||||||
// +build ncurses
|
|
||||||
// +build !windows
|
|
||||||
// +build !tcell
|
|
||||||
|
|
||||||
package tui
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <locale.h>
|
|
||||||
#cgo !static LDFLAGS: -lncurses
|
|
||||||
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
|
|
||||||
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
|
|
||||||
|
|
||||||
FILE* c_tty() {
|
|
||||||
return fopen("/dev/tty", "r");
|
|
||||||
}
|
|
||||||
|
|
||||||
SCREEN* c_newterm(FILE* tty) {
|
|
||||||
return newterm(NULL, stderr, tty);
|
|
||||||
}
|
|
||||||
|
|
||||||
int c_getcurx(WINDOW* win) {
|
|
||||||
return getcurx(win);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Attr C.uint
|
|
||||||
|
|
||||||
type CursesWindow struct {
|
|
||||||
impl *C.WINDOW
|
|
||||||
top int
|
|
||||||
left int
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Top() int {
|
|
||||||
return w.top
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Left() int {
|
|
||||||
return w.left
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Width() int {
|
|
||||||
return w.width
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Height() int {
|
|
||||||
return w.height
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Refresh() {
|
|
||||||
C.wnoutrefresh(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) FinishFill() {
|
|
||||||
// NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bold Attr = C.A_BOLD
|
|
||||||
Dim = C.A_DIM
|
|
||||||
Blink = C.A_BLINK
|
|
||||||
Reverse = C.A_REVERSE
|
|
||||||
Underline = C.A_UNDERLINE
|
|
||||||
)
|
|
||||||
|
|
||||||
var Italic Attr = C.A_VERTICAL << 1 // FIXME
|
|
||||||
|
|
||||||
const (
|
|
||||||
AttrRegular Attr = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_screen *C.SCREEN
|
|
||||||
_colorMap map[int]int16
|
|
||||||
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_colorMap = make(map[int]int16)
|
|
||||||
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
|
||||||
Italic = C.A_NORMAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
|
||||||
return a | b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
|
||||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
|
||||||
return Dark256
|
|
||||||
}
|
|
||||||
return Default16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
|
||||||
C.setlocale(C.LC_ALL, C.CString(""))
|
|
||||||
tty := C.c_tty()
|
|
||||||
if tty == nil {
|
|
||||||
errorExit("Failed to open /dev/tty")
|
|
||||||
}
|
|
||||||
_screen = C.c_newterm(tty)
|
|
||||||
if _screen == nil {
|
|
||||||
errorExit("Invalid $TERM: " + os.Getenv("TERM"))
|
|
||||||
}
|
|
||||||
C.set_term(_screen)
|
|
||||||
if r.mouse {
|
|
||||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
|
||||||
C.mouseinterval(0)
|
|
||||||
}
|
|
||||||
C.noecho()
|
|
||||||
C.raw() // stty dsusp undef
|
|
||||||
C.nonl()
|
|
||||||
C.keypad(C.stdscr, true)
|
|
||||||
|
|
||||||
delay := 50
|
|
||||||
delayEnv := os.Getenv("ESCDELAY")
|
|
||||||
if len(delayEnv) > 0 {
|
|
||||||
num, err := strconv.Atoi(delayEnv)
|
|
||||||
if err == nil && num >= 0 {
|
|
||||||
delay = num
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.set_escdelay(C.int(delay))
|
|
||||||
|
|
||||||
if r.theme != nil {
|
|
||||||
C.start_color()
|
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
|
||||||
initPairs(r.theme)
|
|
||||||
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
_colorFn = attrColored
|
|
||||||
} else {
|
|
||||||
initTheme(r.theme, nil, r.forceBlack)
|
|
||||||
_colorFn = attrMono
|
|
||||||
}
|
|
||||||
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
ch := C.getch()
|
|
||||||
if ch != C.ERR {
|
|
||||||
C.ungetch(ch)
|
|
||||||
}
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initPairs(theme *ColorTheme) {
|
|
||||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
|
||||||
for _, pair := range []ColorPair{
|
|
||||||
ColNormal,
|
|
||||||
ColPrompt,
|
|
||||||
ColMatch,
|
|
||||||
ColCurrent,
|
|
||||||
ColCurrentMatch,
|
|
||||||
ColSpinner,
|
|
||||||
ColInfo,
|
|
||||||
ColCursor,
|
|
||||||
ColSelected,
|
|
||||||
ColHeader,
|
|
||||||
ColBorder} {
|
|
||||||
C.init_pair(C.short(pair.index()), C.short(pair.Fg()), C.short(pair.Bg()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Pause(bool) {
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume(bool) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
|
||||||
C.endwin()
|
|
||||||
C.delscreen(_screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
|
||||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
|
||||||
if r.theme != nil {
|
|
||||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
}
|
|
||||||
// FIXME Does not implement BorderHorizontal
|
|
||||||
if borderStyle != BorderNone {
|
|
||||||
pair, attr := _colorFn(ColBorder, 0)
|
|
||||||
C.wcolor_set(win, pair, nil)
|
|
||||||
C.wattron(win, attr)
|
|
||||||
C.box(win, 0, 0)
|
|
||||||
C.wattroff(win, attr)
|
|
||||||
C.wcolor_set(win, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CursesWindow{
|
|
||||||
impl: win,
|
|
||||||
top: top,
|
|
||||||
left: left,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrColored(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return C.short(color.index()), C.int(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrMono(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return 0, C.int(attrFor(color, a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxX() int {
|
|
||||||
return int(C.COLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxY() int {
|
|
||||||
return int(C.LINES)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Close() {
|
|
||||||
C.delwin(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Enclose(y int, x int) bool {
|
|
||||||
return bool(C.wenclose(w.impl, C.int(y), C.int(x)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Move(y int, x int) {
|
|
||||||
C.wmove(w.impl, C.int(y), C.int(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) MoveAndClear(y int, x int) {
|
|
||||||
w.Move(y, x)
|
|
||||||
C.wclrtoeol(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Print(text string) {
|
|
||||||
C.waddstr(w.impl, C.CString(strings.Map(func(r rune) rune {
|
|
||||||
if r < 32 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, text)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CPrint(color ColorPair, attr Attr, text string) {
|
|
||||||
p, a := _colorFn(color, attr)
|
|
||||||
C.wcolor_set(w.impl, p, nil)
|
|
||||||
C.wattron(w.impl, a)
|
|
||||||
w.Print(text)
|
|
||||||
C.wattroff(w.impl, a)
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Clear() {
|
|
||||||
C.clear()
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Refresh() {
|
|
||||||
C.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Erase() {
|
|
||||||
C.werase(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) X() int {
|
|
||||||
return int(C.c_getcurx(w.impl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Fill(str string) FillReturn {
|
|
||||||
if C.waddstr(w.impl, C.CString(str)) == C.OK {
|
|
||||||
return FillContinue
|
|
||||||
}
|
|
||||||
return FillSuspend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CFill(fg Color, bg Color, attr Attr, str string) FillReturn {
|
|
||||||
index := ColorPair{fg, bg, -1}.index()
|
|
||||||
C.wcolor_set(w.impl, C.short(index), nil)
|
|
||||||
C.wattron(w.impl, C.int(attr))
|
|
||||||
ret := w.Fill(str)
|
|
||||||
C.wattroff(w.impl, C.int(attr))
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|
||||||
for _, w := range windows {
|
|
||||||
w.Refresh()
|
|
||||||
}
|
|
||||||
C.doupdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) index() int16 {
|
|
||||||
if p.id >= 0 {
|
|
||||||
return p.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// ncurses does not support 24-bit colors
|
|
||||||
if p.is24() {
|
|
||||||
return ColDefault.index()
|
|
||||||
}
|
|
||||||
|
|
||||||
key := p.key()
|
|
||||||
if found, prs := _colorMap[key]; prs {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
id := int16(len(_colorMap)) + ColUser.id
|
|
||||||
C.init_pair(C.short(id), C.short(p.Fg()), C.short(p.Bg()))
|
|
||||||
_colorMap[key] = id
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func consume(expects ...rune) bool {
|
|
||||||
for _, r := range expects {
|
|
||||||
if int(C.getch()) != int(r) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func escSequence() Event {
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
defer func() {
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}()
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
return Event{ESC, 0, nil}
|
|
||||||
case CtrlM:
|
|
||||||
return Event{CtrlAltM, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case 127, C.KEY_BACKSPACE:
|
|
||||||
return Event{AltBS, 0, nil}
|
|
||||||
case '[':
|
|
||||||
// Bracketed paste mode (printf "\e[?2004h")
|
|
||||||
// \e[200~ TEXT \e[201~
|
|
||||||
if consume('2', '0', '0', '~') {
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c >= 'a' && c <= 'z' {
|
|
||||||
return Event{AltA + int(c) - 'a', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c >= '0' && c <= '9' {
|
|
||||||
return Event{Alt0 + int(c) - '0', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't care. Ignore the rest.
|
|
||||||
for ; c != C.ERR; c = C.getch() {
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
// Unexpected error from blocking read
|
|
||||||
r.Close()
|
|
||||||
errorExit("Failed to read /dev/tty")
|
|
||||||
case C.KEY_UP:
|
|
||||||
return Event{Up, 0, nil}
|
|
||||||
case C.KEY_DOWN:
|
|
||||||
return Event{Down, 0, nil}
|
|
||||||
case C.KEY_LEFT:
|
|
||||||
return Event{Left, 0, nil}
|
|
||||||
case C.KEY_RIGHT:
|
|
||||||
return Event{Right, 0, nil}
|
|
||||||
case C.KEY_HOME:
|
|
||||||
return Event{Home, 0, nil}
|
|
||||||
case C.KEY_END:
|
|
||||||
return Event{End, 0, nil}
|
|
||||||
case C.KEY_BACKSPACE:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case C.KEY_F0 + 1:
|
|
||||||
return Event{F1, 0, nil}
|
|
||||||
case C.KEY_F0 + 2:
|
|
||||||
return Event{F2, 0, nil}
|
|
||||||
case C.KEY_F0 + 3:
|
|
||||||
return Event{F3, 0, nil}
|
|
||||||
case C.KEY_F0 + 4:
|
|
||||||
return Event{F4, 0, nil}
|
|
||||||
case C.KEY_F0 + 5:
|
|
||||||
return Event{F5, 0, nil}
|
|
||||||
case C.KEY_F0 + 6:
|
|
||||||
return Event{F6, 0, nil}
|
|
||||||
case C.KEY_F0 + 7:
|
|
||||||
return Event{F7, 0, nil}
|
|
||||||
case C.KEY_F0 + 8:
|
|
||||||
return Event{F8, 0, nil}
|
|
||||||
case C.KEY_F0 + 9:
|
|
||||||
return Event{F9, 0, nil}
|
|
||||||
case C.KEY_F0 + 10:
|
|
||||||
return Event{F10, 0, nil}
|
|
||||||
case C.KEY_F0 + 11:
|
|
||||||
return Event{F11, 0, nil}
|
|
||||||
case C.KEY_F0 + 12:
|
|
||||||
return Event{F12, 0, nil}
|
|
||||||
case C.KEY_DC:
|
|
||||||
return Event{Del, 0, nil}
|
|
||||||
case C.KEY_PPAGE:
|
|
||||||
return Event{PgUp, 0, nil}
|
|
||||||
case C.KEY_NPAGE:
|
|
||||||
return Event{PgDn, 0, nil}
|
|
||||||
case C.KEY_BTAB:
|
|
||||||
return Event{BTab, 0, nil}
|
|
||||||
case C.KEY_ENTER:
|
|
||||||
return Event{CtrlM, 0, nil}
|
|
||||||
case C.KEY_SLEFT:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case C.KEY_SRIGHT:
|
|
||||||
return Event{SRight, 0, nil}
|
|
||||||
case C.KEY_MOUSE:
|
|
||||||
var me C.MEVENT
|
|
||||||
if C.getmouse(&me) != C.ERR {
|
|
||||||
mod := ((me.bstate & C.BUTTON_SHIFT) | (me.bstate & C.BUTTON_CTRL) | (me.bstate & C.BUTTON_ALT)) > 0
|
|
||||||
x := int(me.x)
|
|
||||||
y := int(me.y)
|
|
||||||
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
|
||||||
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
|
||||||
now := time.Now()
|
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
r.clickY = append(r.clickY, y)
|
|
||||||
} else {
|
|
||||||
r.clickY = []int{y}
|
|
||||||
r.prevDownTime = now
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
|
||||||
double := false
|
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
double = true
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
|
||||||
} else if (me.bstate&0x8000000) > 0 || (me.bstate&0x80) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON4_PRESSED) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 1, false, false, mod}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
case C.KEY_RESIZE:
|
|
||||||
return Event{Resize, 0, nil}
|
|
||||||
case ESC:
|
|
||||||
return escSequence()
|
|
||||||
case 127:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case 0:
|
|
||||||
return Event{CtrlSpace, 0, nil}
|
|
||||||
}
|
|
||||||
// CTRL-A ~ CTRL-Z
|
|
||||||
if c >= CtrlA && c <= CtrlZ {
|
|
||||||
return Event{int(c), 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-byte character
|
|
||||||
buffer := []byte{byte(c)}
|
|
||||||
for {
|
|
||||||
r, _ := utf8.DecodeRune(buffer)
|
|
||||||
if r != utf8.RuneError {
|
|
||||||
return Event{Rune, r, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := C.getch()
|
|
||||||
if c == C.ERR {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c >= C.KEY_CODE_YES {
|
|
||||||
C.ungetch(c)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer = append(buffer, byte(c))
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
@@ -172,10 +172,6 @@ func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Clear() {
|
func (r *FullscreenRenderer) Clear() {
|
||||||
_screen.Sync()
|
_screen.Sync()
|
||||||
_screen.Clear()
|
_screen.Clear()
|
||||||
@@ -197,19 +193,22 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
button := ev.Buttons()
|
button := ev.Buttons()
|
||||||
mod := ev.Modifiers() != 0
|
mod := ev.Modifiers() != 0
|
||||||
if button&tcell.WheelDown != 0 {
|
if button&tcell.WheelDown != 0 {
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
||||||
} else if button&tcell.WheelUp != 0 {
|
} else if button&tcell.WheelUp != 0 {
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
||||||
} else if runtime.GOOS != "windows" {
|
} else if runtime.GOOS != "windows" {
|
||||||
// double and single taps on Windows don't quite work due to
|
// double and single taps on Windows don't quite work due to
|
||||||
// the console acting on the events and not allowing us
|
// the console acting on the events and not allowing us
|
||||||
// to consume them.
|
// to consume them.
|
||||||
|
|
||||||
down := button&tcell.Button1 != 0 // left
|
left := button&tcell.Button1 != 0
|
||||||
|
down := left || button&tcell.Button3 != 0
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
if !left {
|
||||||
|
r.clickY = []int{}
|
||||||
|
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
r.clickY = append(r.clickY, x)
|
r.clickY = append(r.clickY, x)
|
||||||
} else {
|
} else {
|
||||||
r.clickY = []int{x}
|
r.clickY = []int{x}
|
||||||
@@ -222,7 +221,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process keyboard:
|
// process keyboard:
|
||||||
@@ -296,12 +295,24 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
|
if alt {
|
||||||
|
return Event{AltUp, 0, nil}
|
||||||
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case tcell.KeyDown:
|
case tcell.KeyDown:
|
||||||
|
if alt {
|
||||||
|
return Event{AltDown, 0, nil}
|
||||||
|
}
|
||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case tcell.KeyLeft:
|
case tcell.KeyLeft:
|
||||||
|
if alt {
|
||||||
|
return Event{AltLeft, 0, nil}
|
||||||
|
}
|
||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case tcell.KeyRight:
|
case tcell.KeyRight:
|
||||||
|
if alt {
|
||||||
|
return Event{AltRight, 0, nil}
|
||||||
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
@@ -409,14 +420,13 @@ func (w *TcellWindow) Close() {
|
|||||||
func fill(x, y, w, h int, r rune) {
|
func fill(x, y, w, h int, 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, ColDefault.style())
|
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
// TODO
|
fill(w.left-1, w.top, w.width+1, w.height, ' ')
|
||||||
fill(w.left, w.top, w.width, w.height, ' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
@@ -433,13 +443,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, ColDefault.style())
|
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style())
|
||||||
}
|
}
|
||||||
w.lastX = x
|
w.lastX = x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Print(text string) {
|
func (w *TcellWindow) Print(text string) {
|
||||||
w.printString(text, ColDefault, 0)
|
w.printString(text, ColNormal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||||
@@ -452,7 +462,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 = ColDefault.style().
|
style = ColNormal.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)
|
||||||
}
|
}
|
||||||
@@ -503,7 +513,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 = ColDefault.style()
|
style = ColNormal.style()
|
||||||
}
|
}
|
||||||
style = style.
|
style = style.
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
@@ -543,11 +553,17 @@ 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, ColDefault, 0)
|
return w.fillString(str, ColNormal, 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 {
|
||||||
return w.fillString(str, ColorPair{fg, bg, -1}, a)
|
if fg == colDefault {
|
||||||
|
fg = ColNormal.Fg()
|
||||||
|
}
|
||||||
|
if bg == colDefault {
|
||||||
|
bg = ColNormal.Bg()
|
||||||
|
}
|
||||||
|
return w.fillString(str, NewColorPair(fg, bg), a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) drawBorder(around bool) {
|
func (w *TcellWindow) drawBorder(around bool) {
|
||||||
@@ -560,7 +576,7 @@ func (w *TcellWindow) drawBorder(around bool) {
|
|||||||
if w.color {
|
if w.color {
|
||||||
style = ColBorder.style()
|
style = ColBorder.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = ColNormal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
|
|||||||
31
src/tui/ttyname_unix.go
Normal file
31
src/tui/ttyname_unix.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||||
|
|
||||||
|
func ttyname() string {
|
||||||
|
var stderr syscall.Stat_t
|
||||||
|
if syscall.Fstat(2, &stderr) != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range devPrefixes {
|
||||||
|
files, err := ioutil.ReadDir(prefix)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
|
return prefix + file.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
7
src/tui/ttyname_windows.go
Normal file
7
src/tui/ttyname_windows.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
func ttyname() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -44,6 +44,8 @@ const (
|
|||||||
Resize
|
Resize
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
|
LeftClick
|
||||||
|
RightClick
|
||||||
|
|
||||||
BTab
|
BTab
|
||||||
BSpace
|
BSpace
|
||||||
@@ -59,6 +61,8 @@ const (
|
|||||||
Home
|
Home
|
||||||
End
|
End
|
||||||
|
|
||||||
|
SUp
|
||||||
|
SDown
|
||||||
SLeft
|
SLeft
|
||||||
SRight
|
SRight
|
||||||
|
|
||||||
@@ -81,6 +85,11 @@ const (
|
|||||||
AltSlash
|
AltSlash
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
|
AltUp
|
||||||
|
AltDown
|
||||||
|
AltLeft
|
||||||
|
AltRight
|
||||||
|
|
||||||
Alt0
|
Alt0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,7 +142,7 @@ const (
|
|||||||
type ColorPair struct {
|
type ColorPair struct {
|
||||||
fg Color
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
id int16
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
func HexToColor(rrggbb string) Color {
|
func HexToColor(rrggbb string) Color {
|
||||||
@@ -155,12 +164,8 @@ func (p ColorPair) Bg() Color {
|
|||||||
return p.bg
|
return p.bg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) key() int {
|
|
||||||
return (int(p.Fg()) << 8) + int(p.Bg())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) is24() bool {
|
func (p ColorPair) is24() bool {
|
||||||
return p.Fg().is24() || p.Bg().is24()
|
return p.fg.is24() || p.bg.is24()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
@@ -179,10 +184,6 @@ type ColorTheme struct {
|
|||||||
Border Color
|
Border Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ColorTheme) HasBg() bool {
|
|
||||||
return t.Bg != colDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type int
|
Type int
|
||||||
Char rune
|
Char rune
|
||||||
@@ -193,6 +194,7 @@ type MouseEvent struct {
|
|||||||
Y int
|
Y int
|
||||||
X int
|
X int
|
||||||
S int
|
S int
|
||||||
|
Left bool
|
||||||
Down bool
|
Down bool
|
||||||
Double bool
|
Double bool
|
||||||
Mod bool
|
Mod bool
|
||||||
@@ -220,7 +222,6 @@ type Renderer interface {
|
|||||||
MaxX() int
|
MaxX() int
|
||||||
MaxY() int
|
MaxY() int
|
||||||
DoesAutoWrap() bool
|
DoesAutoWrap() bool
|
||||||
IsOptimized() bool
|
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
||||||
}
|
}
|
||||||
@@ -271,7 +272,6 @@ var (
|
|||||||
Dark256 *ColorTheme
|
Dark256 *ColorTheme
|
||||||
Light256 *ColorTheme
|
Light256 *ColorTheme
|
||||||
|
|
||||||
ColDefault ColorPair
|
|
||||||
ColNormal ColorPair
|
ColNormal ColorPair
|
||||||
ColPrompt ColorPair
|
ColPrompt ColorPair
|
||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
@@ -283,7 +283,6 @@ var (
|
|||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
ColUser ColorPair
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -387,33 +386,36 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initPalette(theme *ColorTheme) {
|
func initPalette(theme *ColorTheme) {
|
||||||
ColDefault = ColorPair{colDefault, colDefault, 0}
|
idx := 0
|
||||||
if theme != nil {
|
pair := func(fg, bg Color) ColorPair {
|
||||||
ColNormal = ColorPair{theme.Fg, theme.Bg, 1}
|
idx++
|
||||||
ColPrompt = ColorPair{theme.Prompt, theme.Bg, 2}
|
return ColorPair{fg, bg, idx}
|
||||||
ColMatch = ColorPair{theme.Match, theme.Bg, 3}
|
}
|
||||||
ColCurrent = ColorPair{theme.Current, theme.DarkBg, 4}
|
if theme != nil {
|
||||||
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg, 5}
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
ColSpinner = ColorPair{theme.Spinner, theme.Bg, 6}
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColInfo = ColorPair{theme.Info, theme.Bg, 7}
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
ColCursor = ColorPair{theme.Cursor, theme.DarkBg, 8}
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColSelected = ColorPair{theme.Selected, theme.DarkBg, 9}
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
ColHeader = ColorPair{theme.Header, theme.Bg, 10}
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColBorder = ColorPair{theme.Border, theme.Bg, 11}
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
} else {
|
ColCursor = pair(theme.Cursor, theme.DarkBg)
|
||||||
ColNormal = ColorPair{colDefault, colDefault, 1}
|
ColSelected = pair(theme.Selected, theme.DarkBg)
|
||||||
ColPrompt = ColorPair{colDefault, colDefault, 2}
|
ColHeader = pair(theme.Header, theme.Bg)
|
||||||
ColMatch = ColorPair{colDefault, colDefault, 3}
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
ColCurrent = ColorPair{colDefault, colDefault, 4}
|
} else {
|
||||||
ColCurrentMatch = ColorPair{colDefault, colDefault, 5}
|
ColNormal = pair(colDefault, colDefault)
|
||||||
ColSpinner = ColorPair{colDefault, colDefault, 6}
|
ColPrompt = pair(colDefault, colDefault)
|
||||||
ColInfo = ColorPair{colDefault, colDefault, 7}
|
ColMatch = pair(colDefault, colDefault)
|
||||||
ColCursor = ColorPair{colDefault, colDefault, 8}
|
ColCurrent = pair(colDefault, colDefault)
|
||||||
ColSelected = ColorPair{colDefault, colDefault, 9}
|
ColCurrentMatch = pair(colDefault, colDefault)
|
||||||
ColHeader = ColorPair{colDefault, colDefault, 10}
|
ColSpinner = pair(colDefault, colDefault)
|
||||||
ColBorder = ColorPair{colDefault, colDefault, 11}
|
ColInfo = pair(colDefault, colDefault)
|
||||||
|
ColCursor = pair(colDefault, colDefault)
|
||||||
|
ColSelected = pair(colDefault, colDefault)
|
||||||
|
ColHeader = pair(colDefault, colDefault)
|
||||||
|
ColBorder = pair(colDefault, colDefault)
|
||||||
}
|
}
|
||||||
ColUser = ColorPair{colDefault, colDefault, 12}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrFor(color ColorPair, attr Attr) Attr {
|
func attrFor(color ColorPair, attr Attr) Attr {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -94,6 +95,11 @@ func (chars *Chars) Length() int {
|
|||||||
return len(chars.slice)
|
return len(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Chars object.
|
||||||
|
func (chars *Chars) String() string {
|
||||||
|
return fmt.Sprintf("Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)
|
||||||
|
}
|
||||||
|
|
||||||
// TrimLength returns the length after trimming leading and trailing whitespaces
|
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||||
func (chars *Chars) TrimLength() uint16 {
|
func (chars *Chars) TrimLength() uint16 {
|
||||||
if chars.trimLengthKnown {
|
if chars.trimLengthKnown {
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ func RuneWidth(r rune, prefixWidth int, tabstop int) int {
|
|||||||
return tabstop - prefixWidth%tabstop
|
return tabstop - prefixWidth%tabstop
|
||||||
} else if w, found := _runeWidths[r]; found {
|
} else if w, found := _runeWidths[r]; found {
|
||||||
return w
|
return w
|
||||||
} else {
|
} else if r == '\n' || r == '\r' {
|
||||||
w := Max(runewidth.RuneWidth(r), 1)
|
return 1
|
||||||
|
}
|
||||||
|
w := runewidth.RuneWidth(r)
|
||||||
_runeWidths[r] = w
|
_runeWidths[r] = w
|
||||||
return w
|
return w
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ func ExecCommand(command string) *exec.Cmd {
|
|||||||
if len(shell) == 0 {
|
if len(shell) == 0 {
|
||||||
shell = "sh"
|
shell = "sh"
|
||||||
}
|
}
|
||||||
|
return ExecCommandWith(shell, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommandWith executes the given command with the specified shell
|
||||||
|
func ExecCommandWith(shell string, command string) *exec.Cmd {
|
||||||
return exec.Command(shell, "-c", command)
|
return exec.Command(shell, "-c", command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,27 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/mattn/go-shellwords"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with cmd
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
args, _ := shellwords.Parse(command)
|
return ExecCommandWith("cmd", command)
|
||||||
allArgs := make([]string, len(args)+1)
|
}
|
||||||
allArgs[0] = "/c"
|
|
||||||
copy(allArgs[1:], args)
|
// ExecCommandWith executes the given command with cmd. _shell parameter is
|
||||||
return exec.Command("cmd", allArgs...)
|
// ignored on Windows.
|
||||||
|
func ExecCommandWith(_shell string, command string) *exec.Cmd {
|
||||||
|
cmd := exec.Command("cmd")
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: false,
|
||||||
|
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
||||||
|
CreationFlags: 0,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWindows returns true on Windows
|
// IsWindows returns true on Windows
|
||||||
|
|||||||
164
test/test_go.rb
Normal file → Executable file
164
test/test_go.rb
Normal file → Executable file
@@ -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?('ERROR') }
|
tmux.until { |lines| lines[-2].include?('FZF_DEFAULT_COMMAND failed') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -769,6 +769,15 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal %w[print-my-query], readonce.split($INPUT_RECORD_SEPARATOR)
|
assert_equal %w[print-my-query], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_bind_replace_query
|
||||||
|
tmux.send_keys "seq 1 1000 | #{fzf '--print-query --bind=ctrl-j:replace-query'}", :Enter
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { |lines| lines[-2].end_with? '272/1000' }
|
||||||
|
tmux.send_keys 'C-k', 'C-j'
|
||||||
|
tmux.until { |lines| lines[-2].end_with? '29/1000' }
|
||||||
|
tmux.until { |lines| lines[-1].end_with? '> 10' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_long_line
|
def test_long_line
|
||||||
data = '.' * 256 * 1024
|
data = '.' * 256 * 1024
|
||||||
File.open(tempname, 'w') do |f|
|
File.open(tempname, 'w') do |f|
|
||||||
@@ -1051,6 +1060,21 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '50', readonce.chomp
|
assert_equal '50', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_header_lines_reverse_list
|
||||||
|
tmux.send_keys "seq 100 | #{fzf '--header-lines=10 -q 5 --layout=reverse-list'}", :Enter
|
||||||
|
2.times do
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[0] == '> 50' &&
|
||||||
|
lines[-4] == ' 2' &&
|
||||||
|
lines[-3] == ' 1' &&
|
||||||
|
lines[-2].include?('/90')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal '50', readonce.chomp
|
||||||
|
end
|
||||||
|
|
||||||
def test_header_lines_overflow
|
def test_header_lines_overflow
|
||||||
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
|
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
@@ -1078,7 +1102,8 @@ class TestGoFZF < TestBase
|
|||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('100/100') &&
|
lines[-2].include?('100/100') &&
|
||||||
lines[-7..-3].map(&:strip) == header
|
lines[-7..-3].map(&:strip) == header &&
|
||||||
|
lines[-8] == '> 1'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1087,7 +1112,18 @@ class TestGoFZF < TestBase
|
|||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[1].include?('100/100') &&
|
lines[1].include?('100/100') &&
|
||||||
lines[2..6].map(&:strip) == header
|
lines[2..6].map(&:strip) == header &&
|
||||||
|
lines[7] == '> 1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_reverse_list
|
||||||
|
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
|
||||||
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[-2].include?('100/100') &&
|
||||||
|
lines[-7..-3].map(&:strip) == header &&
|
||||||
|
lines[0] == '> 1'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1111,6 +1147,16 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_header_and_header_lines_reverse_list
|
||||||
|
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
||||||
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[-2].include?('90/90') &&
|
||||||
|
lines[-7...-2].map(&:strip) == header &&
|
||||||
|
lines[-17...-7].map(&:strip) == (1..10).map(&:to_s).reverse
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_cancel
|
def test_cancel
|
||||||
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
|
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include?('10/10') }
|
tmux.until { |lines| lines[-2].include?('10/10') }
|
||||||
@@ -1136,6 +1182,12 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_margin_reverse_list
|
||||||
|
tmux.send_keys "yes | head -1000 | #{fzf '--margin 5,3 --layout=reverse-list'}", :Enter
|
||||||
|
tmux.until { |lines| lines[4] == '' && lines[5] == ' > y' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
|
||||||
def test_tabstop
|
def test_tabstop
|
||||||
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
|
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
|
||||||
{
|
{
|
||||||
@@ -1319,12 +1371,12 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_hidden
|
def test_preview_hidden
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$LINES-\\$COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||||
tmux.until { |lines| lines[-1] == '>' }
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
tmux.send_keys '?'
|
tmux.send_keys '?'
|
||||||
tmux.until { |lines| lines[-2].include?(' {1-1}') }
|
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||||
tmux.send_keys '555'
|
tmux.send_keys '555'
|
||||||
tmux.until { |lines| lines[-2].include?(' {555-555}') }
|
tmux.until { |lines| lines[-2] =~ / {555-555-1-[0-9]+}/ }
|
||||||
tmux.send_keys '?'
|
tmux.send_keys '?'
|
||||||
tmux.until { |lines| lines[-1] == '> 555' }
|
tmux.until { |lines| lines[-1] == '> 555' }
|
||||||
end
|
end
|
||||||
@@ -1346,6 +1398,35 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
|
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_flags
|
||||||
|
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||||
|
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter
|
||||||
|
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') }
|
||||||
|
tmux.send_keys '123'
|
||||||
|
tmux.until { |lines| lines[1].include?('{////123}') }
|
||||||
|
tmux.send_keys 'C-u', '1'
|
||||||
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
|
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1}') }
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12}') }
|
||||||
|
tmux.send_keys '3'
|
||||||
|
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123}') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_q_no_match
|
||||||
|
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 0 }
|
||||||
|
tmux.until { |lines| !lines[1].include?('foo') }
|
||||||
|
tmux.send_keys 'bar'
|
||||||
|
tmux.until { |lines| lines[1].include?('foo bar') }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| !lines[1].include?('foo') }
|
||||||
|
end
|
||||||
|
|
||||||
def test_no_clear
|
def test_no_clear
|
||||||
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
||||||
prompt = '> < 10/10'
|
prompt = '> < 10/10'
|
||||||
@@ -1369,6 +1450,42 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_accept_non_empty
|
||||||
|
tmux.send_keys %(seq 1000 | #{fzf '--print-query --bind enter:accept-non-empty'}), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.send_keys 'foo'
|
||||||
|
tmux.until { |lines| lines[-2].include? '0/1000' }
|
||||||
|
# fzf doesn't exit since there's no selection
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| lines[-2].include? '0/1000' }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| lines[-2].include? '1000/1000' }
|
||||||
|
tmux.send_keys '999'
|
||||||
|
tmux.until { |lines| lines[-2].include? '1/1000' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal %w[999 999], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_non_empty_with_multi_selection
|
||||||
|
tmux.send_keys %(seq 1000 | #{fzf '-m --print-query --bind enter:accept-non-empty'}), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-2].include? '1000/1000 (1)' }
|
||||||
|
tmux.send_keys 'foo'
|
||||||
|
tmux.until { |lines| lines[-2].include? '0/1000' }
|
||||||
|
# fzf will exit in this case even though there's no match for the current query
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal %w[foo 1], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_non_empty_with_empty_list
|
||||||
|
tmux.send_keys %(: | #{fzf '-q foo --print-query --bind enter:accept-non-empty'}), :Enter
|
||||||
|
tmux.until { |lines| lines[-2].strip == '0/0' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
# fzf will exit anyway since input list is empty
|
||||||
|
assert_equal %w[foo], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_update_on_select
|
def test_preview_update_on_select
|
||||||
tmux.send_keys(%(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
tmux.send_keys(%(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
||||||
:Enter)
|
:Enter)
|
||||||
@@ -1469,7 +1586,7 @@ module TestShell
|
|||||||
lines = retries do
|
lines = retries do
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :Escape, :c
|
tmux.send_keys :Escape, :c
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
end
|
end
|
||||||
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
|
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1506,7 +1623,7 @@ module TestShell
|
|||||||
retries do
|
retries do
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
end
|
end
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.send_keys '3d'
|
tmux.send_keys '3d'
|
||||||
@@ -1538,7 +1655,7 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys ' !d'
|
tmux.send_keys ' !d'
|
||||||
tmux.until { |lines| lines.match_count == 2 }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :Tab, :Tab
|
||||||
@@ -1552,7 +1669,7 @@ module CompletionTest
|
|||||||
# ~USERNAME**<TAB>
|
# ~USERNAME**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
|
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys "'.fzf-home"
|
tmux.send_keys "'.fzf-home"
|
||||||
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1570,7 +1687,7 @@ module CompletionTest
|
|||||||
# /tmp/fzf\ test**<TAB>
|
# /tmp/fzf\ test**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys 'foobar$'
|
tmux.send_keys 'foobar$'
|
||||||
tmux.until { |lines| lines.match_count == 1 }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1590,7 +1707,7 @@ module CompletionTest
|
|||||||
|
|
||||||
def test_file_completion_root
|
def test_file_completion_root
|
||||||
tmux.send_keys 'ls /**', :Tab
|
tmux.send_keys 'ls /**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1601,7 +1718,7 @@ module CompletionTest
|
|||||||
FileUtils.touch '/tmp/fzf-test/d55/xxx'
|
FileUtils.touch '/tmp/fzf-test/d55/xxx'
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys :Tab, :Tab # Tab does not work here
|
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||||
tmux.send_keys 55
|
tmux.send_keys 55
|
||||||
tmux.until { |lines| lines.match_count == 1 }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
@@ -1630,7 +1747,7 @@ module CompletionTest
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
tmux.send_keys 'kill ', :Tab
|
tmux.send_keys 'kill ', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys 'sleep12345'
|
tmux.send_keys 'sleep12345'
|
||||||
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1649,7 +1766,7 @@ module CompletionTest
|
|||||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'ls /tmp/**', :Tab
|
tmux.send_keys 'ls /tmp/**', :Tab
|
||||||
tmux.until { |lines| lines.item_count == 11 }
|
tmux.until { |lines| lines.match_count == 11 }
|
||||||
tmux.send_keys :Tab, :Tab, :Tab
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
tmux.until { |lines| lines.select_count == 3 }
|
tmux.until { |lines| lines.select_count == 3 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1716,6 +1833,23 @@ class TestBash < TestBase
|
|||||||
super
|
super
|
||||||
@tmux = Tmux.new :bash
|
@tmux = Tmux.new :bash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_dynamic_completion_loader
|
||||||
|
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
||||||
|
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||||
|
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
|
||||||
|
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||||
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'fake /tmp/foo'
|
||||||
|
tmux.send_keys :Tab , 'C-u'
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||||
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestZsh < TestBase
|
class TestZsh < TestBase
|
||||||
|
|||||||
72
uninstall
72
uninstall
@@ -1,12 +1,45 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
confirm() {
|
xdg=0
|
||||||
while [ 1 ]; do
|
prefix='~/.fzf'
|
||||||
read -p "$1" -n 1 -r
|
prefix_expand=~/.fzf
|
||||||
echo
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
if [[ "$REPLY" =~ ^[Yy] ]]; then
|
|
||||||
|
help() {
|
||||||
|
cat << EOF
|
||||||
|
usage: $0 [OPTIONS]
|
||||||
|
|
||||||
|
--help Show this message
|
||||||
|
--xdg Remove files generated under \$XDG_CONFIG_HOME/fzf
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in "$@"; do
|
||||||
|
case $opt in
|
||||||
|
--help)
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--xdg)
|
||||||
|
xdg=1
|
||||||
|
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||||
|
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown option: $opt"
|
||||||
|
help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ask() {
|
||||||
|
while true; do
|
||||||
|
read -p "$1 ([y]/n) " -r
|
||||||
|
REPLY=${REPLY:-"y"}
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$REPLY" =~ ^[Nn] ]]; then
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -40,7 +73,7 @@ remove_line() {
|
|||||||
content=$(sed 's/^[0-9]*://' <<< "$line")
|
content=$(sed 's/^[0-9]*://' <<< "$line")
|
||||||
match=1
|
match=1
|
||||||
echo " - Line #$line_no: $content"
|
echo " - Line #$line_no: $content"
|
||||||
[ "$content" = "$1" ] || confirm " - Remove (y/n) ? "
|
[ "$content" = "$1" ] || ask " - Remove?"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
||||||
mv "$src.bak" "$src" || break
|
mv "$src.bak" "$src" || break
|
||||||
@@ -55,25 +88,30 @@ remove_line() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for shell in bash zsh; do
|
for shell in bash zsh; do
|
||||||
remove ~/.fzf.${shell}
|
shell_config=${prefix_expand}.${shell}
|
||||||
|
remove "${shell_config}"
|
||||||
remove_line ~/.${shell}rc \
|
remove_line ~/.${shell}rc \
|
||||||
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
|
"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" \
|
||||||
"source ~/.fzf.${shell}"
|
"source ${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ -f "$bind_file" ]; then
|
if [ -f "$bind_file" ]; then
|
||||||
remove_line "$bind_file" "fzf_key_bindings"
|
remove_line "$bind_file" "fzf_key_bindings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d ~/.config/fish/functions ]; then
|
if [ -d "${fish_dir}/functions" ]; then
|
||||||
remove ~/.config/fish/functions/fzf.fish
|
remove "${fish_dir}/functions/fzf.fish"
|
||||||
remove ~/.config/fish/functions/fzf_key_bindings.fish
|
remove "${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
|
|
||||||
if [ "$(ls -A ~/.config/fish/functions)" ]; then
|
if [ "$(ls -A "${fish_dir}/functions")" ]; then
|
||||||
echo "Can't delete non-empty directory: \"~/.config/fish/functions\""
|
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
|
||||||
else
|
else
|
||||||
rmdir ~/.config/fish/functions
|
rmdir "${fish_dir}/functions"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
config_dir=$(dirname "$prefix_expand")
|
||||||
|
if [[ "$xdg" = 1 ]] && [[ "$config_dir" = */fzf ]] && [[ -d "$config_dir" ]]; then
|
||||||
|
rmdir "$config_dir"
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user