mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-28 02:31:59 -07:00
Compare commits
140 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0818dbc36a | ||
|
347c4b2625 | ||
|
34f0d4d0c4 | ||
|
cbedb57511 | ||
|
9ef825d2fd | ||
|
85ae745910 | ||
|
7411da8d5a | ||
|
3f75a8369f | ||
|
4cd621e877 | ||
|
6e3a2fe0bf | ||
|
8b0e1f941a | ||
|
c7c5e7670a | ||
|
f6c621ef1b | ||
|
faf32d451d | ||
|
252fd7ecb1 | ||
|
7fa89dddb4 | ||
|
fefdb8c84e | ||
|
a6cc05936e | ||
|
b209843545 | ||
|
19759ed36e | ||
|
1a7ae8e7b9 | ||
|
da1f645670 | ||
|
3a2015ee26 | ||
|
c440418ce6 | ||
|
3d37a5ba1d | ||
|
15f4cfb6d9 | ||
|
be36de2482 | ||
|
391237f7df | ||
|
977e5effd9 | ||
|
8b36a4cb19 | ||
|
c8cd94a772 | ||
|
764316a53d | ||
|
2048fd4042 | ||
|
f84b3de24b | ||
|
6a1f3ec08b | ||
|
2e353aee96 | ||
|
8edfd14a37 | ||
|
1a191ec6f7 | ||
|
e7171e94b4 | ||
|
398d937419 | ||
|
34fe5ab143 | ||
|
1b08f43f82 | ||
|
b24a2e2fdc | ||
|
4c4c6e626e | ||
|
7310370a31 | ||
|
8ae94f0059 | ||
|
8fccf20892 | ||
|
5a874ae241 | ||
|
f4e1ed25f2 | ||
|
cbfbb49ab4 | ||
|
489b16efce | ||
|
b82c1693c0 | ||
|
019bfc4e35 | ||
|
dfda5c054a | ||
|
f657169616 | ||
|
4c06da8b70 | ||
|
9fe2393a00 | ||
|
e2e8d94b14 | ||
|
4f9a7f8c87 | ||
|
bb0502ff44 | ||
|
c256442245 | ||
|
1137404190 | ||
|
d57c6d0284 | ||
|
76bbf57b3d | ||
|
806a47a7cc | ||
|
29851c18aa | ||
|
dea950c2c8 | ||
|
a367dfb22e | ||
|
9fe1a7b373 | ||
|
8e2d21c548 | ||
|
bedf1cd357 | ||
|
13f180a70c | ||
|
6654239c94 | ||
|
1b61e5e9e9 | ||
|
43b3b907f8 | ||
|
fcd896508b | ||
|
f55c990e86 | ||
|
d110372f99 | ||
|
c862af09f2 | ||
|
1cfeec0ca3 | ||
|
a0649edc1e | ||
|
0e0bcb3e10 | ||
|
686528d627 | ||
|
3afa920151 | ||
|
32c493e994 | ||
|
1a76bdf891 | ||
|
af48b3df29 | ||
|
58ac1fb2fa | ||
|
e922704f72 | ||
|
c6115735c7 | ||
|
9ddf5c72be | ||
|
cc5640326b | ||
|
bf447d7703 | ||
|
cbb008c938 | ||
|
eaa0c52b45 | ||
|
82791f7efc | ||
|
8c533e34ea | ||
|
37708ad9cd | ||
|
090dee857f | ||
|
d779ff7e6d | ||
|
fd8858f8c9 | ||
|
b234647a63 | ||
|
6e93eefc82 | ||
|
38fca30125 | ||
|
012ee9ca85 | ||
|
151252e33a | ||
|
7136cfc68b | ||
|
408c04f25f | ||
|
7f8e0dbc40 | ||
|
0de7ab18f6 | ||
|
e9bc7331bd | ||
|
797dd7c449 | ||
|
f37ccaa64f | ||
|
ab3937ee5a | ||
|
00f4551a7b | ||
|
257ddd028d | ||
|
e0a22e76f8 | ||
|
00a3610331 | ||
|
f502725120 | ||
|
b62a74b315 | ||
|
2ec382ae0e | ||
|
cbfee31593 | ||
|
6d647e13ff | ||
|
d2af3ff98d | ||
|
052d17e66a | ||
|
a9bc954e17 | ||
|
2983426771 | ||
|
c61eb94b3f | ||
|
3829eab1cf | ||
|
3fe8eeedc5 | ||
|
1efef88b6e | ||
|
7acdaf0b43 | ||
|
1ed25d76ba | ||
|
474c1f5e32 | ||
|
8b71fea5dc | ||
|
7bd99a22ee | ||
|
75b8cca3b3 | ||
|
ef2c29d5d4 | ||
|
218b3c8274 | ||
|
db9cb2ddda |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]
|
36
.github/workflows/codeql-analysis.yml
vendored
Normal file
36
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, devel ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ['go']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
44
.github/workflows/linux.yml
vendored
Normal file
44
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Test fzf on Linux
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, devel ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.14, 1.16]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1.62.0
|
||||
with:
|
||||
ruby-version: 3.0.0
|
||||
|
||||
- name: Install packages
|
||||
run: sudo apt-get install --yes zsh fish tmux
|
||||
|
||||
- name: Install Ruby gems
|
||||
run: sudo gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
||||
|
||||
- name: Rubocop
|
||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||
|
||||
- name: Unit test
|
||||
run: make test
|
||||
|
||||
- name: Integration test
|
||||
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
44
.github/workflows/macos.yml
vendored
Normal file
44
.github/workflows/macos.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Test fzf on macOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, devel ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.14, 1.16]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1.62.0
|
||||
with:
|
||||
ruby-version: 3.0.0
|
||||
|
||||
- name: Install packages
|
||||
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux
|
||||
|
||||
- name: Install Ruby gems
|
||||
run: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
||||
|
||||
- name: Rubocop
|
||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||
|
||||
- name: Unit test
|
||||
run: make test
|
||||
|
||||
- name: Integration test
|
||||
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
11
.gon.hcl
11
.gon.hcl
@@ -1,11 +0,0 @@
|
||||
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
|
||||
sign {
|
||||
application_identity = "Apple Development: junegunn.c@gmail.com"
|
||||
}
|
@@ -15,9 +15,55 @@ builds:
|
||||
ldflags:
|
||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||
hooks:
|
||||
post: gon .gon.hcl
|
||||
post: |
|
||||
sh -c '
|
||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||
}
|
||||
zip {
|
||||
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
|
||||
}
|
||||
EOF
|
||||
gon /tmp/fzf-gon-amd64.hcl
|
||||
'
|
||||
|
||||
- goos:
|
||||
- id: fzf-macos-arm
|
||||
binary: fzf
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
ldflags:
|
||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||
hooks:
|
||||
post: |
|
||||
sh -c '
|
||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||
}
|
||||
zip {
|
||||
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
|
||||
}
|
||||
EOF
|
||||
gon /tmp/fzf-gon-arm64.hcl
|
||||
'
|
||||
|
||||
- id: fzf
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
@@ -44,6 +90,8 @@ builds:
|
||||
|
||||
archives:
|
||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
builds:
|
||||
- fzf
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
@@ -57,6 +105,8 @@ release:
|
||||
name: fzf
|
||||
prerelease: auto
|
||||
name_template: '{{ .Tag }}'
|
||||
extra_files:
|
||||
- glob: ./dist/fzf-*darwin*.zip
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-devel"
|
||||
|
28
.travis.yml
28
.travis.yml
@@ -1,28 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.14"
|
||||
env: GO111MODULE=on
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- fish
|
||||
- zsh
|
||||
sources:
|
||||
sourceline: ppa:fish-shell/release-3
|
||||
homebrew:
|
||||
packages:
|
||||
- fish
|
||||
- tmux
|
||||
update: true
|
||||
install: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
||||
script:
|
||||
- make test
|
||||
# LC_ALL=C to avoid escape codes in
|
||||
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
|
||||
# macOS is built without HANDLE_MULTIBYTE?
|
||||
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
||||
- rubocop --require rubocop-minitest --require rubocop-performance
|
565
ADVANCED.md
Normal file
565
ADVANCED.md
Normal file
@@ -0,0 +1,565 @@
|
||||
Advanced fzf examples
|
||||
======================
|
||||
|
||||
*(Last update: 2021/05/22)*
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Screen Layout](#screen-layout)
|
||||
* [`--height`](#--height)
|
||||
* [`fzf-tmux`](#fzf-tmux)
|
||||
* [Popup window support](#popup-window-support)
|
||||
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
||||
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
||||
* [Toggling between data sources](#toggling-between-data-sources)
|
||||
* [Ripgrep integration](#ripgrep-integration)
|
||||
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
||||
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
||||
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
||||
* [Log tailing](#log-tailing)
|
||||
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
||||
* [Files listed in `git status`](#files-listed-in-git-status)
|
||||
* [Branches](#branches)
|
||||
* [Commit hashes](#commit-hashes)
|
||||
* [Color themes](#color-themes)
|
||||
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
fzf is an interactive [Unix filter][filter] program that is designed to be
|
||||
used with other Unix tools. It reads a list of items from the standard input,
|
||||
allows you to select a subset of the items, and prints the selected ones to
|
||||
the standard output. You can think of it as an interactive version of *grep*,
|
||||
and it's already useful even if you don't know any of its options.
|
||||
|
||||
```sh
|
||||
# 1. ps: Feed the list of processes to fzf
|
||||
# 2. fzf: Interactively select a process using fuzzy matching algorithm
|
||||
# 3. awk: Take the PID from the selected line
|
||||
# 3. kill: Kill the process with the PID
|
||||
ps -ef | fzf | awk '{print $2}' | xargs kill -9
|
||||
```
|
||||
|
||||
[filter]: https://en.wikipedia.org/wiki/Filter_(software)
|
||||
|
||||
While the above example succinctly summarizes the fundamental concept of fzf,
|
||||
you can build much more sophisticated interactive workflows using fzf once you
|
||||
learn its wide variety of features.
|
||||
|
||||
- To see the full list of options and features, see `man fzf`
|
||||
- To see the latest additions, see [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
This document will guide you through some examples that will familiarize you
|
||||
with the advanced features of fzf.
|
||||
|
||||
Screen Layout
|
||||
-------------
|
||||
|
||||
### `--height`
|
||||
|
||||
fzf by default opens in fullscreen mode, but it's not always desirable.
|
||||
Oftentimes, you want to see the current context of the terminal while using
|
||||
fzf. `--height` is an option for opening fzf below the cursor in
|
||||
non-fullscreen mode so you can still see the previous commands and their
|
||||
results above it.
|
||||
|
||||
```sh
|
||||
fzf --height=40%
|
||||
```
|
||||
|
||||

|
||||
|
||||
You might also want to experiment with other layout options such as
|
||||
`--layout=reverse`, `--info=inline`, `--border`, `--margin`, etc.
|
||||
|
||||
```sh
|
||||
fzf --height=40% --layout=reverse
|
||||
fzf --height=40% --layout=reverse --info=inline
|
||||
fzf --height=40% --layout=reverse --info=inline --border
|
||||
fzf --height=40% --layout=reverse --info=inline --border --margin=1
|
||||
fzf --height=40% --layout=reverse --info=inline --border --margin=1 --padding=1
|
||||
```
|
||||
|
||||

|
||||
|
||||
*(See `Layout` section of the man page to see the full list of options)*
|
||||
|
||||
But you definitely don't want to repeat `--height=40% --layout=reverse
|
||||
--info=inline --border --margin=1 --padding=1` every time you use fzf. You
|
||||
could write a wrapper script or shell alias, but there is an easier option.
|
||||
Define `$FZF_DEFAULT_OPTS` like so:
|
||||
|
||||
```sh
|
||||
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
|
||||
```
|
||||
|
||||
### `fzf-tmux`
|
||||
|
||||
Before fzf had `--height` option, we would open fzf in a tmux split pane not
|
||||
to take up the whole screen. This is done using `fzf-tmux` script.
|
||||
|
||||
```sh
|
||||
# Open fzf on a tmux split pane below the current pane.
|
||||
# Takes the same set of options.
|
||||
fzf-tmux --layout=reverse
|
||||
```
|
||||
|
||||

|
||||
|
||||
The limitation of `fzf-tmux` is that it only works when you're on tmux unlike
|
||||
`--height` option. But the advantage of it is that it's more flexible.
|
||||
(See `man fzf-tmux` for available options.)
|
||||
|
||||
```sh
|
||||
# On the right (50%)
|
||||
fzf-tmux -r
|
||||
|
||||
# On the left (30%)
|
||||
fzf-tmux -l30%
|
||||
|
||||
# Above the cursor
|
||||
fzf-tmux -u30%
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### Popup window support
|
||||
|
||||
But here's the really cool part; tmux 3.2 added support for popup windows. So
|
||||
you can open fzf in a popup window, which is quite useful if you frequently
|
||||
use split panes.
|
||||
|
||||
```sh
|
||||
# Open tmux in a tmux popup window (default size: 50% of the screen)
|
||||
fzf-tmux -p
|
||||
|
||||
# 80% width, 60% height
|
||||
fzf-tmux -p 80%,60%
|
||||
```
|
||||
|
||||

|
||||
|
||||
> You might also want to check out my tmux plugins which support this popup
|
||||
> window layout.
|
||||
>
|
||||
> - https://github.com/junegunn/tmux-fzf-url
|
||||
> - https://github.com/junegunn/tmux-fzf-maccy
|
||||
|
||||
Dynamic reloading of the list
|
||||
-----------------------------
|
||||
|
||||
fzf can dynamically update the candidate list using an arbitrary program with
|
||||
`reload` bindings (The design document for `reload` can be found
|
||||
[here][reload]).
|
||||
|
||||
[reload]: https://github.com/junegunn/fzf/issues/1750
|
||||
|
||||
### Updating the list of processes by pressing CTRL-R
|
||||
|
||||
This example shows how you can set up a binding for dynamically updating the
|
||||
list without restarting fzf.
|
||||
|
||||
```sh
|
||||
(date; ps -ef) |
|
||||
fzf --bind='ctrl-r:reload(date; ps -ef)' \
|
||||
--header=$'Press CTRL-R to reload\n\n' --header-lines=2 \
|
||||
--preview='echo {}' --preview-window=down,3,wrap \
|
||||
--layout=reverse --height=80% | awk '{print $2}' | xargs kill -9
|
||||
```
|
||||
|
||||

|
||||
|
||||
- The initial command is `(date; ps -ef)`. It prints the current date and
|
||||
time, and the list of the processes.
|
||||
- With `--header` option, you can show any message as the fixed header.
|
||||
- To disallow selecting the first two lines (`date` and `ps` header), we use
|
||||
`--header-lines=2` option.
|
||||
- `--bind='ctrl-r:reload(date; ps -ef)'` binds CTRL-R to `reload` action that
|
||||
runs `date; ps -ef`, so we can update the list of the processes by pressing
|
||||
CTRL-R.
|
||||
- We use simple `echo {}` preview option, so we can see the entire line on the
|
||||
preview window below even if it's too long
|
||||
|
||||
### Toggling between data sources
|
||||
|
||||
You're not limiited to just one reload binding. Set up multiple bindings so
|
||||
you can switch between data sources.
|
||||
|
||||
```sh
|
||||
find * | fzf --prompt 'All> ' \
|
||||
--header 'CTRL-D: Directories / CTRL-F: Files' \
|
||||
--bind 'ctrl-d:change-prompt(Directories> )+reload(find * -type d)' \
|
||||
--bind 'ctrl-f:change-prompt(Files> )+reload(find * -type f)'
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Ripgrep integration
|
||||
-------------------
|
||||
|
||||
### Using fzf as the secondary filter
|
||||
|
||||
* Requires [bat][bat]
|
||||
* Requires [Ripgrep][rg]
|
||||
|
||||
[bat]: https://github.com/sharkdp/bat
|
||||
[rg]: https://github.com/BurntSushi/ripgrep
|
||||
|
||||
fzf is pretty fast for filtering a list that you will rarely have to think
|
||||
about its performance. But it is not the right tool for searching for text
|
||||
inside many large files, and in that case you should definitely use something
|
||||
like [Ripgrep][rg].
|
||||
|
||||
In the next example, Ripgrep is the primary filter that searches for the given
|
||||
text in files, and fzf is used as the secondary fuzzy filter that adds
|
||||
interactivity to the workflow. And we use [bat][bat] to show the matching line in
|
||||
the preview window.
|
||||
|
||||
This is a bash script and it will not run as expected on other non-compliant
|
||||
shells. To avoid the compatibility issue, let's save this snippet as a script
|
||||
file called `rfv`.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 1. Search for text in files using Ripgrep
|
||||
# 2. Interactively narrow down the list using fzf
|
||||
# 3. Open the file in Vim
|
||||
IFS=: read -ra selected < <(
|
||||
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
|
||||
fzf --ansi \
|
||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||
--delimiter : \
|
||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
||||
)
|
||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
||||
```
|
||||
|
||||
And run it with an initial query string.
|
||||
|
||||
```sh
|
||||
# Make the script executable
|
||||
chmod +x rfv
|
||||
|
||||
# Run it with the initial query "algo"
|
||||
./rfv algo
|
||||
```
|
||||
|
||||
> Ripgrep will perform the initial search and list all the lines that contain
|
||||
`algo`. Then we further narrow down the list on fzf.
|
||||
|
||||

|
||||
|
||||
I know it's a lot to digest, let's try to break down the code.
|
||||
|
||||
- Ripgrep prints the matching lines in the following format
|
||||
```
|
||||
man/man1/fzf.1:54:.BI "--algo=" TYPE
|
||||
man/man1/fzf.1:55:Fuzzy matching algorithm (default: v2)
|
||||
man/man1/fzf.1:58:.BR v2 " Optimal scoring algorithm (quality)"
|
||||
src/pattern_test.go:7: "github.com/junegunn/fzf/src/algo"
|
||||
```
|
||||
The first token delimited by `:` is the file path, and the second token is
|
||||
the line number of the matching line. They respectively correspond to `{1}`
|
||||
and `{2}` in the preview command.
|
||||
- `--preview 'bat --color=always {1} --highlight-line {2}'`
|
||||
- As we run `rg` with `--color=always` option, we should tell fzf to parse
|
||||
ANSI color codes in the input by setting `--ansi`.
|
||||
- We customize how fzf colors various text elements using `--color` option.
|
||||
`-1` tells fzf to keep the original color from the input. See `man fzf` for
|
||||
available color options.
|
||||
- The value of `--preview-window` option consists of 5 components delimited
|
||||
by `,`
|
||||
1. `up` — Position of the preview window
|
||||
1. `60%` — Size of the preview window
|
||||
1. `border-bottom` — Preview window border only on the bottom side
|
||||
1. `+{2}+3/3` — Scroll offset of the preview contents
|
||||
1. `~3` — Fixed header
|
||||
- Let's break down the latter two. We want to display the bat output in the
|
||||
preview window with a certain scroll offset so that the matching line is
|
||||
positioned near the center of the preview window.
|
||||
- `+{2}` — The base offset is extracted from the second token
|
||||
- `+3` — We add 3 lines to the base offset to compensate for the header
|
||||
part of `bat` output
|
||||
- ```
|
||||
───────┬──────────────────────────────────────────────────────────
|
||||
│ File: CHANGELOG.md
|
||||
───────┼──────────────────────────────────────────────────────────
|
||||
1 │ CHANGELOG
|
||||
2 │ =========
|
||||
3 │
|
||||
4 │ 0.26.0
|
||||
5 │ ------
|
||||
```
|
||||
- `/3` adjusts the offset so that the matching line is shown at a third
|
||||
position in the window
|
||||
- `~3` makes the top three lines fixed header so that they are always
|
||||
visible regardless of the scroll offset
|
||||
- Once we selected a line, we open the file with `vim` (`vim
|
||||
"${selected[0]}"`) and move the cursor to the line (`+${selected[1]}`).
|
||||
|
||||
### Using fzf as interative Ripgrep launcher
|
||||
|
||||
We have learned that we can bind `reload` action to a key (e.g.
|
||||
`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind
|
||||
`reload` action to `change` event** so that whenever the user *changes* the
|
||||
query string on fzf, `reload` action is triggered.
|
||||
|
||||
Here is a variation of the above `rfv` script. fzf will restart Ripgrep every
|
||||
time the user updates the query string on fzf. Searching and filtering is
|
||||
completely done by Ripgrep, and fzf merely provides the interactive interface.
|
||||
So we lose the "fuzziness", but the performance will be better on larger
|
||||
projects, and it will free up memory as you narrow down the results.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 1. Search for text in files using Ripgrep
|
||||
# 2. Interactively restart Ripgrep with reload action
|
||||
# 3. Open the file in Vim
|
||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||
INITIAL_QUERY="${*:-}"
|
||||
IFS=: read -ra selected < <(
|
||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||
fzf --ansi \
|
||||
--disabled --query "$INITIAL_QUERY" \
|
||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||
--delimiter : \
|
||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
||||
)
|
||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
||||
```
|
||||
|
||||

|
||||
|
||||
- Instead of starting fzf in `rg ... | fzf` form, we start fzf without an
|
||||
explicit input, but with a custom `FZF_DEFAULT_COMMAND` variable. This way
|
||||
fzf can kill the initial Ripgrep process it starts with the initial query.
|
||||
Otherwise, the initial Ripgrep process will keep consuming system resources
|
||||
even after `reload` is triggered.
|
||||
- Filtering is no longer a responsibitiliy of fzf; hence `--disabled`
|
||||
- `{q}` in the reload command evaluates to the query string on fzf prompt.
|
||||
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
|
||||
reduce the number of intermediate Ripgrep processes while we're typing in
|
||||
a query.
|
||||
|
||||
### Switching to fzf-only search mode
|
||||
|
||||
*(Requires fzf 0.27.1 or above)*
|
||||
|
||||
In the previous example, we lost fuzzy matching capability as we completely
|
||||
delegated search functionality to Ripgrep. But we can dynamically switch to
|
||||
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
||||
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Two-phase filtering with Ripgrep and fzf
|
||||
#
|
||||
# 1. Search for text in files using Ripgrep
|
||||
# 2. Interactively restart Ripgrep with reload action
|
||||
# * Press alt-enter to switch to fzf-only filtering
|
||||
# 3. Open the file in Vim
|
||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||
INITIAL_QUERY="${*:-}"
|
||||
IFS=: read -ra selected < <(
|
||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||
fzf --ansi \
|
||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||
--disabled --query "$INITIAL_QUERY" \
|
||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
|
||||
--prompt '1. ripgrep> ' \
|
||||
--delimiter : \
|
||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
||||
)
|
||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
||||
```
|
||||
|
||||
* Phase 1. Filtering with Ripgrep
|
||||

|
||||
* Phase 2. Filtering with fzf
|
||||

|
||||
|
||||
- We added `--prompt` option to show that fzf is initially running in "Ripgrep
|
||||
launcher mode".
|
||||
- We added `alt-enter` binding that
|
||||
1. unbinds `change` event, so Ripgrep is no longer restarted on key press
|
||||
2. changes the prompt to `2. fzf>`
|
||||
3. enables search functionality of fzf
|
||||
4. clears the current query string that was used to start Ripgrep process
|
||||
5. and unbinds `alt-enter` itself as this is a one-off event
|
||||
- We reverted `--color` option for customizing how the matching chunks are
|
||||
displayed in the second phase
|
||||
|
||||
Log tailing
|
||||
-----------
|
||||
|
||||
fzf can run long-running preview commands and render partial results before
|
||||
completion. And when you specify `follow` flag in `--preview-window` option,
|
||||
fzf will "`tail -f`" the result, automatically scrolling to the bottom.
|
||||
|
||||
```bash
|
||||
# With "follow", preview window will automatically scroll to the bottom.
|
||||
# "\033[2J" is an ANSI escape sequence for clearing the screen.
|
||||
# When fzf reads this code it clears the previous preview contents.
|
||||
fzf --preview-window follow --preview 'for i in $(seq 100000); do
|
||||
echo "$i"
|
||||
sleep 0.01
|
||||
(( i % 300 == 0 )) && printf "\033[2J"
|
||||
done'
|
||||
```
|
||||
|
||||

|
||||
|
||||
Admittedly, that was a silly example. Here's a practical one for browsing
|
||||
Kubernetes pods.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
read -ra tokens < <(
|
||||
kubectl get pods --all-namespaces |
|
||||
fzf --info=inline --layout=reverse --header-lines=1 --border \
|
||||
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
||||
--header $'Press CTRL-O to open log in editor\n\n' \
|
||||
--bind ctrl-/:toggle-preview \
|
||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --namespace {1} {2}) > /dev/tty' \
|
||||
--preview-window up,follow \
|
||||
--preview 'kubectl logs --follow --tail=100000 --namespace {1} {2}' "$@"
|
||||
)
|
||||
[ ${#tokens} -gt 1 ] &&
|
||||
kubectl exec -it --namespace "${tokens[0]}" "${tokens[1]}" -- bash
|
||||
```
|
||||
|
||||

|
||||
|
||||
- The preview window will *"log tail"* the pod
|
||||
- Holding on to a large amount of log will consume a lot of memory. So we
|
||||
limited the initial log amount with `--tail=100000`.
|
||||
- With `execute` binding, you can press CTRL-O to open the log in your editor
|
||||
without leaving fzf
|
||||
- Select a pod (with an enter key) to `kubectl exec` into it
|
||||
|
||||
Key bindings for git objects
|
||||
----------------------------
|
||||
|
||||
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key
|
||||
bindings a few years ago. I'm going to show them here again, because they are
|
||||
seriously useful.
|
||||
|
||||
### Files listed in `git status`
|
||||
|
||||
<kbd>CTRL-G</kbd><kbd>CTRL-F</kbd>
|
||||
|
||||

|
||||
|
||||
### Branches
|
||||
|
||||
<kbd>CTRL-G</kbd><kbd>CTRL-B</kbd>
|
||||
|
||||

|
||||
|
||||
### Commit hashes
|
||||
|
||||
<kbd>CTRL-G</kbd><kbd>CTRL-H</kbd>
|
||||
|
||||

|
||||
|
||||
|
||||
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
|
||||
|
||||
Color themes
|
||||
------------
|
||||
|
||||
You can customize how fzf colors the text elements with `--color` option. Here
|
||||
are a few color themes. Note that you need a terminal emulator that can
|
||||
display 24-bit colors.
|
||||
|
||||
```sh
|
||||
# junegunn/seoul256.vim (dark)
|
||||
export FZF_DEFAULT_OPTS='--color=bg+:#3F3F3F,bg:#4B4B4B,border:#6B6B6B,spinner:#98BC99,hl:#719872,fg:#D9D9D9,header:#719872,info:#BDBB72,pointer:#E12672,marker:#E17899,fg+:#D9D9D9,preview-bg:#3F3F3F,prompt:#98BEDE,hl+:#98BC99'
|
||||
```
|
||||
|
||||

|
||||
|
||||
```sh
|
||||
# junegunn/seoul256.vim (light)
|
||||
export FZF_DEFAULT_OPTS='--color=bg+:#D9D9D9,bg:#E1E1E1,border:#C8C8C8,spinner:#719899,hl:#719872,fg:#616161,header:#719872,info:#727100,pointer:#E12672,marker:#E17899,fg+:#616161,preview-bg:#D9D9D9,prompt:#0099BD,hl+:#719899'
|
||||
```
|
||||
|
||||

|
||||
|
||||
```sh
|
||||
# morhetz/gruvbox
|
||||
export FZF_DEFAULT_OPTS='--color=bg+:#3c3836,bg:#32302f,spinner:#fb4934,hl:#928374,fg:#ebdbb2,header:#928374,info:#8ec07c,pointer:#fb4934,marker:#fb4934,fg+:#ebdbb2,prompt:#fb4934,hl+:#fb4934'
|
||||
```
|
||||
|
||||

|
||||
|
||||
```sh
|
||||
# arcticicestudio/nord-vim
|
||||
export FZF_DEFAULT_OPTS='--color=bg+:#3B4252,bg:#2E3440,spinner:#81A1C1,hl:#616E88,fg:#D8DEE9,header:#616E88,info:#81A1C1,pointer:#81A1C1,marker:#81A1C1,fg+:#D8DEE9,prompt:#81A1C1,hl+:#81A1C1'
|
||||
```
|
||||
|
||||

|
||||
|
||||
```sh
|
||||
# tomasr/molokai
|
||||
export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#E6DB74,hl:#7E8E91,fg:#F8F8F2,header:#7E8E91,info:#A6E22E,pointer:#A6E22E,marker:#F92672,fg+:#F8F8F2,prompt:#F92672,hl+:#F92672'
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Generating fzf color theme from Vim color schemes
|
||||
|
||||
The Vim plugin of fzf can generate `--color` option from the current color
|
||||
scheme according to `g:fzf_colors` variable. You can find the detailed
|
||||
explanation [here](https://github.com/junegunn/fzf/blob/master/README-VIM.md#explanation-of-gfzf_colors).
|
||||
|
||||
Here is an example. Add this to your Vim configuration file.
|
||||
|
||||
```vim
|
||||
let g:fzf_colors =
|
||||
\ { 'fg': ['fg', 'Normal'],
|
||||
\ 'bg': ['bg', 'Normal'],
|
||||
\ 'preview-bg': ['bg', 'NormalFloat'],
|
||||
\ 'hl': ['fg', 'Comment'],
|
||||
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||
\ 'hl+': ['fg', 'Statement'],
|
||||
\ 'info': ['fg', 'PreProc'],
|
||||
\ 'border': ['fg', 'Ignore'],
|
||||
\ 'prompt': ['fg', 'Conditional'],
|
||||
\ 'pointer': ['fg', 'Exception'],
|
||||
\ 'marker': ['fg', 'Keyword'],
|
||||
\ 'spinner': ['fg', 'Label'],
|
||||
\ 'header': ['fg', 'Comment'] }
|
||||
```
|
||||
|
||||
Then you can see how the `--color` option is generated by printing the result
|
||||
of `fzf#wrap()`.
|
||||
|
||||
```vim
|
||||
:echo fzf#wrap()
|
||||
```
|
||||
|
||||
Use this command to append `export FZF_DEFAULT_OPTS="..."` line to the end of
|
||||
the current file.
|
||||
|
||||
```vim
|
||||
:call append('$', printf('export FZF_DEFAULT_OPTS="%s"', matchstr(fzf#wrap().options, "--color[^']*")))
|
||||
```
|
2
BUILD.md
2
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.11 or above
|
||||
- Go 1.13 or above
|
||||
|
||||
### Using Makefile
|
||||
|
||||
|
126
CHANGELOG.md
126
CHANGELOG.md
@@ -1,6 +1,132 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.27.1
|
||||
------
|
||||
- Added `unbind` action. In the following Ripgrep launcher example, you can
|
||||
use `unbind(reload)` to switch to fzf-only filtering mode.
|
||||
- See https://github.com/junegunn/fzf/blob/master/ADVANCED.md#switching-to-fzf-only-search-mode
|
||||
- Vim plugin
|
||||
- Vim plugin will stop immediately even when the source command hasn't finished
|
||||
```vim
|
||||
" fzf will read the stream file while allowing other processes to append to it
|
||||
call fzf#run({'source': 'cat /dev/null > /tmp/stream; tail -f /tmp/stream'})
|
||||
```
|
||||
- It is now possible to open popup window relative to the currrent window
|
||||
```vim
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
|
||||
```
|
||||
|
||||
0.27.0
|
||||
------
|
||||
- More border options for `--preview-window`
|
||||
```sh
|
||||
fzf --preview 'cat {}' --preview-window border-left
|
||||
fzf --preview 'cat {}' --preview-window border-left --border horizontal
|
||||
fzf --preview 'cat {}' --preview-window top:border-bottom
|
||||
fzf --preview 'cat {}' --preview-window top:border-horizontal
|
||||
```
|
||||
- Automatically set `/dev/tty` as STDIN on execute action
|
||||
```sh
|
||||
# Redirect /dev/tty to suppress "Vim: Warning: Input is not from a terminal"
|
||||
# ls | fzf --bind "enter:execute(vim {} < /dev/tty)"
|
||||
|
||||
# "< /dev/tty" part is no longer needed
|
||||
ls | fzf --bind "enter:execute(vim {})"
|
||||
```
|
||||
- Bug fixes and improvements
|
||||
- Signed and notarized macOS binaries
|
||||
(Huge thanks to [BACKERS.md](https://github.com/junegunn/junegunn/blob/main/BACKERS.md)!)
|
||||
|
||||
0.26.0
|
||||
------
|
||||
- Added support for fixed header in preview window
|
||||
```sh
|
||||
# Display top 3 lines as the fixed header
|
||||
fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
|
||||
```
|
||||
- More advanced preview offset expression to better support the fixed header
|
||||
```sh
|
||||
# Preview with bat, matching line in the middle of the window below
|
||||
# the fixed header of the top 3 lines
|
||||
#
|
||||
# ~3 Top 3 lines as the fixed header
|
||||
# +{2} Base scroll offset extracted from the second field
|
||||
# +3 Extra offset to compensate for the 3-line header
|
||||
# /2 Put in the middle of the preview area
|
||||
#
|
||||
git grep --line-number '' |
|
||||
fzf --delimiter : \
|
||||
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \
|
||||
--preview-window '~3:+{2}+3/2'
|
||||
```
|
||||
- Added `select` and `deselect` action for unconditionally selecting or
|
||||
deselecting a single item in `--multi` mode. Complements `toggle` action.
|
||||
- Sigificant performance improvement in ANSI code processing
|
||||
- Bug fixes and improvements
|
||||
- Built with Go 1.16
|
||||
|
||||
0.25.1
|
||||
------
|
||||
- Added `close` action
|
||||
- Close preview window if open, abort fzf otherwise
|
||||
- Bug fixes and improvements
|
||||
|
||||
0.25.0
|
||||
------
|
||||
- Text attributes set in `--color` are not reset when fzf sees another
|
||||
`--color` option for the same element. This allows you to put custom text
|
||||
attributes in your `$FZF_DEFAULT_OPTS` and still have those attributes
|
||||
even when you override the colors.
|
||||
|
||||
```sh
|
||||
# Default colors and attributes
|
||||
fzf
|
||||
|
||||
# Apply custom text attributes
|
||||
export FZF_DEFAULT_OPTS='--color fg+:italic,hl:-1:underline,hl+:-1:reverse:underline'
|
||||
|
||||
fzf
|
||||
|
||||
# Different colors but you still have the attributes
|
||||
fzf --color hl:176,hl+:177
|
||||
|
||||
# Write "regular" if you want to clear the attributes
|
||||
fzf --color hl:176:regular,hl+:177:regular
|
||||
```
|
||||
- Renamed `--phony` to `--disabled`
|
||||
- You can dynamically enable and disable the search functionality using the
|
||||
new `enable-search`, `disable-search`, and `toggle-search` actions
|
||||
- You can assign a different color to the query string for when search is disabled
|
||||
```sh
|
||||
fzf --color query:#ffffff,disabled:#999999 --bind space:toggle-search
|
||||
```
|
||||
- Added `last` action to move the cursor to the last match
|
||||
- The opposite action `top` is renamed to `first`, but `top` is still
|
||||
recognized as a synonym for backward compatibility
|
||||
- Added `preview-top` and `preview-bottom` actions
|
||||
- Extended support for alt key chords: alt with any case-sensitive single character
|
||||
```sh
|
||||
fzf --bind alt-,:first,alt-.:last
|
||||
```
|
||||
|
||||
0.24.4
|
||||
------
|
||||
- Added `--preview-window` option `follow`
|
||||
```sh
|
||||
# Preview window will automatically scroll to the bottom
|
||||
fzf --preview-window follow --preview 'for i in $(seq 100000); do
|
||||
echo "$i"
|
||||
sleep 0.01
|
||||
(( i % 300 == 0 )) && printf "\033[2J"
|
||||
done'
|
||||
```
|
||||
- Added `change-prompt` action
|
||||
```sh
|
||||
fzf --prompt 'foo> ' --bind $'a:change-prompt:\x1b[31mbar> '
|
||||
```
|
||||
- Bug fixes and improvements
|
||||
|
||||
0.24.3
|
||||
------
|
||||
- Added `--padding` option
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
17
Makefile
17
Makefile
@@ -1,3 +1,4 @@
|
||||
SHELL := bash
|
||||
GO ?= go
|
||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
|
||||
@@ -26,6 +27,7 @@ $(error Not on git repository; cannot determine $$FZF_REVISION)
|
||||
endif
|
||||
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
|
||||
|
||||
BINARY32 := fzf-$(GOOS)_386
|
||||
BINARY64 := fzf-$(GOOS)_amd64
|
||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||
@@ -39,6 +41,10 @@ ifeq ($(UNAME_M),x86_64)
|
||||
BINARY := $(BINARY64)
|
||||
else ifeq ($(UNAME_M),amd64)
|
||||
BINARY := $(BINARY64)
|
||||
else ifeq ($(UNAME_M),i686)
|
||||
BINARY := $(BINARY32)
|
||||
else ifeq ($(UNAME_M),i386)
|
||||
BINARY := $(BINARY32)
|
||||
else ifeq ($(UNAME_M),armv5l)
|
||||
BINARY := $(BINARYARM5)
|
||||
else ifeq ($(UNAME_M),armv6l)
|
||||
@@ -47,6 +53,8 @@ else ifeq ($(UNAME_M),armv7l)
|
||||
BINARY := $(BINARYARM7)
|
||||
else ifeq ($(UNAME_M),armv8l)
|
||||
BINARY := $(BINARYARM8)
|
||||
else ifeq ($(UNAME_M),arm64)
|
||||
BINARY := $(BINARYARM8)
|
||||
else ifeq ($(UNAME_M),aarch64)
|
||||
BINARY := $(BINARYARM8)
|
||||
else ifeq ($(UNAME_M),ppc64le)
|
||||
@@ -58,12 +66,16 @@ endif
|
||||
all: target/$(BINARY)
|
||||
|
||||
test: $(SOURCES)
|
||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||
github.com/junegunn/fzf/src \
|
||||
github.com/junegunn/fzf/src/algo \
|
||||
github.com/junegunn/fzf/src/tui \
|
||||
github.com/junegunn/fzf/src/util
|
||||
|
||||
bench:
|
||||
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||
|
||||
install: bin/fzf
|
||||
|
||||
build:
|
||||
@@ -107,6 +119,9 @@ endif
|
||||
clean:
|
||||
$(RM) -r dist target
|
||||
|
||||
target/$(BINARY32): $(SOURCES)
|
||||
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARY64): $(SOURCES)
|
||||
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
@@ -141,4 +156,4 @@ update:
|
||||
$(GO) get -u
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: all build release test install clean docker docker-test update
|
||||
.PHONY: all build release test bench install clean docker docker-test update
|
||||
|
103
README-VIM.md
103
README-VIM.md
@@ -127,9 +127,15 @@ let g:fzf_action = {
|
||||
\ 'ctrl-v': 'vsplit' }
|
||||
|
||||
" Default fzf layout
|
||||
" - Popup window
|
||||
" - Popup window (center of the screen)
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||
|
||||
" - Popup window (center of the current window)
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
|
||||
|
||||
" - Popup window (anchored to the bottom of the current window)
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
|
||||
|
||||
" - down / up / left / right
|
||||
let g:fzf_layout = { 'down': '40%' }
|
||||
|
||||
@@ -171,19 +177,23 @@ list:
|
||||
|
||||
- `element` is an fzf element to apply a color to:
|
||||
|
||||
| Element | Description |
|
||||
| --- | --- |
|
||||
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
|
||||
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
|
||||
| `hl` / `hl+` | Highlighted substrings (normal / current) |
|
||||
| `gutter` | Background of the gutter on the left |
|
||||
| `pointer` | Pointer to the current line (`>`) |
|
||||
| `marker` | Multi-select marker (`>`) |
|
||||
| `border` | Border around the window (`--border` and `--preview`) |
|
||||
| `header` | Header (`--header` or `--header-lines`) |
|
||||
| `info` | Info line (match counters) |
|
||||
| `spinner` | Streaming input indicator |
|
||||
| `prompt` | Prompt before query (`> `) |
|
||||
| Element | Description |
|
||||
| --- | --- |
|
||||
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
|
||||
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
|
||||
| `preview-fg` / `preview-bg` | Preview window text and background |
|
||||
| `hl` / `hl+` | Highlighted substrings (normal / current) |
|
||||
| `gutter` | Background of the gutter on the left |
|
||||
| `pointer` | Pointer to the current line (`>`) |
|
||||
| `marker` | Multi-select marker (`>`) |
|
||||
| `border` | Border around the window (`--border` and `--preview`) |
|
||||
| `header` | Header (`--header` or `--header-lines`) |
|
||||
| `info` | Info line (match counters) |
|
||||
| `spinner` | Streaming input indicator |
|
||||
| `query` | Query string |
|
||||
| `disabled` | Query string when search is disabled |
|
||||
| `prompt` | Prompt before query (`> `) |
|
||||
| `pointer` | Pointer to the current line (`>`) |
|
||||
|
||||
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||
color when considering each of the following highlight groups
|
||||
@@ -298,6 +308,7 @@ following options are allowed:
|
||||
- Optional:
|
||||
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||
- `relative` [boolean default v:false]
|
||||
- `border` [string default `rounded`]: Border style
|
||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
||||
|
||||
@@ -358,7 +369,7 @@ Our `:LS` command will be much more useful if we can pass a directory argument
|
||||
to it, so that something like `:LS /tmp` is possible.
|
||||
|
||||
```vim
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
command! -bang -complete=dir -nargs=? LS
|
||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
```
|
||||
|
||||
@@ -368,7 +379,7 @@ a unique name to our command and pass it as the first argument to `fzf#wrap`.
|
||||
```vim
|
||||
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||
" The name is ignored if g:fzf_history_dir is not defined.
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
command! -bang -complete=dir -nargs=? LS
|
||||
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
```
|
||||
|
||||
@@ -388,15 +399,41 @@ Tips
|
||||
|
||||
### 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 the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
|
||||
If you find the default ANSI colors to be different, consider configuring the
|
||||
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
|
||||
in Neovim.
|
||||
|
||||
- On Neovim
|
||||
- On GVim
|
||||
- On Terminal Vim with a non-default layout
|
||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||
```vim
|
||||
" Terminal colors for seoul256 color scheme
|
||||
if has('nvim')
|
||||
let g:terminal_color_0 = '#4e4e4e'
|
||||
let g:terminal_color_1 = '#d68787'
|
||||
let g:terminal_color_2 = '#5f865f'
|
||||
let g:terminal_color_3 = '#d8af5f'
|
||||
let g:terminal_color_4 = '#85add4'
|
||||
let g:terminal_color_5 = '#d7afaf'
|
||||
let g:terminal_color_6 = '#87afaf'
|
||||
let g:terminal_color_7 = '#d0d0d0'
|
||||
let g:terminal_color_8 = '#626262'
|
||||
let g:terminal_color_9 = '#d75f87'
|
||||
let g:terminal_color_10 = '#87af87'
|
||||
let g:terminal_color_11 = '#ffd787'
|
||||
let g:terminal_color_12 = '#add4fb'
|
||||
let g:terminal_color_13 = '#ffafaf'
|
||||
let g:terminal_color_14 = '#87d7d7'
|
||||
let g:terminal_color_15 = '#e4e4e4'
|
||||
else
|
||||
let g:terminal_ansi_colors = [
|
||||
\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',
|
||||
\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',
|
||||
\ '#626262', '#d75f87', '#87af87', '#ffd787',
|
||||
\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'
|
||||
\ ]
|
||||
endif
|
||||
```
|
||||
|
||||
#### Starting fzf in a popup window
|
||||
### Starting fzf in a popup window
|
||||
|
||||
```vim
|
||||
" Required:
|
||||
@@ -406,7 +443,7 @@ The latest versions of Vim and Neovim include builtin terminal emulator
|
||||
" Optional:
|
||||
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||
" - highlight [string default 'Comment']: Highlight group for border
|
||||
" - relative [boolean default v:false]
|
||||
" - border [string default 'rounded']: Border style
|
||||
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||
@@ -424,21 +461,21 @@ else
|
||||
endif
|
||||
```
|
||||
|
||||
#### Hide statusline
|
||||
### Hide statusline
|
||||
|
||||
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||
the window.
|
||||
|
||||
For example, if you use a non-popup layout (e.g. `{'down': '40%'}`) on Neovim,
|
||||
you might want to temporarily disable the statusline for a cleaner look.
|
||||
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
|
||||
'40%'}`), you might want to temporarily disable the statusline for a cleaner
|
||||
look.
|
||||
|
||||
```vim
|
||||
if has('nvim') && !exists('g:fzf_layout')
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
endif
|
||||
let g:fzf_layout = { 'down': '30%' }
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
```
|
||||
|
||||
[License](LICENSE)
|
||||
@@ -446,4 +483,4 @@ endif
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
68
README.md
68
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)
|
||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://github.com/junegunn/fzf/actions)
|
||||
===
|
||||
|
||||
fzf is a general-purpose command-line fuzzy finder.
|
||||
@@ -17,7 +17,7 @@ Pros
|
||||
- The most comprehensive feature set
|
||||
- Flexible layout
|
||||
- Batteries included
|
||||
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
||||
- Vim/Neovim plugin, key bindings, and fuzzy auto-completion
|
||||
|
||||
Table of Contents
|
||||
-----------------
|
||||
@@ -25,7 +25,7 @@ Table of Contents
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||
* [Using Homebrew](#using-homebrew)
|
||||
* [Using git](#using-git)
|
||||
* [Using Linux package managers](#using-linux-package-managers)
|
||||
* [Windows](#windows)
|
||||
@@ -84,9 +84,9 @@ stuff.
|
||||
|
||||
[bin]: https://github.com/junegunn/fzf/releases
|
||||
|
||||
### Using Homebrew or Linuxbrew
|
||||
### Using Homebrew
|
||||
|
||||
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
||||
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
|
||||
to install fzf.
|
||||
|
||||
```sh
|
||||
@@ -166,12 +166,12 @@ For more installation options, see [README-VIM.md](README-VIM.md).
|
||||
Upgrading fzf
|
||||
-------------
|
||||
|
||||
fzf is being actively developed and you might want to upgrade it once in a
|
||||
fzf is being actively developed, and you might want to upgrade it once in a
|
||||
while. Please follow the instruction below depending on the installation
|
||||
method used.
|
||||
|
||||
- git: `cd ~/.fzf && git pull && ./install`
|
||||
- brew: `brew update; brew reinstall fzf`
|
||||
- brew: `brew update; brew upgrade fzf`
|
||||
- macports: `sudo port upgrade fzf`
|
||||
- chocolatey: `choco upgrade fzf`
|
||||
- vim-plug: `:PlugUpdate fzf`
|
||||
@@ -217,7 +217,7 @@ cursor with `--height` option.
|
||||
vim $(fzf --height 40%)
|
||||
```
|
||||
|
||||
Also check out `--reverse` and `--layout` options if you prefer
|
||||
Also, check out `--reverse` and `--layout` options if you prefer
|
||||
"top-down" layout instead of the default "bottom-up" layout.
|
||||
|
||||
```sh
|
||||
@@ -264,6 +264,13 @@ or `py`.
|
||||
- `FZF_DEFAULT_COMMAND`
|
||||
- Default command to use when input is tty
|
||||
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
||||
- > :warning: This variable is not used by shell extensions due to the
|
||||
> slight difference in requirements.
|
||||
>
|
||||
> (e.g. `CTRL-T` runs `$FZF_CTRL_T_COMMAND` instead, `vim **<tab>` runs
|
||||
> `_fzf_compgen_path()`, and `cd **<tab>` runs `_fzf_compgen_dir()`)
|
||||
>
|
||||
> The available options are described later in this document.
|
||||
- `FZF_DEFAULT_OPTS`
|
||||
- Default options
|
||||
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||
@@ -282,9 +289,10 @@ If you learn by watching videos, check out this screencast by [@samoshkin](https
|
||||
Examples
|
||||
--------
|
||||
|
||||
Many useful examples can be found on [the wiki
|
||||
page](https://github.com/junegunn/fzf/wiki/examples). Feel free to add your
|
||||
own as well.
|
||||
* [Wiki page of examples](https://github.com/junegunn/fzf/wiki/examples)
|
||||
* *Disclaimer: The examples on this page are maintained by the community
|
||||
and are not thoroughly tested*
|
||||
* [Advanced fzf examples](https://github.com/junegunn/fzf/blob/master/ADVANCED.md)
|
||||
|
||||
`fzf-tmux` script
|
||||
-----------------
|
||||
@@ -331,7 +339,7 @@ fish.
|
||||
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||
- Set `FZF_ALT_C_OPTS` to pass additional options
|
||||
|
||||
If you're on a tmux session, you can start fzf in a tmux split pane or in
|
||||
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
||||
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
||||
See `fzf-tmux --help` for available options.
|
||||
|
||||
@@ -343,12 +351,12 @@ Fuzzy completion for bash and zsh
|
||||
#### Files and directories
|
||||
|
||||
Fuzzy completion for files and directories can be triggered if the word before
|
||||
the cursor ends with the trigger sequence which is by default `**`.
|
||||
the cursor ends with the trigger sequence, which is by default `**`.
|
||||
|
||||
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
|
||||
|
||||
```sh
|
||||
# Files under current directory
|
||||
# Files under the current directory
|
||||
# - You can select multiple items with TAB key
|
||||
vim **<TAB>
|
||||
|
||||
@@ -372,7 +380,7 @@ cd ~/github/fzf**<TAB>
|
||||
#### Process IDs
|
||||
|
||||
Fuzzy completion for PIDs is provided for kill command. In this case,
|
||||
there is no trigger sequence, just press tab key after kill command.
|
||||
there is no trigger sequence; just press the tab key after the kill command.
|
||||
|
||||
```sh
|
||||
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
||||
@@ -381,7 +389,7 @@ kill -9 <TAB>
|
||||
|
||||
#### Host names
|
||||
|
||||
For ssh and telnet commands, fuzzy completion for host names is provided. The
|
||||
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
||||
names are extracted from /etc/hosts and ~/.ssh/config.
|
||||
|
||||
```sh
|
||||
@@ -404,7 +412,7 @@ unalias **<TAB>
|
||||
export FZF_COMPLETION_TRIGGER='~~'
|
||||
|
||||
# Options to fzf command
|
||||
export FZF_COMPLETION_OPTS='+c -x'
|
||||
export FZF_COMPLETION_OPTS='--border --info=inline'
|
||||
|
||||
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||
# command for listing path candidates.
|
||||
@@ -469,11 +477,11 @@ _fzf_complete_doge() {
|
||||
|
||||
- The arguments before `--` are the options to fzf.
|
||||
- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
|
||||
- Then write a set of commands that generates the completion candidates and
|
||||
- Then, write a set of commands that generates the completion candidates and
|
||||
feed its output to the function using process substitution (`< <(...)`).
|
||||
|
||||
zsh will automatically pick up the function using the naming convention but in
|
||||
bash you have to manually associate the function with the command using
|
||||
bash you have to manually associate the function with the command using the
|
||||
`complete` command.
|
||||
|
||||
```sh
|
||||
@@ -509,12 +517,12 @@ Advanced topics
|
||||
|
||||
fzf is fast and is [getting even faster][perf]. Performance should not be
|
||||
a problem in most use cases. However, you might want to be aware of the
|
||||
options that affect the performance.
|
||||
options that affect performance.
|
||||
|
||||
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
|
||||
- `--ansi` tells fzf to extract and parse ANSI color codes in the input, and it
|
||||
makes the initial scanning slower. So it's not recommended that you add it
|
||||
to your `$FZF_DEFAULT_OPTS`.
|
||||
- `--nth` makes fzf slower as fzf has to tokenize each line.
|
||||
- `--nth` makes fzf slower because it has to tokenize each line.
|
||||
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||
line.
|
||||
- If you absolutely need better performance, you can consider using
|
||||
@@ -563,17 +571,17 @@ FZF_DEFAULT_COMMAND='find . -type f' \
|
||||
#### 3. Interactive ripgrep integration
|
||||
|
||||
The following example uses fzf as the selector interface for ripgrep. We bound
|
||||
`reload` action to `change` event, so every time you type on fzf, ripgrep
|
||||
`reload` action to `change` event, so every time you type on fzf, the ripgrep
|
||||
process will restart with the updated query string denoted by the placeholder
|
||||
expression `{q}`. Also, note that we used `--phony` option so that fzf doesn't
|
||||
perform any secondary filtering.
|
||||
expression `{q}`. Also, note that we used `--disabled` option so that fzf
|
||||
doesn't perform any secondary filtering.
|
||||
|
||||
```sh
|
||||
INITIAL_QUERY=""
|
||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
||||
--ansi --phony --query "$INITIAL_QUERY" \
|
||||
--ansi --disabled --query "$INITIAL_QUERY" \
|
||||
--height=50% --layout=reverse
|
||||
```
|
||||
|
||||
@@ -589,7 +597,7 @@ Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
|
||||
The window can be scrolled using the mouse or custom key bindings.
|
||||
|
||||
```bash
|
||||
# {} is replaced to the single-quoted string of the focused line
|
||||
# {} is replaced with the single-quoted string of the focused line
|
||||
fzf --preview 'cat {}'
|
||||
```
|
||||
|
||||
@@ -608,7 +616,7 @@ You can customize the size, position, and border of the preview window using
|
||||
|
||||
```bash
|
||||
fzf --height 40% --layout reverse --info inline --border \
|
||||
--preview 'file {}' --preview-window down:1:noborder \
|
||||
--preview 'file {}' --preview-window up,1,border-horizontal \
|
||||
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
||||
```
|
||||
|
||||
@@ -661,7 +669,7 @@ fzf
|
||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||
```
|
||||
|
||||
If you want the command to follow symbolic links, and don't want it to exclude
|
||||
If you want the command to follow symbolic links and don't want it to exclude
|
||||
hidden files, use the following command:
|
||||
|
||||
```sh
|
||||
@@ -698,4 +706,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
15
bin/fzf-tmux
15
bin/fzf-tmux
@@ -135,8 +135,8 @@ if [[ -z "$TMUX" ]]; then
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# --height option is not allowed
|
||||
args=("${args[@]}" "--no-height")
|
||||
# --height option is not allowed. CTRL-Z is also disabled.
|
||||
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
||||
|
||||
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
||||
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
||||
@@ -204,7 +204,16 @@ if [[ "$opt" =~ "-K -E" ]]; then
|
||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||
cat <&0 > $fifo1 &
|
||||
fi
|
||||
tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "bash $argsf" > /dev/null 2>&1
|
||||
|
||||
# tmux dropped the support for `-K`, `-R` to popup command
|
||||
# TODO: We can remove this once tmux 3.2 is released
|
||||
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
|
||||
opt="${opt/-K/}"
|
||||
else
|
||||
opt="${opt} -R"
|
||||
fi
|
||||
|
||||
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
|
||||
exit $?
|
||||
fi
|
||||
|
||||
|
142
doc/fzf.txt
142
doc/fzf.txt
@@ -1,22 +1,22 @@
|
||||
fzf.txt fzf Last change: October 18 2020
|
||||
fzf.txt fzf Last change: May 19 2021
|
||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||
==============================================================================
|
||||
|
||||
FZF Vim integration
|
||||
Installation
|
||||
Summary
|
||||
:FZF[!]
|
||||
Configuration
|
||||
Examples
|
||||
Explanation of g:fzf_colors
|
||||
fzf#run
|
||||
fzf#wrap
|
||||
Global options supported by fzf#wrap
|
||||
Tips
|
||||
fzf inside terminal buffer
|
||||
Starting fzf in a popup window
|
||||
Hide statusline
|
||||
License
|
||||
FZF Vim integration |fzf-vim-integration|
|
||||
Installation |fzf-installation|
|
||||
Summary |fzf-summary|
|
||||
:FZF[!] |:FZF|
|
||||
Configuration |fzf-configuration|
|
||||
Examples |fzf-examples|
|
||||
Explanation of g:fzf_colors |fzf-explanation-of-gfzfcolors|
|
||||
fzf#run |fzf#run|
|
||||
fzf#wrap |fzf#wrap|
|
||||
Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
|
||||
Tips |fzf-tips|
|
||||
fzf inside terminal buffer |fzf-inside-terminal-buffer|
|
||||
Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
|
||||
Hide statusline |fzf-hide-statusline|
|
||||
License |fzf-license|
|
||||
|
||||
FZF VIM INTEGRATION *fzf-vim-integration*
|
||||
==============================================================================
|
||||
@@ -155,9 +155,15 @@ Examples~
|
||||
\ 'ctrl-v': 'vsplit' }
|
||||
|
||||
" Default fzf layout
|
||||
" - Popup window
|
||||
" - Popup window (center of the screen)
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||
|
||||
" - Popup window (center of the current window)
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
|
||||
|
||||
" - Popup window (anchored to the bottom of the current window)
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
|
||||
|
||||
" - down / up / left / right
|
||||
let g:fzf_layout = { 'down': '40%' }
|
||||
|
||||
@@ -200,21 +206,25 @@ list:
|
||||
<
|
||||
- `element` is an fzf element to apply a color to:
|
||||
|
||||
----------------------+------------------------------------------------------
|
||||
Element | Description ~
|
||||
----------------------+------------------------------------------------------
|
||||
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
|
||||
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
|
||||
`hl` / `hl+` | Highlighted substrings (normal / current)
|
||||
`gutter` | Background of the gutter on the left
|
||||
`pointer` | Pointer to the current line ( `>` )
|
||||
`marker` | Multi-select marker ( `>` )
|
||||
`border` | Border around the window ( `--border` and `--preview` )
|
||||
`header` | Header ( `--header` or `--header-lines` )
|
||||
`info` | Info line (match counters)
|
||||
`spinner` | Streaming input indicator
|
||||
`prompt` | Prompt before query ( `> ` )
|
||||
----------------------+------------------------------------------------------
|
||||
----------------------------+------------------------------------------------------
|
||||
Element | Description ~
|
||||
----------------------------+------------------------------------------------------
|
||||
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
|
||||
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
|
||||
`preview-fg` / `preview-bg` | Preview window text and background
|
||||
`hl` / `hl+` | Highlighted substrings (normal / current)
|
||||
`gutter` | Background of the gutter on the left
|
||||
`pointer` | Pointer to the current line ( `>` )
|
||||
`marker` | Multi-select marker ( `>` )
|
||||
`border` | Border around the window ( `--border` and `--preview` )
|
||||
`header` | Header ( `--header` or `--header-lines` )
|
||||
`info` | Info line (match counters)
|
||||
`spinner` | Streaming input indicator
|
||||
`query` | Query string
|
||||
`disabled` | Query string when search is disabled
|
||||
`prompt` | Prompt before query ( `> ` )
|
||||
`pointer` | Pointer to the current line ( `>` )
|
||||
----------------------------+------------------------------------------------------
|
||||
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||
color when considering each of the following highlight groups
|
||||
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
||||
@@ -314,9 +324,9 @@ following options are allowed:
|
||||
- Optional:
|
||||
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||
- `highlight` [string default `'Comment'`]: Highlight group for border
|
||||
- `relative` [boolean default v:false]
|
||||
- `border` [string default `rounded`]: Border style
|
||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
||||
|
||||
|
||||
FZF#WRAP
|
||||
@@ -369,7 +379,7 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
|
||||
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||
to it, so that something like `:LS /tmp` is possible.
|
||||
>
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
command! -bang -complete=dir -nargs=? LS
|
||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
<
|
||||
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
|
||||
@@ -377,7 +387,7 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
|
||||
>
|
||||
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||
" The name is ignored if g:fzf_history_dir is not defined.
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
command! -bang -complete=dir -nargs=? LS
|
||||
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
<
|
||||
|
||||
@@ -409,8 +419,46 @@ The latest versions of Vim and Neovim include builtin terminal emulator
|
||||
- On Terminal Vim with a non-default layout
|
||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||
|
||||
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
|
||||
If you find the default ANSI colors to be different, consider configuring the
|
||||
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
|
||||
in Neovim.
|
||||
|
||||
Starting fzf in a popup window~
|
||||
*g:terminal_color_15* *g:terminal_color_14* *g:terminal_color_13*
|
||||
*g:terminal_color_12* *g:terminal_color_11* *g:terminal_color_10* *g:terminal_color_9*
|
||||
*g:terminal_color_8* *g:terminal_color_7* *g:terminal_color_6* *g:terminal_color_5*
|
||||
*g:terminal_color_4* *g:terminal_color_3* *g:terminal_color_2* *g:terminal_color_1*
|
||||
*g:terminal_color_0*
|
||||
>
|
||||
" Terminal colors for seoul256 color scheme
|
||||
if has('nvim')
|
||||
let g:terminal_color_0 = '#4e4e4e'
|
||||
let g:terminal_color_1 = '#d68787'
|
||||
let g:terminal_color_2 = '#5f865f'
|
||||
let g:terminal_color_3 = '#d8af5f'
|
||||
let g:terminal_color_4 = '#85add4'
|
||||
let g:terminal_color_5 = '#d7afaf'
|
||||
let g:terminal_color_6 = '#87afaf'
|
||||
let g:terminal_color_7 = '#d0d0d0'
|
||||
let g:terminal_color_8 = '#626262'
|
||||
let g:terminal_color_9 = '#d75f87'
|
||||
let g:terminal_color_10 = '#87af87'
|
||||
let g:terminal_color_11 = '#ffd787'
|
||||
let g:terminal_color_12 = '#add4fb'
|
||||
let g:terminal_color_13 = '#ffafaf'
|
||||
let g:terminal_color_14 = '#87d7d7'
|
||||
let g:terminal_color_15 = '#e4e4e4'
|
||||
else
|
||||
let g:terminal_ansi_colors = [
|
||||
\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',
|
||||
\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',
|
||||
\ '#626262', '#d75f87', '#87af87', '#ffd787',
|
||||
\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'
|
||||
\ ]
|
||||
endif
|
||||
<
|
||||
|
||||
< Starting fzf in a popup window >____________________________________________~
|
||||
*fzf-starting-fzf-in-a-popup-window*
|
||||
>
|
||||
" Required:
|
||||
@@ -420,7 +468,7 @@ Starting fzf in a popup window~
|
||||
" Optional:
|
||||
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||
" - highlight [string default 'Comment']: Highlight group for border
|
||||
" - relative [boolean default v:false]
|
||||
" - border [string default 'rounded']: Border style
|
||||
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||
@@ -436,21 +484,21 @@ or above) by putting fzf-tmux options in `tmux` key.
|
||||
endif
|
||||
<
|
||||
|
||||
Hide statusline~
|
||||
< Hide statusline >___________________________________________________________~
|
||||
*fzf-hide-statusline*
|
||||
|
||||
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||
the window.
|
||||
|
||||
For example, if you use a non-popup layout (e.g. `{'down': '40%'}`) on Neovim,
|
||||
you might want to temporarily disable the statusline for a cleaner look.
|
||||
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
|
||||
'40%'}`), you might want to temporarily disable the statusline for a cleaner
|
||||
look.
|
||||
>
|
||||
if has('nvim') && !exists('g:fzf_layout')
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
endif
|
||||
let g:fzf_layout = { 'down': '30%' }
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
<
|
||||
|
||||
LICENSE *fzf-license*
|
||||
@@ -458,7 +506,7 @@ LICENSE *fzf-license*
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||
|
16
go.mod
16
go.mod
@@ -2,14 +2,16 @@ module github.com/junegunn/fzf
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell v1.4.0
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/mattn/go-shellwords v1.0.10
|
||||
github.com/saracen/walker v0.1.1
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.12
|
||||
github.com/mattn/go-shellwords v1.0.11
|
||||
github.com/rivo/uniseg v0.2.0
|
||||
github.com/saracen/walker v0.1.2
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
42
go.sum
42
go.sum
@@ -2,34 +2,32 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
|
||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/saracen/walker v0.1.1 h1:Ou2QIKTWqo0QxhtuHVmtObbmhjMCEUyJ82xp0uV+MGI=
|
||||
github.com/saracen/walker v0.1.1/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
|
||||
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
|
||||
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM=
|
||||
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
5
install
5
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.24.3
|
||||
version=0.27.1
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
@@ -168,7 +168,8 @@ archi=$(uname -sm)
|
||||
binary_available=1
|
||||
binary_error=""
|
||||
case "$archi" in
|
||||
Darwin\ *64) download fzf-$version-darwin_amd64.tar.gz ;;
|
||||
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
|
||||
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
|
||||
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
||||
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
||||
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
||||
|
10
install.ps1
10
install.ps1
@@ -1,4 +1,4 @@
|
||||
$version="0.24.3"
|
||||
$version="0.27.1"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
@@ -43,7 +43,11 @@ function download {
|
||||
$url="https://github.com/junegunn/fzf/releases/download/$version/$file"
|
||||
$temp=$env:TMP + "\fzf.zip"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
|
||||
if ($PSVersionTable.PSVersion.Major -ge 3) {
|
||||
Invoke-WebRequest -Uri $url -OutFile $temp
|
||||
} else {
|
||||
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
|
||||
}
|
||||
if ($?) {
|
||||
(Microsoft.PowerShell.Archive\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
|
||||
} else {
|
||||
@@ -53,7 +57,7 @@ function download {
|
||||
$binary_error="Failed to download $file"
|
||||
return
|
||||
}
|
||||
check_binary >$null
|
||||
echo y | icacls $fzf_base\bin\fzf.exe /grant Administrator:F ; check_binary >$null
|
||||
}
|
||||
|
||||
download "fzf-$version-windows_amd64.zip"
|
||||
|
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var version string = "0.24"
|
||||
var version string = "0.27"
|
||||
var revision string = "devel"
|
||||
|
||||
func main() {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Nov 2020" "fzf 0.24.3" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "May 2021" "fzf 0.27.1" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
196
man/man1/fzf.1
196
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Nov 2020" "fzf 0.24.3" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "May 2021" "fzf 0.27.1" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -71,9 +71,10 @@ Transform the presentation of each line using field index expressions
|
||||
.BI "-d, --delimiter=" "STR"
|
||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
||||
.TP
|
||||
.BI "--phony"
|
||||
.BI "--disabled"
|
||||
Do not perform search. With this option, fzf becomes a simple selector
|
||||
interface rather than a "fuzzy finder".
|
||||
interface rather than a "fuzzy finder". You can later enable the search using
|
||||
\fBenable-search\fR or \fBtoggle-search\fR action.
|
||||
.SS Search result
|
||||
.TP
|
||||
.B "+s, --no-sort"
|
||||
@@ -202,6 +203,8 @@ Draw border around the finder
|
||||
.br
|
||||
.BR right
|
||||
.br
|
||||
.BR none
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B "--no-unicode"
|
||||
@@ -323,6 +326,8 @@ color mappings.
|
||||
\fBbg+ \fRBackground (current line)
|
||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||
\fBhl+ \fRHighlighted substrings (current line)
|
||||
\fBquery \fRQuery string
|
||||
\fBdisabled \fRQuery string when search is disabled
|
||||
\fBinfo \fRInfo line (match counters)
|
||||
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
||||
\fBprompt \fRPrompt
|
||||
@@ -439,7 +444,7 @@ e.g.
|
||||
done'\fR
|
||||
.RE
|
||||
.TP
|
||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
|
||||
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"
|
||||
|
||||
.RS
|
||||
.B POSITION: (default: right)
|
||||
@@ -448,45 +453,77 @@ e.g.
|
||||
\fBleft
|
||||
\fBright
|
||||
|
||||
\fRDetermines the layout of the preview window. If the argument contains
|
||||
\fB:hidden\fR, the preview window will be hidden by default until
|
||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||
Line wrap can be enabled with \fB:wrap\fR flag. Cyclic scrolling is enabled
|
||||
with \fB:cycle\fR flag.
|
||||
\fRDetermines the layout of the preview window.
|
||||
|
||||
If size is given as 0, preview window will not be visible, but fzf will still
|
||||
* If the argument contains \fB:hidden\fR, the preview window will be hidden by
|
||||
default until \fBtoggle-preview\fR action is triggered.
|
||||
|
||||
* If size is given as 0, preview window will not be visible, but fzf will still
|
||||
execute the command in the background.
|
||||
|
||||
To change the style of the border of the preview window, specify one of
|
||||
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
|
||||
sharp edges), or \fBnoborder\fR (no border).
|
||||
* Long lines are truncated by default. Line wrap can be enabled with
|
||||
\fB:wrap\fR flag.
|
||||
|
||||
\fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
|
||||
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
|
||||
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
|
||||
for adjusting the base offset so that you can see the text above it. It should
|
||||
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
|
||||
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height.
|
||||
* Preview window will automatically scroll to the bottom when \fB:follow\fR
|
||||
flag is set, similarly to how \fBtail -f\fR works.
|
||||
|
||||
\fBdefault\fR resets all options previously set to the default.
|
||||
.RS
|
||||
e.g.
|
||||
\fBfzf --preview-window follow --preview 'for i in $(seq 100000); do
|
||||
echo "$i"
|
||||
sleep 0.01
|
||||
(( i % 300 == 0 )) && printf "\\033[2J"
|
||||
done'\fR
|
||||
.RE
|
||||
|
||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
||||
|
||||
* To change the style of the border of the preview window, specify one of
|
||||
the options for \fB--border\fR with \fBborder-\fR prefix.
|
||||
e.g. \fBborder-rounded\fR (border with rounded edges, default),
|
||||
\fBborder-sharp\fR (border with sharp edges), \fBborder-left\fR,
|
||||
\fBborder-none\fR, etc.
|
||||
|
||||
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
|
||||
preview window.
|
||||
|
||||
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
|
||||
|
||||
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB-INTEGER\fR or \fB+INTEGER\fR).
|
||||
|
||||
- The final \fB/DENOM\fR part is for specifying a fraction of the preview window height.
|
||||
|
||||
* \fB~HEADER_LINES\fR keeps the top N lines as the fixed header so that they
|
||||
are always visible.
|
||||
|
||||
* \fBdefault\fR resets all options previously set to the default.
|
||||
|
||||
.RS
|
||||
e.g.
|
||||
\fB# Non-default scroll window positions and sizes
|
||||
fzf --preview="head {}" --preview-window=up:30%
|
||||
fzf --preview="file {}" --preview-window=down:1
|
||||
fzf --preview="head {}" --preview-window=up,30%
|
||||
fzf --preview="file {}" --preview-window=down,1
|
||||
|
||||
# Initial scroll offset is set to the line number of each line of
|
||||
# git grep output *minus* 5 lines (-5)
|
||||
git grep --line-number '' |
|
||||
fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
|
||||
fzf --delimiter : --preview 'nl {1}' --preview-window '+{2}-5'
|
||||
|
||||
# Preview with bat, matching line in the middle of the window (-/2)
|
||||
# Preview with bat, matching line in the middle of the window below
|
||||
# the fixed header of the top 3 lines
|
||||
#
|
||||
# ~3 Top 3 lines as the fixed header
|
||||
# +{2} Base scroll offset extracted from the second field
|
||||
# +3 Extra offset to compensate for the 3-line header
|
||||
# /2 Put in the middle of the preview area
|
||||
#
|
||||
git grep --line-number '' |
|
||||
fzf --delimiter : \\
|
||||
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\
|
||||
--preview-window +{2}-/2\fR
|
||||
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \\
|
||||
--preview-window '~3,+{2}+3/2'
|
||||
|
||||
# Display top 3 lines as the fixed header
|
||||
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||
.RE
|
||||
|
||||
.SS Scripting
|
||||
@@ -495,10 +532,12 @@ e.g.
|
||||
Start the finder with the given query
|
||||
.TP
|
||||
.B "-1, --select-1"
|
||||
Automatically select the only match
|
||||
If there is only one match for the initial query (\fB--query\fR), do not start
|
||||
interactive finder and automatically select the only match
|
||||
.TP
|
||||
.B "-0, --exit-0"
|
||||
Exit immediately when there's no match
|
||||
If there is no match for the initial query (\fB--query\fR), do not start
|
||||
interactive finder and exit immediately
|
||||
.TP
|
||||
.BI "-f, --filter=" "STR"
|
||||
Filter mode. Do not start interactive finder. When used with \fB--no-sort\fR,
|
||||
@@ -551,7 +590,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
|
||||
.TP
|
||||
.B FZF_DEFAULT_COMMAND
|
||||
Default command to use when input is tty. On *nix systems, fzf runs the command
|
||||
with \fBsh -c\fR, so make sure that it's POSIX-compliant.
|
||||
with \fB$SHELL -c\fR if \fBSHELL\fR is set, otherwise with \fBsh -c\fR, so in
|
||||
this case make sure that the command is POSIX-compliant.
|
||||
.TP
|
||||
.B FZF_DEFAULT_OPTS
|
||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||
@@ -650,9 +690,7 @@ e.g.
|
||||
.br
|
||||
\fIctrl-alt-[a-z]\fR
|
||||
.br
|
||||
\fIalt-[a-z]\fR
|
||||
.br
|
||||
\fIalt-[0-9]\fR
|
||||
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
||||
.br
|
||||
\fIf[1-12]\fR
|
||||
.br
|
||||
@@ -676,8 +714,6 @@ e.g.
|
||||
.br
|
||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||
.br
|
||||
\fIalt-/\fR
|
||||
.br
|
||||
\fItab\fR
|
||||
.br
|
||||
\fIbtab\fR (\fIshift-tab\fR)
|
||||
@@ -712,6 +748,14 @@ e.g.
|
||||
.br
|
||||
\fIshift-right\fR
|
||||
.br
|
||||
\fIalt-shift-up\fR
|
||||
.br
|
||||
\fIalt-shift-down\fR
|
||||
.br
|
||||
\fIalt-shift-left\fR
|
||||
.br
|
||||
\fIalt-shift-right\fR
|
||||
.br
|
||||
\fIleft-click\fR
|
||||
.br
|
||||
\fIright-click\fR
|
||||
@@ -726,8 +770,8 @@ or any single character
|
||||
Triggered whenever the query string is changed
|
||||
|
||||
e.g.
|
||||
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
|
||||
fzf --bind change:top\fR
|
||||
\fB# Move cursor to the first entry whenever the query is changed
|
||||
fzf --bind change:first\fR
|
||||
.RE
|
||||
|
||||
\fIbackward-eof\fR
|
||||
@@ -753,16 +797,22 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBclear-selection\fR (clear multi-selection)
|
||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||
\fBclear-query\fR (clear query string)
|
||||
\fBdelete-char\fR \fIdel\fR
|
||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||
\fBdeselect\fR
|
||||
\fBdeselect-all\fR (deselect all matches)
|
||||
\fBdisable-search\fR (disable search functionality)
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBenable-search\fR (enable search functionality)
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\fR (see below for the details)
|
||||
\fBexecute-silent(...)\fR (see below for the details)
|
||||
\fBfirst\fR (move to the first match)
|
||||
\fBforward-char\fR \fIctrl-f right\fR
|
||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||
\fBignore\fR
|
||||
@@ -770,6 +820,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBkill-line\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBlast\fR (move to the last match)
|
||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||
\fBpage-down\fR \fIpgdn\fR
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
@@ -782,11 +833,14 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBpreview-page-up\fR
|
||||
\fBpreview-half-page-down\fR
|
||||
\fBpreview-half-page-up\fR
|
||||
\fBpreview-bottom\fR
|
||||
\fBpreview-top\fR
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBrefresh-preview\fR
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBselect\fR
|
||||
\fBselect-all\fR (select all matches)
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
\fBtoggle-all\fR (toggle all matches)
|
||||
@@ -795,9 +849,10 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||
\fBtoggle-preview\fR
|
||||
\fBtoggle-preview-wrap\fR
|
||||
\fBtoggle-search\fR (toggle search functionality)
|
||||
\fBtoggle-sort\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBtop\fR (move to the top result)
|
||||
\fBunbind(...)\fR (unbind bindings)
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
@@ -808,7 +863,40 @@ A key or an event can be bound to one or more of the following actions.
|
||||
Multiple actions can be chained using \fB+\fR separator.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||
\fBfzf --multi --bind 'ctrl-a:select-all+accept'\fR
|
||||
\fBfzf --multi --bind 'ctrl-a:select-all' --bind 'ctrl-a:+accept'\fR
|
||||
|
||||
.SS ACTION ARGUMENT
|
||||
|
||||
An action denoted with \fB(...)\fR suffix takes an argument.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind 'ctrl-a:change-prompt(NewPrompt> )'\fR
|
||||
\fBfzf --bind 'ctrl-v:preview(cat {})' --preview-window hidden\fR
|
||||
|
||||
If the argument contains parentheses, fzf may fail to parse the expression. In
|
||||
that case, you can use any of the following alternative notations to avoid
|
||||
parse errors.
|
||||
|
||||
\fBaction-name[...]\fR
|
||||
\fBaction-name~...~\fR
|
||||
\fBaction-name!...!\fR
|
||||
\fBaction-name@...@\fR
|
||||
\fBaction-name#...#\fR
|
||||
\fBaction-name$...$\fR
|
||||
\fBaction-name%...%\fR
|
||||
\fBaction-name^...^\fR
|
||||
\fBaction-name&...&\fR
|
||||
\fBaction-name*...*\fR
|
||||
\fBaction-name;...;\fR
|
||||
\fBaction-name/.../\fR
|
||||
\fBaction-name|...|\fR
|
||||
\fBaction-name:...\fR
|
||||
.RS
|
||||
The last one is the special form that frees you from parse errors as it does
|
||||
not expect the closing character. The catch is that it should be the last one
|
||||
in the comma-separated list of key-action pairs.
|
||||
.RE
|
||||
|
||||
.SS COMMAND EXECUTION
|
||||
|
||||
@@ -820,30 +908,6 @@ binding \fBenter\fR key to \fBless\fR command like follows.
|
||||
|
||||
You can use the same placeholder expressions as in \fB--preview\fR.
|
||||
|
||||
If the command contains parentheses, fzf may fail to parse the expression. In
|
||||
that case, you can use any of the following alternative notations to avoid
|
||||
parse errors.
|
||||
|
||||
\fBexecute[...]\fR
|
||||
\fBexecute~...~\fR
|
||||
\fBexecute!...!\fR
|
||||
\fBexecute@...@\fR
|
||||
\fBexecute#...#\fR
|
||||
\fBexecute$...$\fR
|
||||
\fBexecute%...%\fR
|
||||
\fBexecute^...^\fR
|
||||
\fBexecute&...&\fR
|
||||
\fBexecute*...*\fR
|
||||
\fBexecute;...;\fR
|
||||
\fBexecute/.../\fR
|
||||
\fBexecute|...|\fR
|
||||
\fBexecute:...\fR
|
||||
.RS
|
||||
The last one is the special form that frees you from parse errors as it does
|
||||
not expect the closing character. The catch is that it should be the last one
|
||||
in the comma-separated list of key-action pairs.
|
||||
.RE
|
||||
|
||||
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
|
||||
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||
@@ -851,6 +915,10 @@ executes the command without the switching. Note that fzf will not be
|
||||
responsive until the command is complete. For asynchronous execution, start
|
||||
your command as a background process (i.e. appending \fB&\fR).
|
||||
|
||||
On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
|
||||
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
||||
POSIX-compliant.
|
||||
|
||||
.SS RELOAD INPUT
|
||||
|
||||
\fBreload(...)\fR action is used to dynamically update the input list
|
||||
@@ -869,7 +937,7 @@ e.g.
|
||||
INITIAL_QUERY="foobar"
|
||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
|
||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||
--ansi --phony --query "$INITIAL_QUERY"\fR
|
||||
--ansi --disabled --query "$INITIAL_QUERY"\fR
|
||||
|
||||
.SS PREVIEW BINDING
|
||||
|
||||
|
173
plugin/fzf.vim
173
plugin/fzf.vim
@@ -154,46 +154,79 @@ function! fzf#install()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:version_requirement(val, min)
|
||||
let val = split(a:val, '\.')
|
||||
let min = split(a:min, '\.')
|
||||
for idx in range(0, len(min) - 1)
|
||||
let v = get(val, idx, 0)
|
||||
if v < min[idx] | return 0
|
||||
elseif v > min[idx] | return 1
|
||||
let s:versions = {}
|
||||
function s:get_version(bin)
|
||||
if has_key(s:versions, a:bin)
|
||||
return s:versions[a:bin]
|
||||
end
|
||||
let command = a:bin . ' --version'
|
||||
let output = systemlist(command)
|
||||
if v:shell_error || empty(output)
|
||||
return ''
|
||||
endif
|
||||
let ver = matchstr(output[-1], '[0-9.]\+')
|
||||
let s:versions[a:bin] = ver
|
||||
return ver
|
||||
endfunction
|
||||
|
||||
function! s:compare_versions(a, b)
|
||||
let a = split(a:a, '\.')
|
||||
let b = split(a:b, '\.')
|
||||
for idx in range(0, max([len(a), len(b)]) - 1)
|
||||
let v1 = str2nr(get(a, idx, 0))
|
||||
let v2 = str2nr(get(b, idx, 0))
|
||||
if v1 < v2 | return -1
|
||||
elseif v1 > v2 | return 1
|
||||
endif
|
||||
endfor
|
||||
return 1
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:compare_binary_versions(a, b)
|
||||
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
||||
endfunction
|
||||
|
||||
let s:checked = {}
|
||||
function! fzf#exec(...)
|
||||
if !exists('s:exec')
|
||||
if executable(s:fzf_go)
|
||||
let s:exec = s:fzf_go
|
||||
elseif executable('fzf')
|
||||
let s:exec = 'fzf'
|
||||
elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||
redraw
|
||||
call fzf#install()
|
||||
return fzf#exec()
|
||||
else
|
||||
redraw
|
||||
throw 'fzf executable not found'
|
||||
let binaries = []
|
||||
if executable('fzf')
|
||||
call add(binaries, 'fzf')
|
||||
endif
|
||||
if executable(s:fzf_go)
|
||||
call add(binaries, s:fzf_go)
|
||||
endif
|
||||
|
||||
if empty(binaries)
|
||||
if input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||
redraw
|
||||
call fzf#install()
|
||||
return fzf#exec()
|
||||
else
|
||||
redraw
|
||||
throw 'fzf executable not found'
|
||||
endif
|
||||
elseif len(binaries) > 1
|
||||
call sort(binaries, 's:compare_binary_versions')
|
||||
endif
|
||||
|
||||
let s:exec = binaries[-1]
|
||||
endif
|
||||
|
||||
if a:0 && !has_key(s:checked, a:1)
|
||||
let command = s:exec . ' --version'
|
||||
let output = systemlist(command)
|
||||
if v:shell_error || empty(output)
|
||||
throw printf('Failed to run "%s": %s', command, output)
|
||||
endif
|
||||
let fzf_version = matchstr(output[-1], '[0-9.]\+')
|
||||
if s:version_requirement(fzf_version, a:1)
|
||||
let fzf_version = s:get_version(s:exec)
|
||||
if empty(fzf_version)
|
||||
let message = printf('Failed to run "%s --version"', s:exec)
|
||||
unlet s:exec
|
||||
throw message
|
||||
end
|
||||
|
||||
if s:compare_versions(fzf_version, a:1) >= 0
|
||||
let s:checked[a:1] = 1
|
||||
return s:exec
|
||||
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
|
||||
let s:versions = {}
|
||||
unlet s:exec
|
||||
redraw
|
||||
call fzf#install()
|
||||
return fzf#exec(a:1, 1)
|
||||
@@ -424,27 +457,27 @@ try
|
||||
throw v:exception
|
||||
endtry
|
||||
|
||||
if !has_key(dict, 'dir')
|
||||
if !s:present(dict, 'dir')
|
||||
let dict.dir = s:fzf_getcwd()
|
||||
endif
|
||||
if has('win32unix') && has_key(dict, 'dir')
|
||||
if has('win32unix') && s:present(dict, 'dir')
|
||||
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||
endif
|
||||
|
||||
if has_key(dict, 'source')
|
||||
let source = dict.source
|
||||
let source = remove(dict, 'source')
|
||||
let type = type(source)
|
||||
if type == 1
|
||||
let prefix = '( '.source.' )|'
|
||||
let source_command = source
|
||||
elseif type == 3
|
||||
let temps.input = s:fzf_tempname()
|
||||
call writefile(source, temps.input)
|
||||
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
||||
else
|
||||
throw 'Invalid source type'
|
||||
endif
|
||||
else
|
||||
let prefix = ''
|
||||
let source_command = ''
|
||||
endif
|
||||
|
||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
||||
@@ -454,20 +487,24 @@ try
|
||||
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'))
|
||||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
||||
if prefer_tmux && use_tmux
|
||||
let use_height = 0
|
||||
let use_term = 0
|
||||
endif
|
||||
if use_height
|
||||
if use_term
|
||||
let optstr .= ' --no-height'
|
||||
elseif use_height
|
||||
let height = s:calc_size(&lines, dict.down, dict)
|
||||
let optstr .= ' --height='.height
|
||||
elseif use_term
|
||||
let optstr .= ' --no-height'
|
||||
endif
|
||||
let optstr .= s:border_opt(get(dict, 'window', 0))
|
||||
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
||||
if len(source_command)
|
||||
let $FZF_DEFAULT_COMMAND = source_command
|
||||
endif
|
||||
let command = (use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||
|
||||
if use_term
|
||||
return s:execute_term(dict, command, temps)
|
||||
@@ -478,6 +515,13 @@ try
|
||||
call s:callback(dict, lines)
|
||||
return lines
|
||||
finally
|
||||
if len(source_command)
|
||||
if len(prev_default_command)
|
||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
||||
else
|
||||
execute 'unlet $FZF_DEFAULT_COMMAND'
|
||||
endif
|
||||
endif
|
||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||
endtry
|
||||
endfunction
|
||||
@@ -507,8 +551,8 @@ function! s:fzf_tmux(dict)
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||
return printf('LINES=%d COLUMNS=%d %s %s - --',
|
||||
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size)
|
||||
endfunction
|
||||
|
||||
function! s:splittable(dict)
|
||||
@@ -638,14 +682,14 @@ function! s:execute(dict, command, use_height, temps) abort
|
||||
let a:temps.shellscript = shellscript
|
||||
endif
|
||||
if a:use_height
|
||||
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
||||
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s < /dev/tty 2> /dev/tty', &lines, command))
|
||||
else
|
||||
execute 'silent !'.command
|
||||
endif
|
||||
let exit_status = v:shell_error
|
||||
redraw!
|
||||
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||
let lines = s:collect(a:temps)
|
||||
return s:exit_handler(exit_status, command) ? lines : []
|
||||
endfunction
|
||||
|
||||
function! s:execute_tmux(dict, command, temps) abort
|
||||
@@ -659,7 +703,8 @@ function! s:execute_tmux(dict, command, temps) abort
|
||||
call system(command)
|
||||
let exit_status = v:shell_error
|
||||
redraw!
|
||||
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||
let lines = s:collect(a:temps)
|
||||
return s:exit_handler(exit_status, command) ? lines : []
|
||||
endfunction
|
||||
|
||||
function! s:calc_size(max, val, dict)
|
||||
@@ -762,6 +807,13 @@ function! s:split(dict)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
nnoremap <silent> <Plug>(fzf-insert) i
|
||||
nnoremap <silent> <Plug>(fzf-normal) <Nop>
|
||||
if exists(':tnoremap')
|
||||
tnoremap <silent> <Plug>(fzf-insert) <C-\><C-n>i
|
||||
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
|
||||
endif
|
||||
|
||||
function! s:execute_term(dict, command, temps) abort
|
||||
let winrest = winrestcmd()
|
||||
let pbuf = bufnr('')
|
||||
@@ -774,7 +826,7 @@ function! s:execute_term(dict, command, temps) abort
|
||||
function! fzf.switch_back(inplace)
|
||||
if a:inplace && bufnr('') == self.buf
|
||||
if bufexists(self.pbuf)
|
||||
execute 'keepalt b' self.pbuf
|
||||
execute 'keepalt keepjumps b' self.pbuf
|
||||
endif
|
||||
" No other listed buffer
|
||||
if bufnr('') == self.buf
|
||||
@@ -806,14 +858,18 @@ function! s:execute_term(dict, command, temps) abort
|
||||
execute self.winrest
|
||||
endif
|
||||
|
||||
let lines = s:collect(self.temps)
|
||||
if !s:exit_handler(a:code, self.command, 1)
|
||||
return
|
||||
endif
|
||||
|
||||
call s:pushd(self.dict)
|
||||
let lines = s:collect(self.temps)
|
||||
call s:callback(self.dict, lines)
|
||||
call self.switch_back(s:getpos() == self.ppos)
|
||||
|
||||
if &buftype == 'terminal'
|
||||
call feedkeys(&filetype == 'fzf' ? "\<Plug>(fzf-insert)" : "\<Plug>(fzf-normal)")
|
||||
endif
|
||||
endfunction
|
||||
|
||||
try
|
||||
@@ -830,6 +886,9 @@ function! s:execute_term(dict, command, temps) abort
|
||||
call termopen(command, fzf)
|
||||
else
|
||||
let term_opts = {'exit_cb': function(fzf.on_exit)}
|
||||
if v:version >= 802
|
||||
let term_opts.term_kill = 'term'
|
||||
endif
|
||||
if is_popup
|
||||
let term_opts.hidden = 1
|
||||
else
|
||||
@@ -843,6 +902,10 @@ function! s:execute_term(dict, command, temps) abort
|
||||
call term_wait(fzf.buf, 20)
|
||||
endif
|
||||
endif
|
||||
tnoremap <buffer> <c-z> <nop>
|
||||
if exists('&termwinkey') && (empty(&termwinkey) || &termwinkey =~? '<c-w>')
|
||||
tnoremap <buffer> <c-w> <c-w>.
|
||||
endif
|
||||
finally
|
||||
call s:dopopd()
|
||||
endtry
|
||||
@@ -898,13 +961,9 @@ if has('nvim')
|
||||
function s:create_popup(hl, opts) abort
|
||||
let buf = nvim_create_buf(v:false, v:true)
|
||||
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
||||
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
|
||||
let win = nvim_open_win(buf, v:true, opts)
|
||||
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
||||
call setwinvar(win, '&colorcolumn', '')
|
||||
if !empty(border)
|
||||
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
|
||||
endif
|
||||
return buf
|
||||
endfunction
|
||||
else
|
||||
@@ -923,11 +982,19 @@ else
|
||||
endif
|
||||
|
||||
function! s:popup(opts) abort
|
||||
let xoffset = get(a:opts, 'xoffset', 0.5)
|
||||
let yoffset = get(a:opts, 'yoffset', 0.5)
|
||||
let relative = get(a:opts, 'relative', 0)
|
||||
|
||||
" Use current window size for positioning relatively positioned popups
|
||||
let columns = relative ? winwidth(0) : &columns
|
||||
let lines = relative ? winheight(0) : (&lines - has('nvim'))
|
||||
|
||||
" Size and position
|
||||
let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(&columns * a:opts.width)]), &columns])
|
||||
let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(&lines * a:opts.height)]), &lines - has('nvim')])
|
||||
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
|
||||
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
|
||||
let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(columns * a:opts.width)]), columns])
|
||||
let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(lines * a:opts.height)]), lines])
|
||||
let row = float2nr(yoffset * (lines - height)) + (relative ? win_screenpos(0)[0] - 1 : 0)
|
||||
let col = float2nr(xoffset * (columns - width)) + (relative ? win_screenpos(0)[1] - 1 : 0)
|
||||
|
||||
" Managing the differences
|
||||
let row = min([max([0, row]), &lines - has('nvim') - height])
|
||||
|
@@ -46,9 +46,20 @@ __fzf_comprun() {
|
||||
fi
|
||||
}
|
||||
|
||||
__fzf_orig_completion_filter() {
|
||||
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= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
|
||||
__fzf_orig_completion() {
|
||||
local l comp f cmd
|
||||
while read -r l; do
|
||||
if [[ "$l" =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
|
||||
comp="${BASH_REMATCH[1]}"
|
||||
f="${BASH_REMATCH[2]}"
|
||||
cmd="${BASH_REMATCH[3]}"
|
||||
[[ "$f" = _fzf_* ]] && continue
|
||||
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
||||
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then
|
||||
__fzf_nospace_commands="$__fzf_nospace_commands $cmd "
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_fzf_opts_completion() {
|
||||
@@ -137,7 +148,7 @@ _fzf_handle_dynamic_completion() {
|
||||
ret=$?
|
||||
# _completion_loader may not have updated completion for the command
|
||||
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||
eval "${orig_complete/ -F / -o nospace -F }"
|
||||
else
|
||||
@@ -165,7 +176,7 @@ __fzf_generic_path_completion() {
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$dir" ] && dir='.'
|
||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||
printf "%q$3 " "$item"
|
||||
done)
|
||||
matches=${matches% }
|
||||
@@ -221,7 +232,7 @@ _fzf_complete() {
|
||||
if [[ "$cur" == *"$trigger" ]]; then
|
||||
cur=${cur:0:${#cur}-${#trigger}}
|
||||
|
||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||
if [ -n "$selected" ]; then
|
||||
COMPREPLY=("$selected")
|
||||
@@ -306,9 +317,7 @@ a_cmds="
|
||||
svn tar unzip zip"
|
||||
|
||||
# Preserve existing completion
|
||||
eval "$(complete |
|
||||
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
||||
__fzf_orig_completion_filter)"
|
||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
|
||||
|
||||
if type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=1
|
||||
@@ -353,7 +362,7 @@ _fzf_setup_completion() {
|
||||
return 1
|
||||
fi
|
||||
shift
|
||||
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||
__fzf_orig_completion < <(complete -p "$@" 2> /dev/null)
|
||||
for cmd in "$@"; do
|
||||
case "$kind" in
|
||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||
|
@@ -145,7 +145,7 @@ __fzf_generic_path_completion() {
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$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_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||
echo -n "${(q)item}$suffix "
|
||||
done)
|
||||
matches=${matches% }
|
||||
@@ -207,11 +207,10 @@ _fzf_complete() {
|
||||
type $post > /dev/null 2>&1 || post=cat
|
||||
|
||||
_fzf_feed_fifo "$fifo"
|
||||
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$lbuf$matches"
|
||||
fi
|
||||
zle reset-prompt
|
||||
command rm -f "$fifo"
|
||||
}
|
||||
|
||||
@@ -302,6 +301,7 @@ fzf-completion() {
|
||||
|
||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
||||
zle reset-prompt
|
||||
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
||||
_fzf_dir_completion "$prefix" "$lbuf"
|
||||
else
|
||||
|
@@ -18,7 +18,7 @@ __fzf_select__() {
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
|
||||
printf '%q ' "$item"
|
||||
done
|
||||
echo
|
||||
@@ -41,7 +41,7 @@ __fzf_cd__() {
|
||||
local cmd dir
|
||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||
}
|
||||
|
||||
__fzf_history__() {
|
||||
@@ -49,7 +49,7 @@ __fzf_history__() {
|
||||
output=$(
|
||||
builtin fc -lnr -2147483648 |
|
||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||
) || return
|
||||
READLINE_LINE=${output#*$'\t'}
|
||||
if [ -z "$READLINE_POINT" ]; then
|
||||
|
@@ -20,6 +20,7 @@ function fzf_key_bindings
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||
# $dir itself, even if hidden.
|
||||
@@ -31,7 +32,7 @@ function fzf_key_bindings
|
||||
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||
end
|
||||
if [ -z "$result" ]
|
||||
@@ -42,6 +43,7 @@ function fzf_key_bindings
|
||||
commandline -t ""
|
||||
end
|
||||
for i in $result
|
||||
commandline -it -- $prefix
|
||||
commandline -it -- (string escape $i)
|
||||
commandline -it -- ' '
|
||||
end
|
||||
@@ -51,7 +53,7 @@ function fzf_key_bindings
|
||||
function fzf-history-widget -d "Show command history"
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
||||
|
||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||
@@ -74,13 +76,14 @@ function fzf_key_bindings
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||
|
||||
if [ -n "$result" ]
|
||||
@@ -88,6 +91,7 @@ function fzf_key_bindings
|
||||
|
||||
# Remove last token from commandline.
|
||||
commandline -t ""
|
||||
commandline -it -- $prefix
|
||||
end
|
||||
end
|
||||
|
||||
@@ -116,9 +120,15 @@ function fzf_key_bindings
|
||||
bind -M insert \ec fzf-cd-widget
|
||||
end
|
||||
|
||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
|
||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||
set -l commandline (commandline -t)
|
||||
|
||||
# strip -option= from token if present
|
||||
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
||||
set commandline (string replace -- "$prefix" '' $commandline)
|
||||
|
||||
# eval is used to do shell expansion on paths
|
||||
set -l commandline (eval "printf '%s' "(commandline -t))
|
||||
eval set commandline $commandline
|
||||
|
||||
if [ -z $commandline ]
|
||||
# Default to current directory with no --query
|
||||
@@ -138,6 +148,7 @@ function fzf_key_bindings
|
||||
|
||||
echo $dir
|
||||
echo $fzf_query
|
||||
echo $prefix
|
||||
end
|
||||
|
||||
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
||||
|
@@ -45,7 +45,8 @@ __fsel() {
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||
local item
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||
echo -n "${(q)item} "
|
||||
done
|
||||
local ret=$?
|
||||
@@ -67,36 +68,22 @@ fzf-file-widget() {
|
||||
zle -N 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
|
||||
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 \
|
||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
||||
if [[ -z "$dir" ]]; then
|
||||
zle redisplay
|
||||
return 0
|
||||
fi
|
||||
if [ -z "$BUFFER" ]; then
|
||||
BUFFER="cd ${(q)dir}"
|
||||
zle accept-line
|
||||
else
|
||||
print -sr "cd ${(q)dir}"
|
||||
cd "$dir"
|
||||
fi
|
||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||
BUFFER="cd ${(q)dir}"
|
||||
zle accept-line
|
||||
local ret=$?
|
||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||
zle fzf-redraw-prompt
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-cd-widget
|
||||
@@ -107,7 +94,7 @@ fzf-history-widget() {
|
||||
local selected num
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||
local ret=$?
|
||||
if [ -n "$selected" ]; then
|
||||
num=$selected[1]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2020 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
269
src/ansi.go
269
src/ansi.go
@@ -1,8 +1,6 @@
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
@@ -19,17 +17,18 @@ type ansiState struct {
|
||||
fg tui.Color
|
||||
bg tui.Color
|
||||
attr tui.Attr
|
||||
lbg tui.Color
|
||||
}
|
||||
|
||||
func (s *ansiState) colored() bool {
|
||||
return s.fg != -1 || s.bg != -1 || s.attr > 0
|
||||
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
|
||||
}
|
||||
|
||||
func (s *ansiState) equals(t *ansiState) bool {
|
||||
if t == nil {
|
||||
return !s.colored()
|
||||
}
|
||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
|
||||
}
|
||||
|
||||
func (s *ansiState) ToString() string {
|
||||
@@ -81,73 +80,154 @@ func toAnsiString(color tui.Color, offset int) string {
|
||||
return ret + ";"
|
||||
}
|
||||
|
||||
var ansiRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
/*
|
||||
References:
|
||||
- https://github.com/gnachman/iTerm2
|
||||
- http://ascii-table.com/ansi-escape-sequences.php
|
||||
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
*/
|
||||
// The following regular expression will include not all but most of the
|
||||
// frequently used ANSI sequences
|
||||
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||
func isPrint(c uint8) bool {
|
||||
return '\x20' <= c && c <= '\x7e'
|
||||
}
|
||||
|
||||
func findAnsiStart(str string) int {
|
||||
idx := 0
|
||||
for ; idx < len(str); idx++ {
|
||||
b := str[idx]
|
||||
if b == 0x1b || b == 0x0e || b == 0x0f {
|
||||
return idx
|
||||
func matchOperatingSystemCommand(s string) int {
|
||||
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
||||
// ^ match starting here
|
||||
//
|
||||
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
||||
for ; i < len(s) && isPrint(s[i]); i++ {
|
||||
}
|
||||
if i < len(s) {
|
||||
if s[i] == '\x07' {
|
||||
return i + 1
|
||||
}
|
||||
if b == 0x08 && idx > 0 {
|
||||
return idx - 1
|
||||
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||
return i + 2
|
||||
}
|
||||
}
|
||||
return idx
|
||||
return -1
|
||||
}
|
||||
|
||||
func matchControlSequence(s string) int {
|
||||
// `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
||||
// ^ match starting here
|
||||
//
|
||||
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
||||
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ {
|
||||
}
|
||||
if i < len(s) {
|
||||
c := s[i]
|
||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func isCtrlSeqStart(c uint8) bool {
|
||||
return c == '\\' || c == '[' || c == '(' || c == ')'
|
||||
}
|
||||
|
||||
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
|
||||
// calling FindStringIndex() on the below regex (which was originally used):
|
||||
//
|
||||
// "(?:\x1b[\\[()][0-9;?]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
|
||||
//
|
||||
func nextAnsiEscapeSequence(s string) (int, int) {
|
||||
// fast check for ANSI escape sequences
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '\x0e', '\x0f', '\x1b', '\x08':
|
||||
// We ignore the fact that '\x08' cannot be the first char
|
||||
// in the string and be an escape sequence for the sake of
|
||||
// speed and simplicity.
|
||||
goto Loop
|
||||
}
|
||||
}
|
||||
return -1, -1
|
||||
|
||||
Loop:
|
||||
for ; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '\x08':
|
||||
// backtrack to match: `.\x08`
|
||||
if i > 0 && s[i-1] != '\n' {
|
||||
if s[i-1] < utf8.RuneSelf {
|
||||
return i - 1, i + 1
|
||||
}
|
||||
_, n := utf8.DecodeLastRuneInString(s[:i])
|
||||
return i - n, i + 1
|
||||
}
|
||||
case '\x1b':
|
||||
// match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
||||
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
||||
if j := matchControlSequence(s[i:]); j != -1 {
|
||||
return i, i + j
|
||||
}
|
||||
}
|
||||
|
||||
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
||||
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
||||
s[i+3] == ';' && isPrint(s[i+4]) {
|
||||
|
||||
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
||||
return i, i + j
|
||||
}
|
||||
}
|
||||
|
||||
// match: `\x1b.`
|
||||
if i+1 < len(s) && s[i+1] != '\n' {
|
||||
if s[i+1] < utf8.RuneSelf {
|
||||
return i, i + 2
|
||||
}
|
||||
_, n := utf8.DecodeRuneInString(s[i+1:])
|
||||
return i, i + n + 1
|
||||
}
|
||||
case '\x0e', '\x0f':
|
||||
// match: `[\x0e\x0f]`
|
||||
return i, i + 1
|
||||
}
|
||||
}
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||
var offsets []ansiOffset
|
||||
var output bytes.Buffer
|
||||
// We append to a stack allocated variable that we'll
|
||||
// later copy and return, to save on allocations.
|
||||
offsets := make([]ansiOffset, 0, 32)
|
||||
|
||||
if state != nil {
|
||||
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
||||
}
|
||||
|
||||
prevIdx := 0
|
||||
runeCount := 0
|
||||
var (
|
||||
pstate *ansiState // lazily allocated
|
||||
output strings.Builder
|
||||
prevIdx int
|
||||
runeCount int
|
||||
)
|
||||
for idx := 0; idx < len(str); {
|
||||
idx += findAnsiStart(str[idx:])
|
||||
if idx == len(str) {
|
||||
// Make sure that we found an ANSI code
|
||||
start, end := nextAnsiEscapeSequence(str[idx:])
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
// Make sure that we found an ANSI code
|
||||
offset := ansiRegex.FindStringIndex(str[idx:])
|
||||
if len(offset) < 2 {
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
offset[0] += idx
|
||||
offset[1] += idx
|
||||
idx = offset[1]
|
||||
start += idx
|
||||
idx += end
|
||||
|
||||
// Check if we should continue
|
||||
prev := str[prevIdx:offset[0]]
|
||||
prev := str[prevIdx:start]
|
||||
if proc != nil && !proc(prev, state) {
|
||||
return "", nil, nil
|
||||
}
|
||||
prevIdx = idx
|
||||
|
||||
prevIdx = offset[1]
|
||||
runeCount += utf8.RuneCountInString(prev)
|
||||
output.WriteString(prev)
|
||||
if len(prev) != 0 {
|
||||
runeCount += utf8.RuneCountInString(prev)
|
||||
// Grow the buffer size to the maximum possible length (string length
|
||||
// containing ansi codes) to avoid repetitive allocation
|
||||
if output.Cap() == 0 {
|
||||
output.Grow(len(str))
|
||||
}
|
||||
output.WriteString(prev)
|
||||
}
|
||||
|
||||
newState := interpretCode(str[offset[0]:offset[1]], state)
|
||||
newState := interpretCode(str[start:idx], state)
|
||||
if !newState.equals(state) {
|
||||
if state != nil {
|
||||
// Update last offset
|
||||
@@ -156,8 +236,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
||||
|
||||
if newState.colored() {
|
||||
// Append new offset
|
||||
state = newState
|
||||
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
|
||||
if pstate == nil {
|
||||
pstate = &ansiState{}
|
||||
}
|
||||
*pstate = newState
|
||||
state = pstate
|
||||
offsets = append(offsets, ansiOffset{
|
||||
[2]int32{int32(runeCount), int32(runeCount)},
|
||||
newState,
|
||||
})
|
||||
} else {
|
||||
// Discard state
|
||||
state = nil
|
||||
@@ -167,7 +254,6 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
||||
|
||||
var rest string
|
||||
var trimmed string
|
||||
|
||||
if prevIdx == 0 {
|
||||
// No ANSI code found
|
||||
rest = str
|
||||
@@ -177,48 +263,75 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
||||
output.WriteString(rest)
|
||||
trimmed = output.String()
|
||||
}
|
||||
if len(rest) > 0 && state != nil {
|
||||
// Update last offset
|
||||
runeCount += utf8.RuneCountInString(rest)
|
||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||
}
|
||||
if proc != nil {
|
||||
proc(rest, state)
|
||||
}
|
||||
if len(offsets) == 0 {
|
||||
return trimmed, nil, state
|
||||
if len(offsets) > 0 {
|
||||
if len(rest) > 0 && state != nil {
|
||||
// Update last offset
|
||||
runeCount += utf8.RuneCountInString(rest)
|
||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||
}
|
||||
// Return a copy of the offsets slice
|
||||
a := make([]ansiOffset, len(offsets))
|
||||
copy(a, offsets)
|
||||
return trimmed, &a, state
|
||||
}
|
||||
return trimmed, &offsets, state
|
||||
return trimmed, nil, state
|
||||
}
|
||||
|
||||
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
// State
|
||||
var state *ansiState
|
||||
func parseAnsiCode(s string) (int, string) {
|
||||
var remaining string
|
||||
if i := strings.IndexByte(s, ';'); i >= 0 {
|
||||
remaining = s[i+1:]
|
||||
s = s[:i]
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
// Inlined version of strconv.Atoi() that only handles positive
|
||||
// integers and does not allocate on error.
|
||||
code := 0
|
||||
for _, ch := range []byte(s) {
|
||||
ch -= '0'
|
||||
if ch > 9 {
|
||||
return -1, remaining
|
||||
}
|
||||
code = code*10 + int(ch)
|
||||
}
|
||||
return code, remaining
|
||||
}
|
||||
|
||||
return -1, remaining
|
||||
}
|
||||
|
||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||
var state ansiState
|
||||
if prevState == nil {
|
||||
state = &ansiState{-1, -1, 0}
|
||||
state = ansiState{-1, -1, 0, -1}
|
||||
} else {
|
||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
||||
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
||||
}
|
||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||
state.lbg = prevState.bg
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
ptr := &state.fg
|
||||
state256 := 0
|
||||
|
||||
init := func() {
|
||||
if len(ansiCode) <= 3 {
|
||||
state.fg = -1
|
||||
state.bg = -1
|
||||
state.attr = 0
|
||||
state256 = 0
|
||||
return state
|
||||
}
|
||||
|
||||
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
||||
if len(ansiCode) == 0 {
|
||||
init()
|
||||
}
|
||||
for _, code := range strings.Split(ansiCode, ";") {
|
||||
if num, err := strconv.Atoi(code); err == nil {
|
||||
|
||||
state256 := 0
|
||||
ptr := &state.fg
|
||||
|
||||
for len(ansiCode) != 0 {
|
||||
var num int
|
||||
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
|
||||
switch state256 {
|
||||
case 0:
|
||||
switch num {
|
||||
@@ -249,7 +362,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
case 24: // tput rmul
|
||||
state.attr = state.attr &^ tui.Underline
|
||||
case 0:
|
||||
init()
|
||||
state.fg = -1
|
||||
state.bg = -1
|
||||
state.attr = 0
|
||||
state256 = 0
|
||||
default:
|
||||
if num >= 30 && num <= 37 {
|
||||
state.fg = tui.Color(num - 30)
|
||||
@@ -285,6 +401,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if state256 > 0 {
|
||||
*ptr = -1
|
||||
}
|
||||
|
245
src/ansi_test.go
245
src/ansi_test.go
@@ -2,12 +2,190 @@ package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
)
|
||||
|
||||
// The following regular expression will include not all but most of the
|
||||
// frequently used ANSI sequences. This regex is used as a reference for
|
||||
// testing nextAnsiEscapeSequence().
|
||||
//
|
||||
// References:
|
||||
// - https://github.com/gnachman/iTerm2
|
||||
// - http://ascii-table.com/ansi-escape-sequences.php
|
||||
// - http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
var ansiRegexRefence = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||
|
||||
func testParserReference(t testing.TB, str string) {
|
||||
t.Helper()
|
||||
|
||||
toSlice := func(start, end int) []int {
|
||||
if start == -1 {
|
||||
return nil
|
||||
}
|
||||
return []int{start, end}
|
||||
}
|
||||
|
||||
s := str
|
||||
for i := 0; ; i++ {
|
||||
got := toSlice(nextAnsiEscapeSequence(s))
|
||||
exp := ansiRegexRefence.FindStringIndex(s)
|
||||
|
||||
equal := len(got) == len(exp)
|
||||
if equal {
|
||||
for i := 0; i < len(got); i++ {
|
||||
if got[i] != exp[i] {
|
||||
equal = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !equal {
|
||||
var exps, gots []rune
|
||||
if len(got) == 2 {
|
||||
gots = []rune(s[got[0]:got[1]])
|
||||
}
|
||||
if len(exp) == 2 {
|
||||
exps = []rune(s[exp[0]:exp[1]])
|
||||
}
|
||||
t.Errorf("%d: %q: got: %v (%q) want: %v (%q)", i, s, got, gots, exp, exps)
|
||||
return
|
||||
}
|
||||
if len(exp) == 0 {
|
||||
return
|
||||
}
|
||||
s = s[exp[1]:]
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextAnsiEscapeSequence(t *testing.T) {
|
||||
testStrs := []string{
|
||||
"\x1b[0mhello world",
|
||||
"\x1b[1mhello world",
|
||||
"椙\x1b[1m椙",
|
||||
"椙\x1b[1椙m椙",
|
||||
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
||||
"\x1b[1mhello \x1b[Kworld",
|
||||
"hello \x1b[34;45;1mworld",
|
||||
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
||||
"hello \x1b[34;45;1mwor\x1b[0mld",
|
||||
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
||||
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
||||
"hello \x1b[32;1mworld",
|
||||
"hello world",
|
||||
"hello \x1b[0;38;5;200;48;5;100mworld",
|
||||
"\x1b椙",
|
||||
"椙\x08",
|
||||
"\n\x08",
|
||||
"X\x08",
|
||||
"",
|
||||
"\x1b]4;3;rgb:aa/bb/cc\x07 ",
|
||||
"\x1b]4;3;rgb:aa/bb/cc\x1b\\ ",
|
||||
ansiBenchmarkString,
|
||||
}
|
||||
|
||||
for _, s := range testStrs {
|
||||
testParserReference(t, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {
|
||||
t.Parallel()
|
||||
if testing.Short() {
|
||||
t.Skip("short test")
|
||||
}
|
||||
|
||||
testStrs := []string{
|
||||
"\x1b[0mhello world",
|
||||
"\x1b[1mhello world",
|
||||
"椙\x1b[1m椙",
|
||||
"椙\x1b[1椙m椙",
|
||||
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
||||
"\x1b[1mhello \x1b[Kworld",
|
||||
"hello \x1b[34;45;1mworld",
|
||||
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
||||
"hello \x1b[34;45;1mwor\x1b[0mld",
|
||||
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
||||
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
||||
"hello \x1b[32;1mworld",
|
||||
"hello world",
|
||||
"hello \x1b[0;38;5;200;48;5;100mworld",
|
||||
ansiBenchmarkString,
|
||||
}
|
||||
|
||||
replacementBytes := [...]rune{'\x0e', '\x0f', '\x1b', '\x08'}
|
||||
|
||||
modifyString := func(s string, rr *rand.Rand) string {
|
||||
n := rr.Intn(len(s))
|
||||
b := []rune(s)
|
||||
for ; n >= 0 && len(b) != 0; n-- {
|
||||
i := rr.Intn(len(b))
|
||||
switch x := rr.Intn(4); x {
|
||||
case 0:
|
||||
b = append(b[:i], b[i+1:]...)
|
||||
case 1:
|
||||
j := rr.Intn(len(replacementBytes) - 1)
|
||||
b[i] = replacementBytes[j]
|
||||
case 2:
|
||||
x := rune(rr.Intn(utf8.MaxRune))
|
||||
for !utf8.ValidRune(x) {
|
||||
x = rune(rr.Intn(utf8.MaxRune))
|
||||
}
|
||||
b[i] = x
|
||||
case 3:
|
||||
b[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid
|
||||
default:
|
||||
t.Fatalf("unsupported value: %d", x)
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
rr := rand.New(rand.NewSource(1))
|
||||
for _, s := range testStrs {
|
||||
for i := 1_000; i >= 0; i-- {
|
||||
testParserReference(t, modifyString(s, rr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("short test")
|
||||
}
|
||||
|
||||
randomString := func(rr *rand.Rand) string {
|
||||
numChars := rand.Intn(50)
|
||||
codePoints := make([]rune, numChars)
|
||||
for i := 0; i < len(codePoints); i++ {
|
||||
var r rune
|
||||
for n := 0; n < 1000; n++ {
|
||||
r = rune(rr.Intn(utf8.MaxRune))
|
||||
// Allow 10% of runes to be invalid
|
||||
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
codePoints[i] = r
|
||||
}
|
||||
return string(codePoints)
|
||||
}
|
||||
|
||||
rr := rand.New(rand.NewSource(1))
|
||||
for i := 0; i < 100_000; i++ {
|
||||
testParserReference(t, randomString(rr))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractColor(t *testing.T) {
|
||||
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
||||
var attr tui.Attr
|
||||
@@ -168,7 +346,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
assert("\x1b[m", nil, "")
|
||||
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
|
||||
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
|
||||
|
||||
assert("\x1b[31m", nil, "\x1b[31;49m")
|
||||
assert("\x1b[41m", nil, "\x1b[39;41m")
|
||||
@@ -176,8 +354,8 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
||||
assert("\x1b[92m", nil, "\x1b[92;49m")
|
||||
assert("\x1b[102m", nil, "\x1b[39;102m")
|
||||
|
||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
|
||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
|
||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
||||
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
||||
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
||||
@@ -185,3 +363,64 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
||||
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
||||
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
||||
}
|
||||
|
||||
func TestParseAnsiCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
In, Exp string
|
||||
N int
|
||||
}{
|
||||
{"123", "", 123},
|
||||
{"1a", "", -1},
|
||||
{"1a;12", "12", -1},
|
||||
{"12;a", "a", 12},
|
||||
{"-2", "", -1},
|
||||
}
|
||||
for _, x := range tests {
|
||||
n, s := parseAnsiCode(x.In)
|
||||
if n != x.N || s != x.Exp {
|
||||
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// kernel/bpf/preload/iterators/README
|
||||
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
|
||||
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
||||
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
|
||||
|
||||
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
||||
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
s := ansiBenchmarkString
|
||||
for {
|
||||
_, o := nextAnsiEscapeSequence(s)
|
||||
if o == -1 {
|
||||
break
|
||||
}
|
||||
s = s[o:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Baseline test to compare the speed of nextAnsiEscapeSequence() to the
|
||||
// previously used regex based implementation.
|
||||
func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
|
||||
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
s := ansiBenchmarkString
|
||||
for {
|
||||
a := ansiRegexRefence.FindStringIndex(s)
|
||||
if len(a) == 0 {
|
||||
break
|
||||
}
|
||||
s = s[a[1]:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExtractColor(b *testing.B) {
|
||||
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
extractColor(ansiBenchmarkString, nil, nil)
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,8 @@ func TestChunkCache(t *testing.T) {
|
||||
cache := NewChunkCache()
|
||||
chunk1p := &Chunk{}
|
||||
chunk2p := &Chunk{count: chunkSize}
|
||||
items1 := []Result{Result{}}
|
||||
items2 := []Result{Result{}, Result{}}
|
||||
items1 := []Result{{}}
|
||||
items2 := []Result{{}, {}}
|
||||
cache.Add(chunk1p, "foo", items1)
|
||||
cache.Add(chunk2p, "foo", items1)
|
||||
cache.Add(chunk2p, "bar", items2)
|
||||
|
@@ -73,6 +73,7 @@ const (
|
||||
EvtSearchFin
|
||||
EvtHeader
|
||||
EvtReady
|
||||
EvtQuit
|
||||
)
|
||||
|
||||
const (
|
||||
|
16
src/core.go
16
src/core.go
@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2013-2021 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -237,14 +237,16 @@ func Run(opts *Options, version string, revision string) {
|
||||
go reader.restart(command)
|
||||
}
|
||||
eventBox.Watch(EvtReadNew)
|
||||
query := []rune{}
|
||||
for {
|
||||
delay := true
|
||||
ticks++
|
||||
input := func() []rune {
|
||||
if opts.Phony {
|
||||
return []rune{}
|
||||
paused, input := terminal.Input()
|
||||
if !paused {
|
||||
query = input
|
||||
}
|
||||
return []rune(terminal.Input())
|
||||
return query
|
||||
}
|
||||
eventBox.Wait(func(events *util.Events) {
|
||||
if _, fin := (*events)[EvtReadFin]; fin {
|
||||
@@ -252,7 +254,11 @@ func Run(opts *Options, version string, revision string) {
|
||||
}
|
||||
for evt, value := range *events {
|
||||
switch evt {
|
||||
|
||||
case EvtQuit:
|
||||
if reading {
|
||||
reader.terminate()
|
||||
}
|
||||
os.Exit(value.(int))
|
||||
case EvtReadNew, EvtReadFin:
|
||||
if evt == EvtReadFin && nextCommand != nil {
|
||||
restart(*nextCommand)
|
||||
|
@@ -3,7 +3,6 @@ package fzf
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
@@ -12,16 +11,12 @@ func TestHistory(t *testing.T) {
|
||||
maxHistory := 50
|
||||
|
||||
// Invalid arguments
|
||||
user, _ := user.Current()
|
||||
var paths []string
|
||||
if runtime.GOOS == "windows" {
|
||||
// GOPATH should exist, so we shouldn't be able to override it
|
||||
paths = []string{os.Getenv("GOPATH")}
|
||||
} else {
|
||||
paths = []string{"/etc", "/proc"}
|
||||
if user.Name != "root" {
|
||||
paths = append(paths, "/etc/sudoers")
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
|
@@ -106,7 +106,6 @@ func (mg *Merger) mergedGet(idx int) Result {
|
||||
minIdx = listIdx
|
||||
}
|
||||
}
|
||||
mg.cursors[listIdx] = cursor
|
||||
}
|
||||
|
||||
if minIdx >= 0 {
|
||||
|
387
src/options.go
387
src/options.go
@@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/junegunn/fzf/src/algo"
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
@@ -34,7 +33,7 @@ const usage = `usage: fzf [options]
|
||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||
+s, --no-sort Do not sort the result
|
||||
--tac Reverse the order of the input
|
||||
--phony Do not perform search
|
||||
--disabled Do not perform search
|
||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||
when the scores are tied [length|begin|end|index]
|
||||
(default: length)
|
||||
@@ -59,7 +58,7 @@ const usage = `usage: fzf [options]
|
||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||
--border[=STYLE] Draw border around the finder
|
||||
[rounded|sharp|horizontal|vertical|
|
||||
top|bottom|left|right] (default: rounded)
|
||||
top|bottom|left|right|none] (default: rounded)
|
||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--info=STYLE Finder info style [default|inline|hidden]
|
||||
@@ -82,11 +81,11 @@ const usage = `usage: fzf [options]
|
||||
Preview
|
||||
--preview=COMMAND Command to preview highlighted line ({})
|
||||
--preview-window=OPT Preview window layout (default: right:50%)
|
||||
[up|down|left|right][:SIZE[%]]
|
||||
[:[no]wrap][:[no]cycle][:[no]hidden]
|
||||
[:rounded|sharp|noborder]
|
||||
[:+SCROLL[-OFFSET]]
|
||||
[:default]
|
||||
[up|down|left|right][,SIZE[%]]
|
||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
||||
[,border-BORDER_OPT]
|
||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||
[,default]
|
||||
|
||||
Scripting
|
||||
-q, --query=STR Start the finder with the given query
|
||||
@@ -162,14 +161,16 @@ const (
|
||||
)
|
||||
|
||||
type previewOpts struct {
|
||||
command string
|
||||
position windowPosition
|
||||
size sizeSpec
|
||||
scroll string
|
||||
hidden bool
|
||||
wrap bool
|
||||
cycle bool
|
||||
border tui.BorderShape
|
||||
command string
|
||||
position windowPosition
|
||||
size sizeSpec
|
||||
scroll string
|
||||
hidden bool
|
||||
wrap bool
|
||||
cycle bool
|
||||
follow bool
|
||||
border tui.BorderShape
|
||||
headerLines int
|
||||
}
|
||||
|
||||
// Options stores the values of command-line options
|
||||
@@ -210,8 +211,8 @@ type Options struct {
|
||||
Exit0 bool
|
||||
Filter *string
|
||||
ToggleSort bool
|
||||
Expect map[int]string
|
||||
Keymap map[int][]action
|
||||
Expect map[tui.Event]string
|
||||
Keymap map[tui.Event][]action
|
||||
Preview previewOpts
|
||||
PrintQuery bool
|
||||
ReadZero bool
|
||||
@@ -231,7 +232,7 @@ type Options struct {
|
||||
}
|
||||
|
||||
func defaultPreviewOpts(command string) previewOpts {
|
||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded}
|
||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
|
||||
}
|
||||
|
||||
func defaultOptions() *Options {
|
||||
@@ -271,8 +272,8 @@ func defaultOptions() *Options {
|
||||
Exit0: false,
|
||||
Filter: nil,
|
||||
ToggleSort: false,
|
||||
Expect: make(map[int]string),
|
||||
Keymap: make(map[int][]action),
|
||||
Expect: make(map[tui.Event]string),
|
||||
Keymap: make(map[tui.Event][]action),
|
||||
Preview: defaultPreviewOpts(""),
|
||||
PrintQuery: false,
|
||||
ReadZero: false,
|
||||
@@ -435,135 +436,152 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
||||
return tui.BorderLeft
|
||||
case "right":
|
||||
return tui.BorderRight
|
||||
case "none":
|
||||
return tui.BorderNone
|
||||
default:
|
||||
if optional && str == "" {
|
||||
return tui.BorderRounded
|
||||
}
|
||||
errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right)")
|
||||
errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right|none)")
|
||||
}
|
||||
return tui.BorderNone
|
||||
}
|
||||
|
||||
func parseKeyChords(str string, message string) map[int]string {
|
||||
func parseKeyChords(str string, message string) map[tui.Event]string {
|
||||
if len(str) == 0 {
|
||||
errorExit(message)
|
||||
}
|
||||
|
||||
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
|
||||
tokens := strings.Split(str, ",")
|
||||
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
||||
tokens = append(tokens, ",")
|
||||
}
|
||||
|
||||
chords := make(map[int]string)
|
||||
chords := make(map[tui.Event]string)
|
||||
for _, key := range tokens {
|
||||
if len(key) == 0 {
|
||||
continue // ignore
|
||||
}
|
||||
key = strings.ReplaceAll(key, string([]rune{escapedComma}), ",")
|
||||
lkey := strings.ToLower(key)
|
||||
chord := 0
|
||||
add := func(e tui.EventType) {
|
||||
chords[e.AsEvent()] = key
|
||||
}
|
||||
switch lkey {
|
||||
case "up":
|
||||
chord = tui.Up
|
||||
add(tui.Up)
|
||||
case "down":
|
||||
chord = tui.Down
|
||||
add(tui.Down)
|
||||
case "left":
|
||||
chord = tui.Left
|
||||
add(tui.Left)
|
||||
case "right":
|
||||
chord = tui.Right
|
||||
add(tui.Right)
|
||||
case "enter", "return":
|
||||
chord = tui.CtrlM
|
||||
add(tui.CtrlM)
|
||||
case "space":
|
||||
chord = tui.AltZ + int(' ')
|
||||
chords[tui.Key(' ')] = key
|
||||
case "bspace", "bs":
|
||||
chord = tui.BSpace
|
||||
add(tui.BSpace)
|
||||
case "ctrl-space":
|
||||
chord = tui.CtrlSpace
|
||||
add(tui.CtrlSpace)
|
||||
case "ctrl-^", "ctrl-6":
|
||||
chord = tui.CtrlCaret
|
||||
add(tui.CtrlCaret)
|
||||
case "ctrl-/", "ctrl-_":
|
||||
chord = tui.CtrlSlash
|
||||
add(tui.CtrlSlash)
|
||||
case "ctrl-\\":
|
||||
chord = tui.CtrlBackSlash
|
||||
add(tui.CtrlBackSlash)
|
||||
case "ctrl-]":
|
||||
chord = tui.CtrlRightBracket
|
||||
add(tui.CtrlRightBracket)
|
||||
case "change":
|
||||
chord = tui.Change
|
||||
add(tui.Change)
|
||||
case "backward-eof":
|
||||
chord = tui.BackwardEOF
|
||||
add(tui.BackwardEOF)
|
||||
case "alt-enter", "alt-return":
|
||||
chord = tui.CtrlAltM
|
||||
chords[tui.CtrlAltKey('m')] = key
|
||||
case "alt-space":
|
||||
chord = tui.AltSpace
|
||||
case "alt-/":
|
||||
chord = tui.AltSlash
|
||||
chords[tui.AltKey(' ')] = key
|
||||
case "alt-bs", "alt-bspace":
|
||||
chord = tui.AltBS
|
||||
add(tui.AltBS)
|
||||
case "alt-up":
|
||||
chord = tui.AltUp
|
||||
add(tui.AltUp)
|
||||
case "alt-down":
|
||||
chord = tui.AltDown
|
||||
add(tui.AltDown)
|
||||
case "alt-left":
|
||||
chord = tui.AltLeft
|
||||
add(tui.AltLeft)
|
||||
case "alt-right":
|
||||
chord = tui.AltRight
|
||||
add(tui.AltRight)
|
||||
case "tab":
|
||||
chord = tui.Tab
|
||||
add(tui.Tab)
|
||||
case "btab", "shift-tab":
|
||||
chord = tui.BTab
|
||||
add(tui.BTab)
|
||||
case "esc":
|
||||
chord = tui.ESC
|
||||
add(tui.ESC)
|
||||
case "del":
|
||||
chord = tui.Del
|
||||
add(tui.Del)
|
||||
case "home":
|
||||
chord = tui.Home
|
||||
add(tui.Home)
|
||||
case "end":
|
||||
chord = tui.End
|
||||
add(tui.End)
|
||||
case "insert":
|
||||
chord = tui.Insert
|
||||
add(tui.Insert)
|
||||
case "pgup", "page-up":
|
||||
chord = tui.PgUp
|
||||
add(tui.PgUp)
|
||||
case "pgdn", "page-down":
|
||||
chord = tui.PgDn
|
||||
add(tui.PgDn)
|
||||
case "alt-shift-up", "shift-alt-up":
|
||||
add(tui.AltSUp)
|
||||
case "alt-shift-down", "shift-alt-down":
|
||||
add(tui.AltSDown)
|
||||
case "alt-shift-left", "shift-alt-left":
|
||||
add(tui.AltSLeft)
|
||||
case "alt-shift-right", "shift-alt-right":
|
||||
add(tui.AltSRight)
|
||||
case "shift-up":
|
||||
chord = tui.SUp
|
||||
add(tui.SUp)
|
||||
case "shift-down":
|
||||
chord = tui.SDown
|
||||
add(tui.SDown)
|
||||
case "shift-left":
|
||||
chord = tui.SLeft
|
||||
add(tui.SLeft)
|
||||
case "shift-right":
|
||||
chord = tui.SRight
|
||||
add(tui.SRight)
|
||||
case "left-click":
|
||||
chord = tui.LeftClick
|
||||
add(tui.LeftClick)
|
||||
case "right-click":
|
||||
chord = tui.RightClick
|
||||
add(tui.RightClick)
|
||||
case "double-click":
|
||||
chord = tui.DoubleClick
|
||||
add(tui.DoubleClick)
|
||||
case "f10":
|
||||
chord = tui.F10
|
||||
add(tui.F10)
|
||||
case "f11":
|
||||
chord = tui.F11
|
||||
add(tui.F11)
|
||||
case "f12":
|
||||
chord = tui.F12
|
||||
add(tui.F12)
|
||||
default:
|
||||
runes := []rune(key)
|
||||
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
||||
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
|
||||
chords[tui.CtrlAltKey(rune(key[9]))] = key
|
||||
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
||||
chord = tui.AltA + int(lkey[4]) - 'a'
|
||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
|
||||
chord = tui.Alt0 + int(lkey[4]) - '0'
|
||||
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
|
||||
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
|
||||
r := runes[4]
|
||||
switch r {
|
||||
case escapedColon:
|
||||
r = ':'
|
||||
case escapedComma:
|
||||
r = ','
|
||||
case escapedPlus:
|
||||
r = '+'
|
||||
}
|
||||
chords[tui.AltKey(r)] = key
|
||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||
chord = tui.F1 + int(key[1]) - '1'
|
||||
} else if utf8.RuneCountInString(key) == 1 {
|
||||
chord = tui.AltZ + int([]rune(key)[0])
|
||||
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
|
||||
} else if len(runes) == 1 {
|
||||
chords[tui.Key(runes[0])] = key
|
||||
} else {
|
||||
errorExit("unsupported key: " + key)
|
||||
}
|
||||
}
|
||||
if chord > 0 {
|
||||
chords[chord] = key
|
||||
}
|
||||
}
|
||||
return chords
|
||||
}
|
||||
@@ -635,71 +653,74 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
fail()
|
||||
}
|
||||
|
||||
cattr := tui.NewColorAttr()
|
||||
for _, component := range components[1:] {
|
||||
switch component {
|
||||
case "regular":
|
||||
cattr.Attr = tui.AttrRegular
|
||||
case "bold", "strong":
|
||||
cattr.Attr |= tui.Bold
|
||||
case "dim":
|
||||
cattr.Attr |= tui.Dim
|
||||
case "italic":
|
||||
cattr.Attr |= tui.Italic
|
||||
case "underline":
|
||||
cattr.Attr |= tui.Underline
|
||||
case "blink":
|
||||
cattr.Attr |= tui.Blink
|
||||
case "reverse":
|
||||
cattr.Attr |= tui.Reverse
|
||||
case "":
|
||||
default:
|
||||
if rrggbb.MatchString(component) {
|
||||
cattr.Color = tui.HexToColor(component)
|
||||
} else {
|
||||
ansi32, err := strconv.Atoi(component)
|
||||
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
||||
fail()
|
||||
mergeAttr := func(cattr *tui.ColorAttr) {
|
||||
for _, component := range components[1:] {
|
||||
switch component {
|
||||
case "regular":
|
||||
cattr.Attr = tui.AttrRegular
|
||||
case "bold", "strong":
|
||||
cattr.Attr |= tui.Bold
|
||||
case "dim":
|
||||
cattr.Attr |= tui.Dim
|
||||
case "italic":
|
||||
cattr.Attr |= tui.Italic
|
||||
case "underline":
|
||||
cattr.Attr |= tui.Underline
|
||||
case "blink":
|
||||
cattr.Attr |= tui.Blink
|
||||
case "reverse":
|
||||
cattr.Attr |= tui.Reverse
|
||||
case "":
|
||||
default:
|
||||
if rrggbb.MatchString(component) {
|
||||
cattr.Color = tui.HexToColor(component)
|
||||
} else {
|
||||
ansi32, err := strconv.Atoi(component)
|
||||
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
||||
fail()
|
||||
}
|
||||
cattr.Color = tui.Color(ansi32)
|
||||
}
|
||||
cattr.Color = tui.Color(ansi32)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch components[0] {
|
||||
case "input":
|
||||
theme.Input = cattr
|
||||
case "query", "input":
|
||||
mergeAttr(&theme.Input)
|
||||
case "disabled":
|
||||
mergeAttr(&theme.Disabled)
|
||||
case "fg":
|
||||
theme.Fg = cattr
|
||||
mergeAttr(&theme.Fg)
|
||||
case "bg":
|
||||
theme.Bg = cattr
|
||||
mergeAttr(&theme.Bg)
|
||||
case "preview-fg":
|
||||
theme.PreviewFg = cattr
|
||||
mergeAttr(&theme.PreviewFg)
|
||||
case "preview-bg":
|
||||
theme.PreviewBg = cattr
|
||||
mergeAttr(&theme.PreviewBg)
|
||||
case "fg+":
|
||||
theme.Current = cattr
|
||||
mergeAttr(&theme.Current)
|
||||
case "bg+":
|
||||
theme.DarkBg = cattr
|
||||
mergeAttr(&theme.DarkBg)
|
||||
case "gutter":
|
||||
theme.Gutter = cattr
|
||||
mergeAttr(&theme.Gutter)
|
||||
case "hl":
|
||||
theme.Match = cattr
|
||||
mergeAttr(&theme.Match)
|
||||
case "hl+":
|
||||
theme.CurrentMatch = cattr
|
||||
mergeAttr(&theme.CurrentMatch)
|
||||
case "border":
|
||||
theme.Border = cattr
|
||||
mergeAttr(&theme.Border)
|
||||
case "prompt":
|
||||
theme.Prompt = cattr
|
||||
mergeAttr(&theme.Prompt)
|
||||
case "spinner":
|
||||
theme.Spinner = cattr
|
||||
mergeAttr(&theme.Spinner)
|
||||
case "info":
|
||||
theme.Info = cattr
|
||||
mergeAttr(&theme.Info)
|
||||
case "pointer":
|
||||
theme.Cursor = cattr
|
||||
mergeAttr(&theme.Cursor)
|
||||
case "marker":
|
||||
theme.Selected = cattr
|
||||
mergeAttr(&theme.Selected)
|
||||
case "header":
|
||||
theme.Header = cattr
|
||||
mergeAttr(&theme.Header)
|
||||
default:
|
||||
fail()
|
||||
}
|
||||
@@ -710,11 +731,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
|
||||
var executeRegexp *regexp.Regexp
|
||||
|
||||
func firstKey(keymap map[int]string) int {
|
||||
func firstKey(keymap map[tui.Event]string) tui.Event {
|
||||
for k := range keymap {
|
||||
return k
|
||||
}
|
||||
return 0
|
||||
return tui.EventType(0).AsEvent()
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -727,10 +748,10 @@ func init() {
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
}
|
||||
|
||||
func parseKeymap(keymap map[int][]action, str string) {
|
||||
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||
symbol := ":"
|
||||
if strings.HasPrefix(src, "+") {
|
||||
@@ -741,6 +762,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
prefix = symbol + "reload"
|
||||
} else if strings.HasPrefix(src[1:], "preview") {
|
||||
prefix = symbol + "preview"
|
||||
} else if strings.HasPrefix(src[1:], "unbind") {
|
||||
prefix = symbol + "unbind"
|
||||
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
||||
prefix = symbol + "change-prompt"
|
||||
} else if src[len(prefix)] == '-' {
|
||||
c := src[len(prefix)+1]
|
||||
if c == 's' || c == 'S' {
|
||||
@@ -764,13 +789,13 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
if len(pair) < 2 {
|
||||
errorExit("bind action not specified: " + origPairStr)
|
||||
}
|
||||
var key int
|
||||
var key tui.Event
|
||||
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
||||
key = ':' + tui.AltZ
|
||||
key = tui.Key(':')
|
||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
||||
key = ',' + tui.AltZ
|
||||
key = tui.Key(',')
|
||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
||||
key = '+' + tui.AltZ
|
||||
key = tui.Key('+')
|
||||
} else {
|
||||
keys := parseKeyChords(pair[0], "key name required")
|
||||
key = firstKey(keys)
|
||||
@@ -819,6 +844,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actDeleteChar)
|
||||
case "delete-char/eof":
|
||||
appendAction(actDeleteCharEOF)
|
||||
case "deselect":
|
||||
appendAction(actDeselect)
|
||||
case "end-of-line":
|
||||
appendAction(actEndOfLine)
|
||||
case "cancel":
|
||||
@@ -857,18 +884,26 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actToggleOut)
|
||||
case "toggle-all":
|
||||
appendAction(actToggleAll)
|
||||
case "toggle-search":
|
||||
appendAction(actToggleSearch)
|
||||
case "select":
|
||||
appendAction(actSelect)
|
||||
case "select-all":
|
||||
appendAction(actSelectAll)
|
||||
case "deselect-all":
|
||||
appendAction(actDeselectAll)
|
||||
case "close":
|
||||
appendAction(actClose)
|
||||
case "toggle":
|
||||
appendAction(actToggle)
|
||||
case "down":
|
||||
appendAction(actDown)
|
||||
case "up":
|
||||
appendAction(actUp)
|
||||
case "top":
|
||||
appendAction(actTop)
|
||||
case "first", "top":
|
||||
appendAction(actFirst)
|
||||
case "last":
|
||||
appendAction(actLast)
|
||||
case "page-up":
|
||||
appendAction(actPageUp)
|
||||
case "page-down":
|
||||
@@ -887,6 +922,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actTogglePreviewWrap)
|
||||
case "toggle-sort":
|
||||
appendAction(actToggleSort)
|
||||
case "preview-top":
|
||||
appendAction(actPreviewTop)
|
||||
case "preview-bottom":
|
||||
appendAction(actPreviewBottom)
|
||||
case "preview-up":
|
||||
appendAction(actPreviewUp)
|
||||
case "preview-down":
|
||||
@@ -899,6 +938,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actPreviewHalfPageUp)
|
||||
case "preview-half-page-down":
|
||||
appendAction(actPreviewHalfPageDown)
|
||||
case "enable-search":
|
||||
appendAction(actEnableSearch)
|
||||
case "disable-search":
|
||||
appendAction(actDisableSearch)
|
||||
default:
|
||||
t := isExecuteAction(specLower)
|
||||
if t == actIgnore {
|
||||
@@ -914,6 +957,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
offset = len("reload")
|
||||
case actPreview:
|
||||
offset = len("preview")
|
||||
case actChangePrompt:
|
||||
offset = len("change-prompt")
|
||||
case actUnbind:
|
||||
offset = len("unbind")
|
||||
case actExecuteSilent:
|
||||
offset = len("execute-silent")
|
||||
case actExecuteMulti:
|
||||
@@ -921,15 +968,21 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
default:
|
||||
offset = len("execute")
|
||||
}
|
||||
var actionArg string
|
||||
if spec[offset] == ':' {
|
||||
if specIndex == len(specs)-1 {
|
||||
actions = append(actions, action{t: t, a: spec[offset+1:]})
|
||||
actionArg = spec[offset+1:]
|
||||
actions = append(actions, action{t: t, a: actionArg})
|
||||
} else {
|
||||
prevSpec = spec + "+"
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
actions = append(actions, action{t: t, a: spec[offset+1 : len(spec)-1]})
|
||||
actionArg = spec[offset+1 : len(spec)-1]
|
||||
actions = append(actions, action{t: t, a: actionArg})
|
||||
}
|
||||
if t == actUnbind {
|
||||
parseKeyChords(actionArg, "unbind target required")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -951,8 +1004,12 @@ func isExecuteAction(str string) actionType {
|
||||
switch prefix {
|
||||
case "reload":
|
||||
return actReload
|
||||
case "unbind":
|
||||
return actUnbind
|
||||
case "preview":
|
||||
return actPreview
|
||||
case "change-prompt":
|
||||
return actChangePrompt
|
||||
case "execute":
|
||||
return actExecute
|
||||
case "execute-silent":
|
||||
@@ -963,7 +1020,7 @@ func isExecuteAction(str string) actionType {
|
||||
return actIgnore
|
||||
}
|
||||
|
||||
func parseToggleSort(keymap map[int][]action, str string) {
|
||||
func parseToggleSort(keymap map[tui.Event][]action, str string) {
|
||||
keys := parseKeyChords(str, "key name required")
|
||||
if len(keys) != 1 {
|
||||
errorExit("multiple keys specified")
|
||||
@@ -1033,9 +1090,11 @@ func parseInfoStyle(str string) infoStyle {
|
||||
}
|
||||
|
||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
tokens := strings.Split(input, ":")
|
||||
delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
|
||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$")
|
||||
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
||||
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||
tokens := delimRegex.Split(input, -1)
|
||||
for _, token := range tokens {
|
||||
switch token {
|
||||
case "":
|
||||
@@ -1061,17 +1120,35 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
opts.position = posLeft
|
||||
case "right":
|
||||
opts.position = posRight
|
||||
case "rounded", "border":
|
||||
case "rounded", "border", "border-rounded":
|
||||
opts.border = tui.BorderRounded
|
||||
case "sharp":
|
||||
case "sharp", "border-sharp":
|
||||
opts.border = tui.BorderSharp
|
||||
case "noborder":
|
||||
case "noborder", "border-none":
|
||||
opts.border = tui.BorderNone
|
||||
case "border-horizontal":
|
||||
opts.border = tui.BorderHorizontal
|
||||
case "border-vertical":
|
||||
opts.border = tui.BorderVertical
|
||||
case "border-top":
|
||||
opts.border = tui.BorderTop
|
||||
case "border-bottom":
|
||||
opts.border = tui.BorderBottom
|
||||
case "border-left":
|
||||
opts.border = tui.BorderLeft
|
||||
case "border-right":
|
||||
opts.border = tui.BorderRight
|
||||
case "follow":
|
||||
opts.follow = true
|
||||
case "nofollow":
|
||||
opts.follow = false
|
||||
default:
|
||||
if sizeRegex.MatchString(token) {
|
||||
if headerRegex.MatchString(token) {
|
||||
opts.headerLines = atoi(token[1:])
|
||||
} else if sizeRegex.MatchString(token) {
|
||||
opts.size = parseSize(token, 99, "window size")
|
||||
} else if offsetRegex.MatchString(token) {
|
||||
opts.scroll = token[1:]
|
||||
opts.scroll = token
|
||||
} else {
|
||||
errorExit("invalid preview window option: " + token)
|
||||
}
|
||||
@@ -1166,10 +1243,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Expect[k] = v
|
||||
}
|
||||
case "--no-expect":
|
||||
opts.Expect = make(map[int]string)
|
||||
case "--no-phony":
|
||||
opts.Expect = make(map[tui.Event]string)
|
||||
case "--enabled", "--no-phony":
|
||||
opts.Phony = false
|
||||
case "--phony":
|
||||
case "--disabled", "--phony":
|
||||
opts.Phony = true
|
||||
case "--tiebreak":
|
||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||
@@ -1318,7 +1395,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Preview.command = ""
|
||||
case "--preview-window":
|
||||
parsePreviewWindow(&opts.Preview,
|
||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
|
||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
|
||||
case "--height":
|
||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||
case "--min-height":
|
||||
@@ -1471,15 +1548,13 @@ func validateSign(sign string, signOptName string) error {
|
||||
if sign == "" {
|
||||
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||
}
|
||||
widthSum := 0
|
||||
for _, r := range sign {
|
||||
if !unicode.IsGraphic(r) {
|
||||
return fmt.Errorf("invalid character in %v", signOptName)
|
||||
}
|
||||
widthSum += runewidth.RuneWidth(r)
|
||||
if widthSum > 2 {
|
||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||
}
|
||||
}
|
||||
if runewidth.StringWidth(sign) > 2 {
|
||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1490,11 +1565,11 @@ func postProcessOptions(opts *Options) {
|
||||
}
|
||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||
if opts.History != nil {
|
||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
||||
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
|
||||
if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
|
||||
opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
|
||||
}
|
||||
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
||||
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
|
||||
if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
|
||||
opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -102,7 +102,7 @@ func TestIrrelevantNth(t *testing.T) {
|
||||
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{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
||||
{
|
||||
opts := defaultOptions()
|
||||
parseOptions(opts, words)
|
||||
@@ -125,26 +125,29 @@ func TestIrrelevantNth(t *testing.T) {
|
||||
|
||||
func TestParseKeys(t *testing.T) {
|
||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||
check := func(i int, s string) {
|
||||
if pairs[i] != s {
|
||||
t.Errorf("%s != %s", pairs[i], s)
|
||||
checkEvent := func(e tui.Event, s string) {
|
||||
if pairs[e] != s {
|
||||
t.Errorf("%s != %s", pairs[e], s)
|
||||
}
|
||||
}
|
||||
check := func(et tui.EventType, s string) {
|
||||
checkEvent(et.AsEvent(), s)
|
||||
}
|
||||
if len(pairs) != 12 {
|
||||
t.Error(12)
|
||||
}
|
||||
check(tui.CtrlZ, "ctrl-z")
|
||||
check(tui.AltZ, "alt-z")
|
||||
check(tui.F2, "f2")
|
||||
check(tui.AltZ+'@', "@")
|
||||
check(tui.AltA, "Alt-a")
|
||||
check(tui.AltZ+'!', "!")
|
||||
check(tui.CtrlA+'g'-'a', "ctrl-G")
|
||||
check(tui.AltZ+'J', "J")
|
||||
check(tui.AltZ+'g', "g")
|
||||
check(tui.CtrlAltA, "ctrl-alt-a")
|
||||
check(tui.CtrlAltM, "ALT-enter")
|
||||
check(tui.AltSpace, "alt-SPACE")
|
||||
check(tui.CtrlG, "ctrl-G")
|
||||
checkEvent(tui.AltKey('z'), "alt-z")
|
||||
checkEvent(tui.Key('@'), "@")
|
||||
checkEvent(tui.AltKey('a'), "Alt-a")
|
||||
checkEvent(tui.Key('!'), "!")
|
||||
checkEvent(tui.Key('J'), "J")
|
||||
checkEvent(tui.Key('g'), "g")
|
||||
checkEvent(tui.CtrlAltKey('a'), "ctrl-alt-a")
|
||||
checkEvent(tui.CtrlAltKey('m'), "ALT-enter")
|
||||
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||
|
||||
// Synonyms
|
||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||
@@ -152,7 +155,7 @@ func TestParseKeys(t *testing.T) {
|
||||
t.Error(9)
|
||||
}
|
||||
check(tui.CtrlM, "Return")
|
||||
check(tui.AltZ+' ', "space")
|
||||
checkEvent(tui.Key(' '), "space")
|
||||
check(tui.Tab, "tab")
|
||||
check(tui.BTab, "btab")
|
||||
check(tui.ESC, "esc")
|
||||
@@ -184,93 +187,98 @@ func TestParseKeysWithComma(t *testing.T) {
|
||||
t.Errorf("%d != %d", a, b)
|
||||
}
|
||||
}
|
||||
check := func(pairs map[int]string, i int, s string) {
|
||||
if pairs[i] != s {
|
||||
t.Errorf("%s != %s", pairs[i], s)
|
||||
check := func(pairs map[tui.Event]string, e tui.Event, s string) {
|
||||
if pairs[e] != s {
|
||||
t.Errorf("%s != %s", pairs[e], s)
|
||||
}
|
||||
}
|
||||
|
||||
pairs := parseKeyChords(",", "")
|
||||
checkN(len(pairs), 1)
|
||||
check(pairs, tui.AltZ+',', ",")
|
||||
check(pairs, tui.Key(','), ",")
|
||||
|
||||
pairs = parseKeyChords(",,a,b", "")
|
||||
checkN(len(pairs), 3)
|
||||
check(pairs, tui.AltZ+'a', "a")
|
||||
check(pairs, tui.AltZ+'b', "b")
|
||||
check(pairs, tui.AltZ+',', ",")
|
||||
check(pairs, tui.Key('a'), "a")
|
||||
check(pairs, tui.Key('b'), "b")
|
||||
check(pairs, tui.Key(','), ",")
|
||||
|
||||
pairs = parseKeyChords("a,b,,", "")
|
||||
checkN(len(pairs), 3)
|
||||
check(pairs, tui.AltZ+'a', "a")
|
||||
check(pairs, tui.AltZ+'b', "b")
|
||||
check(pairs, tui.AltZ+',', ",")
|
||||
check(pairs, tui.Key('a'), "a")
|
||||
check(pairs, tui.Key('b'), "b")
|
||||
check(pairs, tui.Key(','), ",")
|
||||
|
||||
pairs = parseKeyChords("a,,,b", "")
|
||||
checkN(len(pairs), 3)
|
||||
check(pairs, tui.AltZ+'a', "a")
|
||||
check(pairs, tui.AltZ+'b', "b")
|
||||
check(pairs, tui.AltZ+',', ",")
|
||||
check(pairs, tui.Key('a'), "a")
|
||||
check(pairs, tui.Key('b'), "b")
|
||||
check(pairs, tui.Key(','), ",")
|
||||
|
||||
pairs = parseKeyChords("a,,,b,c", "")
|
||||
checkN(len(pairs), 4)
|
||||
check(pairs, tui.AltZ+'a', "a")
|
||||
check(pairs, tui.AltZ+'b', "b")
|
||||
check(pairs, tui.AltZ+'c', "c")
|
||||
check(pairs, tui.AltZ+',', ",")
|
||||
check(pairs, tui.Key('a'), "a")
|
||||
check(pairs, tui.Key('b'), "b")
|
||||
check(pairs, tui.Key('c'), "c")
|
||||
check(pairs, tui.Key(','), ",")
|
||||
|
||||
pairs = parseKeyChords(",,,", "")
|
||||
checkN(len(pairs), 1)
|
||||
check(pairs, tui.AltZ+',', ",")
|
||||
check(pairs, tui.Key(','), ",")
|
||||
|
||||
pairs = parseKeyChords(",ALT-,,", "")
|
||||
checkN(len(pairs), 1)
|
||||
check(pairs, tui.AltKey(','), "ALT-,")
|
||||
}
|
||||
|
||||
func TestBind(t *testing.T) {
|
||||
keymap := defaultKeymap()
|
||||
check := func(keyName int, arg1 string, types ...actionType) {
|
||||
if len(keymap[keyName]) != len(types) {
|
||||
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
|
||||
check := func(event tui.Event, arg1 string, types ...actionType) {
|
||||
if len(keymap[event]) != len(types) {
|
||||
t.Errorf("invalid number of actions for %v (%d != %d)",
|
||||
event, len(types), len(keymap[event]))
|
||||
return
|
||||
}
|
||||
for idx, action := range keymap[keyName] {
|
||||
for idx, action := range keymap[event] {
|
||||
if types[idx] != action.t {
|
||||
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
||||
}
|
||||
}
|
||||
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
|
||||
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
|
||||
if len(arg1) > 0 && keymap[event][0].a != arg1 {
|
||||
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[event][0].a)
|
||||
}
|
||||
}
|
||||
check(tui.CtrlA, "", actBeginningOfLine)
|
||||
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
||||
parseKeymap(keymap,
|
||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||
",f1:+top,f1:+top"+
|
||||
",f1:+first,f1:+top"+
|
||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||
check(tui.CtrlA, "", actKillLine)
|
||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||
check(tui.AltZ+'c', "", actPageUp)
|
||||
check(tui.AltZ+',', "", actAbort)
|
||||
check(tui.AltZ+':', "", actAccept)
|
||||
check(tui.AltZ, "", actPageDown)
|
||||
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
|
||||
check(tui.F2, "echo {}, {}, {}", actExecute)
|
||||
check(tui.F3, "echo '({})'", actExecute)
|
||||
check(tui.F4, "less {}", actExecute)
|
||||
check(tui.AltZ+'x', "foo+bar", actExecute)
|
||||
check(tui.AltZ+'X', "bar+baz", actExecute)
|
||||
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
||||
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
|
||||
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
||||
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
||||
check(tui.Key('c'), "", actPageUp)
|
||||
check(tui.Key(','), "", actAbort)
|
||||
check(tui.Key(':'), "", actAccept)
|
||||
check(tui.AltKey('z'), "", actPageDown)
|
||||
check(tui.F1.AsEvent(), "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
|
||||
check(tui.F2.AsEvent(), "echo {}, {}, {}", actExecute)
|
||||
check(tui.F3.AsEvent(), "echo '({})'", actExecute)
|
||||
check(tui.F4.AsEvent(), "less {}", actExecute)
|
||||
check(tui.Key('x'), "foo+bar", actExecute)
|
||||
check(tui.Key('X'), "bar+baz", actExecute)
|
||||
check(tui.AltKey('a'), "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
||||
check(tui.AltKey('b'), "echo (,),[,],/,:,@,%,{}", actExecute)
|
||||
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||
|
||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||
}
|
||||
|
||||
parseKeymap(keymap, "f1:abort")
|
||||
check(tui.F1, "", actAbort)
|
||||
check(tui.F1.AsEvent(), "", actAbort)
|
||||
}
|
||||
|
||||
func TestColorSpec(t *testing.T) {
|
||||
@@ -314,11 +322,12 @@ func TestColorSpec(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultCtrlNP(t *testing.T) {
|
||||
check := func(words []string, key int, expected actionType) {
|
||||
check := func(words []string, et tui.EventType, expected actionType) {
|
||||
e := et.AsEvent()
|
||||
opts := defaultOptions()
|
||||
parseOptions(opts, words)
|
||||
postProcessOptions(opts)
|
||||
if opts.Keymap[key][0].t != expected {
|
||||
if opts.Keymap[e][0].t != expected {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
@@ -375,23 +384,23 @@ func TestPreviewOpts(t *testing.T) {
|
||||
opts.Preview.size.size == 50) {
|
||||
t.Error()
|
||||
}
|
||||
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap:+{1}-/2")
|
||||
opts = optsFor("--preview", "cat {}", "--preview-window=left:15,hidden,wrap:+{1}-/2")
|
||||
if !(opts.Preview.command == "cat {}" &&
|
||||
opts.Preview.hidden == true &&
|
||||
opts.Preview.wrap == true &&
|
||||
opts.Preview.position == posLeft &&
|
||||
opts.Preview.scroll == "{1}-/2" &&
|
||||
opts.Preview.scroll == "+{1}-/2" &&
|
||||
opts.Preview.size.percent == false &&
|
||||
opts.Preview.size.size == 15) {
|
||||
t.Error(opts.Preview)
|
||||
}
|
||||
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle")
|
||||
opts = optsFor("--preview-window=up,15,wrap,hidden,+{1}+3-1-2/2", "--preview-window=down", "--preview-window=cycle")
|
||||
if !(opts.Preview.command == "" &&
|
||||
opts.Preview.hidden == true &&
|
||||
opts.Preview.wrap == true &&
|
||||
opts.Preview.cycle == true &&
|
||||
opts.Preview.position == posDown &&
|
||||
opts.Preview.scroll == "{1}-/2" &&
|
||||
opts.Preview.scroll == "+{1}+3-1-2/2" &&
|
||||
opts.Preview.size.percent == false &&
|
||||
opts.Preview.size.size == 15) {
|
||||
t.Error(opts.Preview.size.size)
|
||||
|
@@ -337,7 +337,7 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
|
||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||
var input []Token
|
||||
if len(p.nth) == 0 {
|
||||
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||
input = []Token{{text: &item.text, prefixLength: 0}}
|
||||
} else {
|
||||
input = p.transformInput(item)
|
||||
}
|
||||
@@ -350,7 +350,7 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
|
||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||
var input []Token
|
||||
if len(p.nth) == 0 {
|
||||
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||
input = []Token{{text: &item.text, prefixLength: 0}}
|
||||
} else {
|
||||
input = p.transformInput(item)
|
||||
}
|
||||
|
@@ -131,7 +131,7 @@ func TestCaseSensitivity(t *testing.T) {
|
||||
func TestOrigTextAndTransformed(t *testing.T) {
|
||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||
tokens := Tokenize("junegunn", Delimiter{})
|
||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
||||
trans := Transform(tokens, []Range{{1, 1}})
|
||||
|
||||
origBytes := []byte("junegunn.choi")
|
||||
for _, extended := range []bool{false, true} {
|
||||
|
@@ -160,7 +160,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
||||
color := colMatch
|
||||
if curr < -1 && theme.Colored {
|
||||
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
|
||||
color = origColor.MergeNonDefault(color)
|
||||
// hl or hl+ only sets the foreground color, so colMatch is the
|
||||
// combination of either [hl and bg] or [hl+ and bg+].
|
||||
//
|
||||
// If the original text already has background color, and the
|
||||
// foreground color of colMatch is -1, we shouldn't only apply the
|
||||
// background color of colMatch.
|
||||
// e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
||||
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
||||
if color.Fg().IsDefault() && origColor.HasBg() {
|
||||
color = origColor
|
||||
} else {
|
||||
color = origColor.MergeNonDefault(color)
|
||||
}
|
||||
}
|
||||
colors = append(colors, colorOffset{
|
||||
offset: [2]int32{int32(start), int32(idx)}, color: color})
|
||||
|
@@ -18,8 +18,8 @@ func withIndex(i *Item, index int) *Item {
|
||||
|
||||
func TestOffsetSort(t *testing.T) {
|
||||
offsets := []Offset{
|
||||
Offset{3, 5}, Offset{2, 7},
|
||||
Offset{1, 3}, Offset{2, 9}}
|
||||
{3, 5}, {2, 7},
|
||||
{1, 3}, {2, 9}}
|
||||
sort.Sort(ByOrder(offsets))
|
||||
|
||||
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
||||
@@ -84,13 +84,13 @@ func TestResultRank(t *testing.T) {
|
||||
|
||||
// Sort by relevance
|
||||
item3 := buildResult(
|
||||
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
||||
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)
|
||||
item4 := buildResult(
|
||||
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
||||
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)
|
||||
item5 := buildResult(
|
||||
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
||||
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)
|
||||
item6 := buildResult(
|
||||
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
||||
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)
|
||||
items = []Result{item1, item2, item3, item4, item5, item6}
|
||||
sort.Sort(ByRelevance(items))
|
||||
if !(items[0] == item6 && items[1] == item5 &&
|
||||
@@ -109,10 +109,10 @@ func TestColorOffset(t *testing.T) {
|
||||
item := Result{
|
||||
item: &Item{
|
||||
colors: &[]ansiOffset{
|
||||
{[2]int32{0, 20}, ansiState{1, 5, 0}},
|
||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
|
||||
{[2]int32{30, 32}, ansiState{3, 7, 0}},
|
||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
|
||||
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
|
||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
|
||||
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
|
||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
|
||||
|
||||
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
||||
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
||||
|
634
src/terminal.go
634
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ type Delimiter struct {
|
||||
str *string
|
||||
}
|
||||
|
||||
// String returns the string representation of a Delimeter.
|
||||
// String returns the string representation of a Delimiter.
|
||||
func (d Delimiter) String() string {
|
||||
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
package tui
|
||||
|
||||
type Attr int
|
||||
type Attr int32
|
||||
|
||||
func HasFullscreenRenderer() bool {
|
||||
return false
|
||||
|
150
src/tui/light.go
150
src/tui/light.go
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -9,9 +10,10 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,7 +51,7 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
||||
}
|
||||
bytes = bytes[sz:]
|
||||
}
|
||||
r.queued += string(runes)
|
||||
r.queued.WriteString(string(runes))
|
||||
}
|
||||
|
||||
func (r *LightRenderer) csi(code string) {
|
||||
@@ -57,9 +59,9 @@ func (r *LightRenderer) csi(code string) {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) flush() {
|
||||
if len(r.queued) > 0 {
|
||||
fmt.Fprint(os.Stderr, r.queued)
|
||||
r.queued = ""
|
||||
if r.queued.Len() > 0 {
|
||||
fmt.Fprint(os.Stderr, r.queued.String())
|
||||
r.queued.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ type LightRenderer struct {
|
||||
clickY []int
|
||||
ttyin *os.File
|
||||
buffer []byte
|
||||
origState *terminal.State
|
||||
origState *term.State
|
||||
width int
|
||||
height int
|
||||
yoffset int
|
||||
@@ -81,7 +83,7 @@ type LightRenderer struct {
|
||||
escDelay int
|
||||
fullscreen bool
|
||||
upOneLine bool
|
||||
queued string
|
||||
queued strings.Builder
|
||||
y int
|
||||
x int
|
||||
maxHeightFunc func(int) int
|
||||
@@ -230,7 +232,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
}
|
||||
|
||||
retries := 0
|
||||
if c == ESC || nonblock {
|
||||
if c == ESC.Int() || nonblock {
|
||||
retries = r.escDelay / escPollInterval
|
||||
}
|
||||
buffer = append(buffer, byte(c))
|
||||
@@ -245,7 +247,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
continue
|
||||
}
|
||||
break
|
||||
} else if c == ESC && pc != c {
|
||||
} else if c == ESC.Int() && pc != c {
|
||||
retries = r.escDelay / escPollInterval
|
||||
} else {
|
||||
retries = 0
|
||||
@@ -278,11 +280,11 @@ func (r *LightRenderer) GetChar() Event {
|
||||
}()
|
||||
|
||||
switch r.buffer[0] {
|
||||
case CtrlC:
|
||||
case CtrlC.Byte():
|
||||
return Event{CtrlC, 0, nil}
|
||||
case CtrlG:
|
||||
case CtrlG.Byte():
|
||||
return Event{CtrlG, 0, nil}
|
||||
case CtrlQ:
|
||||
case CtrlQ.Byte():
|
||||
return Event{CtrlQ, 0, nil}
|
||||
case 127:
|
||||
return Event{BSpace, 0, nil}
|
||||
@@ -296,7 +298,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
return Event{CtrlCaret, 0, nil}
|
||||
case 31:
|
||||
return Event{CtrlSlash, 0, nil}
|
||||
case ESC:
|
||||
case ESC.Byte():
|
||||
ev := r.escSequence(&sz)
|
||||
// Second chance
|
||||
if ev.Type == Invalid {
|
||||
@@ -307,8 +309,8 @@ func (r *LightRenderer) GetChar() Event {
|
||||
}
|
||||
|
||||
// CTRL-A ~ CTRL-Z
|
||||
if r.buffer[0] <= CtrlZ {
|
||||
return Event{int(r.buffer[0]), 0, nil}
|
||||
if r.buffer[0] <= CtrlZ.Byte() {
|
||||
return Event{EventType(r.buffer[0]), 0, nil}
|
||||
}
|
||||
char, rsz := utf8.DecodeRune(r.buffer)
|
||||
if char == utf8.RuneError {
|
||||
@@ -331,26 +333,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
|
||||
*sz = 2
|
||||
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
||||
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
||||
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||
}
|
||||
alt := false
|
||||
if len(r.buffer) > 2 && r.buffer[1] == ESC {
|
||||
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
|
||||
r.buffer = r.buffer[1:]
|
||||
alt = true
|
||||
}
|
||||
switch r.buffer[1] {
|
||||
case ESC:
|
||||
case ESC.Byte():
|
||||
return Event{ESC, 0, nil}
|
||||
case ' ':
|
||||
return Event{AltSpace, 0, nil}
|
||||
case '/':
|
||||
return Event{AltSlash, 0, nil}
|
||||
case 'b':
|
||||
return Event{AltB, 0, nil}
|
||||
case 'd':
|
||||
return Event{AltD, 0, nil}
|
||||
case 'f':
|
||||
return Event{AltF, 0, nil}
|
||||
case 127:
|
||||
return Event{AltBS, 0, nil}
|
||||
case '[', 'O':
|
||||
@@ -463,20 +455,54 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case ';':
|
||||
if len(r.buffer) != 6 {
|
||||
if len(r.buffer) < 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch r.buffer[4] {
|
||||
case '2', '5':
|
||||
switch r.buffer[5] {
|
||||
case '1', '2', '3', '5':
|
||||
alt := r.buffer[4] == '3'
|
||||
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
|
||||
char := r.buffer[5]
|
||||
if altShift {
|
||||
if len(r.buffer) < 7 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 7
|
||||
char = r.buffer[6]
|
||||
}
|
||||
switch char {
|
||||
case 'A':
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
}
|
||||
return Event{SUp, 0, nil}
|
||||
case 'B':
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
}
|
||||
return Event{SDown, 0, nil}
|
||||
case 'C':
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
}
|
||||
return Event{SRight, 0, nil}
|
||||
case 'D':
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
}
|
||||
return Event{SLeft, 0, nil}
|
||||
}
|
||||
} // r.buffer[4]
|
||||
@@ -484,11 +510,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
} // r.buffer[2]
|
||||
} // r.buffer[2]
|
||||
} // r.buffer[1]
|
||||
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
||||
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
||||
}
|
||||
if r.buffer[1] >= '0' && r.buffer[1] <= '9' {
|
||||
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil}
|
||||
rest := bytes.NewBuffer(r.buffer[1:])
|
||||
c, size, err := rest.ReadRune()
|
||||
if err == nil {
|
||||
*sz = 1 + size
|
||||
return AltKey(c)
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
@@ -668,13 +694,17 @@ func (w *LightWindow) drawBorder() {
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
||||
color := ColBorder
|
||||
if w.preview {
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
if top {
|
||||
w.Move(0, 0)
|
||||
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
|
||||
w.CPrint(color, repeat(w.border.horizontal, w.width))
|
||||
}
|
||||
if bottom {
|
||||
w.Move(w.height-1, 0)
|
||||
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
|
||||
w.CPrint(color, repeat(w.border.horizontal, w.width))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,14 +713,18 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||
if !left || !right {
|
||||
width++
|
||||
}
|
||||
color := ColBorder
|
||||
if w.preview {
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
for y := 0; y < w.height; y++ {
|
||||
w.Move(y, 0)
|
||||
if left {
|
||||
w.CPrint(ColBorder, string(w.border.vertical))
|
||||
w.CPrint(color, string(w.border.vertical))
|
||||
}
|
||||
w.CPrint(ColBorder, repeat(' ', width))
|
||||
w.CPrint(color, repeat(' ', width))
|
||||
if right {
|
||||
w.CPrint(ColBorder, string(w.border.vertical))
|
||||
w.CPrint(color, string(w.border.vertical))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -856,20 +890,26 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
||||
lines := []wrappedLine{}
|
||||
width := 0
|
||||
line := ""
|
||||
for _, r := range input {
|
||||
w := util.RuneWidth(r, prefixLength+width, 8)
|
||||
width += w
|
||||
str := string(r)
|
||||
if r == '\t' {
|
||||
gr := uniseg.NewGraphemes(input)
|
||||
for gr.Next() {
|
||||
rs := gr.Runes()
|
||||
str := string(rs)
|
||||
var w int
|
||||
if len(rs) == 1 && rs[0] == '\t' {
|
||||
w = tabstop - (prefixLength+width)%tabstop
|
||||
str = repeat(' ', w)
|
||||
} else {
|
||||
w = runewidth.StringWidth(str)
|
||||
}
|
||||
width += w
|
||||
|
||||
if prefixLength+width <= max {
|
||||
line += str
|
||||
} else {
|
||||
lines = append(lines, wrappedLine{string(line), width - w})
|
||||
line = str
|
||||
prefixLength = 0
|
||||
width = util.RuneWidth(r, prefixLength, 8)
|
||||
width = w
|
||||
}
|
||||
}
|
||||
lines = append(lines, wrappedLine{string(line), width})
|
||||
@@ -881,12 +921,6 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
||||
for i, line := range allLines {
|
||||
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
||||
for j, wl := range lines {
|
||||
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
||||
if w.posy < w.height-1 {
|
||||
w.Move(w.posy+1, 0)
|
||||
}
|
||||
return FillNextLine
|
||||
}
|
||||
w.stderrInternal(wl.text, false)
|
||||
w.posx += wl.displayWidth
|
||||
|
||||
@@ -901,6 +935,14 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.posx+1 >= w.Width() {
|
||||
if w.posy+1 >= w.height {
|
||||
return FillSuspend
|
||||
}
|
||||
w.Move(w.posy+1, 0)
|
||||
onMove()
|
||||
return FillNextLine
|
||||
}
|
||||
return FillContinue
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func IsLightRendererSupported() bool {
|
||||
@@ -34,12 +34,12 @@ func (r *LightRenderer) fd() int {
|
||||
|
||||
func (r *LightRenderer) initPlatform() error {
|
||||
fd := r.fd()
|
||||
origState, err := terminal.GetState(fd)
|
||||
origState, err := term.GetState(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.origState = origState
|
||||
terminal.MakeRaw(fd)
|
||||
term.MakeRaw(fd)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -63,15 +63,15 @@ func openTtyIn() *os.File {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) setupTerminal() {
|
||||
terminal.MakeRaw(r.fd())
|
||||
term.MakeRaw(r.fd())
|
||||
}
|
||||
|
||||
func (r *LightRenderer) restoreTerminal() {
|
||||
terminal.Restore(r.fd(), r.origState)
|
||||
term.Restore(r.fd(), r.origState)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) updateTerminalSize() {
|
||||
width, height, err := terminal.GetSize(r.fd())
|
||||
width, height, err := term.GetSize(r.fd())
|
||||
|
||||
if err == nil {
|
||||
r.width = width
|
||||
|
@@ -5,11 +5,16 @@ package tui
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
timeoutInterval = 10
|
||||
)
|
||||
|
||||
var (
|
||||
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||
@@ -60,7 +65,7 @@ func (r *LightRenderer) initPlatform() error {
|
||||
|
||||
// channel for non-blocking reads. Buffer to make sure
|
||||
// we get the ESC sets:
|
||||
r.ttyinChannel = make(chan byte, 12)
|
||||
r.ttyinChannel = make(chan byte, 1024)
|
||||
|
||||
// the following allows for non-blocking IO.
|
||||
// syscall.SetNonblock() is a NOOP under Windows.
|
||||
@@ -68,9 +73,6 @@ func (r *LightRenderer) initPlatform() error {
|
||||
fd := int(r.inHandle)
|
||||
b := make([]byte, 1)
|
||||
for {
|
||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||
|
||||
_, err := util.Read(fd, b)
|
||||
if err == nil {
|
||||
r.ttyinChannel <- b[0]
|
||||
@@ -130,7 +132,7 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||
select {
|
||||
case bc := <-r.ttyinChannel:
|
||||
return int(bc), true
|
||||
default:
|
||||
case <-time.After(timeoutInterval * time.Millisecond):
|
||||
return 0, false
|
||||
}
|
||||
} else {
|
||||
|
186
src/tui/tcell.go
186
src/tui/tcell.go
@@ -5,7 +5,6 @@ package tui
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"runtime"
|
||||
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"github.com/gdamore/tcell/encoding"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
func HasFullscreenRenderer() bool {
|
||||
@@ -222,66 +222,69 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
|
||||
// process keyboard:
|
||||
case *tcell.EventKey:
|
||||
alt := (ev.Modifiers() & tcell.ModAlt) > 0
|
||||
keyfn := func(r rune) int {
|
||||
mods := ev.Modifiers()
|
||||
alt := (mods & tcell.ModAlt) > 0
|
||||
shift := (mods & tcell.ModShift) > 0
|
||||
altShift := alt && shift
|
||||
keyfn := func(r rune) Event {
|
||||
if alt {
|
||||
return CtrlAltA - 'a' + int(r)
|
||||
return CtrlAltKey(r)
|
||||
}
|
||||
return CtrlA - 'a' + int(r)
|
||||
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
|
||||
}
|
||||
switch ev.Key() {
|
||||
case tcell.KeyCtrlA:
|
||||
return Event{keyfn('a'), 0, nil}
|
||||
return keyfn('a')
|
||||
case tcell.KeyCtrlB:
|
||||
return Event{keyfn('b'), 0, nil}
|
||||
return keyfn('b')
|
||||
case tcell.KeyCtrlC:
|
||||
return Event{keyfn('c'), 0, nil}
|
||||
return keyfn('c')
|
||||
case tcell.KeyCtrlD:
|
||||
return Event{keyfn('d'), 0, nil}
|
||||
return keyfn('d')
|
||||
case tcell.KeyCtrlE:
|
||||
return Event{keyfn('e'), 0, nil}
|
||||
return keyfn('e')
|
||||
case tcell.KeyCtrlF:
|
||||
return Event{keyfn('f'), 0, nil}
|
||||
return keyfn('f')
|
||||
case tcell.KeyCtrlG:
|
||||
return Event{keyfn('g'), 0, nil}
|
||||
return keyfn('g')
|
||||
case tcell.KeyCtrlH:
|
||||
return Event{keyfn('h'), 0, nil}
|
||||
return keyfn('h')
|
||||
case tcell.KeyCtrlI:
|
||||
return Event{keyfn('i'), 0, nil}
|
||||
return keyfn('i')
|
||||
case tcell.KeyCtrlJ:
|
||||
return Event{keyfn('j'), 0, nil}
|
||||
return keyfn('j')
|
||||
case tcell.KeyCtrlK:
|
||||
return Event{keyfn('k'), 0, nil}
|
||||
return keyfn('k')
|
||||
case tcell.KeyCtrlL:
|
||||
return Event{keyfn('l'), 0, nil}
|
||||
return keyfn('l')
|
||||
case tcell.KeyCtrlM:
|
||||
return Event{keyfn('m'), 0, nil}
|
||||
return keyfn('m')
|
||||
case tcell.KeyCtrlN:
|
||||
return Event{keyfn('n'), 0, nil}
|
||||
return keyfn('n')
|
||||
case tcell.KeyCtrlO:
|
||||
return Event{keyfn('o'), 0, nil}
|
||||
return keyfn('o')
|
||||
case tcell.KeyCtrlP:
|
||||
return Event{keyfn('p'), 0, nil}
|
||||
return keyfn('p')
|
||||
case tcell.KeyCtrlQ:
|
||||
return Event{keyfn('q'), 0, nil}
|
||||
return keyfn('q')
|
||||
case tcell.KeyCtrlR:
|
||||
return Event{keyfn('r'), 0, nil}
|
||||
return keyfn('r')
|
||||
case tcell.KeyCtrlS:
|
||||
return Event{keyfn('s'), 0, nil}
|
||||
return keyfn('s')
|
||||
case tcell.KeyCtrlT:
|
||||
return Event{keyfn('t'), 0, nil}
|
||||
return keyfn('t')
|
||||
case tcell.KeyCtrlU:
|
||||
return Event{keyfn('u'), 0, nil}
|
||||
return keyfn('u')
|
||||
case tcell.KeyCtrlV:
|
||||
return Event{keyfn('v'), 0, nil}
|
||||
return keyfn('v')
|
||||
case tcell.KeyCtrlW:
|
||||
return Event{keyfn('w'), 0, nil}
|
||||
return keyfn('w')
|
||||
case tcell.KeyCtrlX:
|
||||
return Event{keyfn('x'), 0, nil}
|
||||
return keyfn('x')
|
||||
case tcell.KeyCtrlY:
|
||||
return Event{keyfn('y'), 0, nil}
|
||||
return keyfn('y')
|
||||
case tcell.KeyCtrlZ:
|
||||
return Event{keyfn('z'), 0, nil}
|
||||
return keyfn('z')
|
||||
case tcell.KeyCtrlSpace:
|
||||
return Event{CtrlSpace, 0, nil}
|
||||
case tcell.KeyCtrlBackslash:
|
||||
@@ -297,21 +300,45 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{BSpace, 0, nil}
|
||||
|
||||
case tcell.KeyUp:
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SUp, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
return Event{Up, 0, nil}
|
||||
case tcell.KeyDown:
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SDown, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
return Event{Down, 0, nil}
|
||||
case tcell.KeyLeft:
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SLeft, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
return Event{Left, 0, nil}
|
||||
case tcell.KeyRight:
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SRight, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
@@ -362,18 +389,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
case tcell.KeyRune:
|
||||
r := ev.Rune()
|
||||
if alt {
|
||||
switch r {
|
||||
case ' ':
|
||||
return Event{AltSpace, 0, nil}
|
||||
case '/':
|
||||
return Event{AltSlash, 0, nil}
|
||||
}
|
||||
if r >= 'a' && r <= 'z' {
|
||||
return Event{AltA + int(r) - 'a', 0, nil}
|
||||
}
|
||||
if r >= '0' && r <= '9' {
|
||||
return Event{Alt0 + int(r) - '0', 0, nil}
|
||||
}
|
||||
return AltKey(r)
|
||||
}
|
||||
return Event{Rune, r, nil}
|
||||
|
||||
@@ -466,7 +482,6 @@ func (w *TcellWindow) Print(text string) {
|
||||
}
|
||||
|
||||
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
||||
t := text
|
||||
lx := 0
|
||||
a := pair.Attr()
|
||||
|
||||
@@ -480,33 +495,28 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
||||
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||
}
|
||||
|
||||
for {
|
||||
if len(t) == 0 {
|
||||
break
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(t)
|
||||
t = t[size:]
|
||||
gr := uniseg.NewGraphemes(text)
|
||||
for gr.Next() {
|
||||
rs := gr.Runes()
|
||||
|
||||
if r < rune(' ') { // ignore control characters
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\n' {
|
||||
w.lastY++
|
||||
lx = 0
|
||||
} else {
|
||||
|
||||
if r == '\u000D' { // skip carriage return
|
||||
if len(rs) == 1 {
|
||||
r := rs[0]
|
||||
if r < rune(' ') { // ignore control characters
|
||||
continue
|
||||
} else if r == '\n' {
|
||||
w.lastY++
|
||||
lx = 0
|
||||
continue
|
||||
} else if r == '\u000D' { // skip carriage return
|
||||
continue
|
||||
}
|
||||
|
||||
var xPos = w.left + w.lastX + lx
|
||||
var yPos = w.top + w.lastY
|
||||
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
|
||||
_screen.SetContent(xPos, yPos, r, nil, style)
|
||||
}
|
||||
lx += runewidth.RuneWidth(r)
|
||||
}
|
||||
var xPos = w.left + w.lastX + lx
|
||||
var yPos = w.top + w.lastY
|
||||
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
|
||||
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
|
||||
}
|
||||
lx += runewidth.StringWidth(string(rs))
|
||||
}
|
||||
w.lastX += lx
|
||||
}
|
||||
@@ -533,30 +543,32 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
||||
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
||||
Italic(a&Attr(tcell.AttrItalic) != 0)
|
||||
|
||||
for _, r := range text {
|
||||
if r == '\n' {
|
||||
gr := uniseg.NewGraphemes(text)
|
||||
for gr.Next() {
|
||||
rs := gr.Runes()
|
||||
if len(rs) == 1 && rs[0] == '\n' {
|
||||
w.lastY++
|
||||
w.lastX = 0
|
||||
lx = 0
|
||||
} else {
|
||||
var xPos = w.left + w.lastX + lx
|
||||
|
||||
// word wrap:
|
||||
if xPos >= (w.left + w.width) {
|
||||
w.lastY++
|
||||
w.lastX = 0
|
||||
lx = 0
|
||||
xPos = w.left
|
||||
}
|
||||
var yPos = w.top + w.lastY
|
||||
|
||||
if yPos >= (w.top + w.height) {
|
||||
return FillSuspend
|
||||
}
|
||||
|
||||
_screen.SetContent(xPos, yPos, r, nil, style)
|
||||
lx += runewidth.RuneWidth(r)
|
||||
continue
|
||||
}
|
||||
|
||||
// word wrap:
|
||||
xPos := w.left + w.lastX + lx
|
||||
if xPos >= (w.left + w.width) {
|
||||
w.lastY++
|
||||
w.lastX = 0
|
||||
lx = 0
|
||||
xPos = w.left
|
||||
}
|
||||
|
||||
yPos := w.top + w.lastY
|
||||
if yPos >= (w.top + w.height) {
|
||||
return FillSuspend
|
||||
}
|
||||
|
||||
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
|
||||
lx += runewidth.StringWidth(string(rs))
|
||||
}
|
||||
w.lastX += lx
|
||||
if w.lastX == w.width {
|
||||
|
@@ -4,6 +4,7 @@ package tui
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -29,3 +30,18 @@ func ttyname() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TtyIn returns terminal device to be used as STDIN, falls back to os.Stdin
|
||||
func TtyIn() *os.File {
|
||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
tty := ttyname()
|
||||
if len(tty) > 0 {
|
||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||
return in
|
||||
}
|
||||
}
|
||||
return os.Stdin
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
@@ -2,6 +2,13 @@
|
||||
|
||||
package tui
|
||||
|
||||
import "os"
|
||||
|
||||
func ttyname() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// TtyIn on Windows returns os.Stdin
|
||||
func TtyIn() *os.File {
|
||||
return os.Stdin
|
||||
}
|
||||
|
@@ -8,8 +8,10 @@ import (
|
||||
)
|
||||
|
||||
// Types of user action
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
Rune = iota
|
||||
Rune EventType = iota
|
||||
|
||||
CtrlA
|
||||
CtrlB
|
||||
@@ -89,8 +91,6 @@ const (
|
||||
Change
|
||||
BackwardEOF
|
||||
|
||||
AltSpace
|
||||
AltSlash
|
||||
AltBS
|
||||
|
||||
AltUp
|
||||
@@ -98,20 +98,43 @@ const (
|
||||
AltLeft
|
||||
AltRight
|
||||
|
||||
Alt0
|
||||
AltSUp
|
||||
AltSDown
|
||||
AltSLeft
|
||||
AltSRight
|
||||
|
||||
Alt
|
||||
CtrlAlt
|
||||
)
|
||||
|
||||
const ( // Reset iota
|
||||
AltA = Alt0 + 'a' - '0' + iota
|
||||
AltB
|
||||
AltC
|
||||
AltD
|
||||
AltE
|
||||
AltF
|
||||
AltZ = AltA + 'z' - 'a'
|
||||
CtrlAltA = AltZ + 1
|
||||
CtrlAltM = CtrlAltA + 'm' - 'a'
|
||||
)
|
||||
func (t EventType) AsEvent() Event {
|
||||
return Event{t, 0, nil}
|
||||
}
|
||||
|
||||
func (t EventType) Int() int {
|
||||
return int(t)
|
||||
}
|
||||
|
||||
func (t EventType) Byte() byte {
|
||||
return byte(t)
|
||||
}
|
||||
|
||||
func (e Event) Comparable() Event {
|
||||
// Ignore MouseEvent pointer
|
||||
return Event{e.Type, e.Char, nil}
|
||||
}
|
||||
|
||||
func Key(r rune) Event {
|
||||
return Event{Rune, r, nil}
|
||||
}
|
||||
|
||||
func AltKey(r rune) Event {
|
||||
return Event{Alt, r, nil}
|
||||
}
|
||||
|
||||
func CtrlAltKey(r rune) Event {
|
||||
return Event{CtrlAlt, r, nil}
|
||||
}
|
||||
|
||||
const (
|
||||
doubleClickDuration = 500 * time.Millisecond
|
||||
@@ -119,6 +142,10 @@ const (
|
||||
|
||||
type Color int32
|
||||
|
||||
func (c Color) IsDefault() bool {
|
||||
return c == colDefault
|
||||
}
|
||||
|
||||
func (c Color) is24() bool {
|
||||
return c > 0 && (c&(1<<24)) > 0
|
||||
}
|
||||
@@ -185,6 +212,11 @@ func (p ColorPair) Attr() Attr {
|
||||
return p.attr
|
||||
}
|
||||
|
||||
func (p ColorPair) HasBg() bool {
|
||||
return p.attr&Reverse == 0 && p.bg != colDefault ||
|
||||
p.attr&Reverse > 0 && p.fg != colDefault
|
||||
}
|
||||
|
||||
func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
|
||||
dup := p
|
||||
dup.attr = dup.attr.Merge(other.attr)
|
||||
@@ -218,6 +250,7 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
|
||||
type ColorTheme struct {
|
||||
Colored bool
|
||||
Input ColorAttr
|
||||
Disabled ColorAttr
|
||||
Fg ColorAttr
|
||||
Bg ColorAttr
|
||||
PreviewFg ColorAttr
|
||||
@@ -237,7 +270,7 @@ type ColorTheme struct {
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type int
|
||||
Type EventType
|
||||
Char rune
|
||||
MouseEvent *MouseEvent
|
||||
}
|
||||
@@ -389,6 +422,7 @@ var (
|
||||
ColPrompt ColorPair
|
||||
ColNormal ColorPair
|
||||
ColInput ColorPair
|
||||
ColDisabled ColorPair
|
||||
ColMatch ColorPair
|
||||
ColCursor ColorPair
|
||||
ColCursorEmpty ColorPair
|
||||
@@ -411,6 +445,7 @@ func EmptyTheme() *ColorTheme {
|
||||
return &ColorTheme{
|
||||
Colored: true,
|
||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
@@ -433,6 +468,7 @@ func NoColorTheme() *ColorTheme {
|
||||
return &ColorTheme{
|
||||
Colored: false,
|
||||
Input: ColorAttr{colDefault, AttrRegular},
|
||||
Disabled: ColorAttr{colDefault, AttrRegular},
|
||||
Fg: ColorAttr{colDefault, AttrRegular},
|
||||
Bg: ColorAttr{colDefault, AttrRegular},
|
||||
PreviewFg: ColorAttr{colDefault, AttrRegular},
|
||||
@@ -460,6 +496,7 @@ func init() {
|
||||
Default16 = &ColorTheme{
|
||||
Colored: true,
|
||||
Input: ColorAttr{colDefault, AttrUndefined},
|
||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
@@ -479,6 +516,7 @@ func init() {
|
||||
Dark256 = &ColorTheme{
|
||||
Colored: true,
|
||||
Input: ColorAttr{colDefault, AttrUndefined},
|
||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
@@ -498,6 +536,7 @@ func init() {
|
||||
Light256 = &ColorTheme{
|
||||
Colored: true,
|
||||
Input: ColorAttr{colDefault, AttrUndefined},
|
||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
@@ -532,6 +571,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||
return c
|
||||
}
|
||||
theme.Input = o(baseTheme.Input, theme.Input)
|
||||
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
|
||||
@@ -565,6 +605,7 @@ func initPalette(theme *ColorTheme) {
|
||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||
ColNormal = pair(theme.Fg, theme.Bg)
|
||||
ColInput = pair(theme.Input, theme.Bg)
|
||||
ColDisabled = pair(theme.Disabled, theme.Bg)
|
||||
ColMatch = pair(theme.Match, theme.Bg)
|
||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||
|
@@ -7,22 +7,29 @@ import (
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
var _runeWidths = make(map[rune]int)
|
||||
|
||||
// RuneWidth returns rune width
|
||||
func RuneWidth(r rune, prefixWidth int, tabstop int) int {
|
||||
if r == '\t' {
|
||||
return tabstop - prefixWidth%tabstop
|
||||
} else if w, found := _runeWidths[r]; found {
|
||||
return w
|
||||
} else if r == '\n' || r == '\r' {
|
||||
return 1
|
||||
// RunesWidth returns runes width
|
||||
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
|
||||
width := 0
|
||||
gr := uniseg.NewGraphemes(string(runes))
|
||||
idx := 0
|
||||
for gr.Next() {
|
||||
rs := gr.Runes()
|
||||
var w int
|
||||
if len(rs) == 1 && rs[0] == '\t' {
|
||||
w = tabstop - (prefixWidth+width)%tabstop
|
||||
} else {
|
||||
w = runewidth.StringWidth(string(rs))
|
||||
}
|
||||
width += w
|
||||
if limit > 0 && width > limit {
|
||||
return width, idx
|
||||
}
|
||||
idx += len(rs)
|
||||
}
|
||||
w := runewidth.RuneWidth(r)
|
||||
_runeWidths[r] = w
|
||||
return w
|
||||
return width, -1
|
||||
}
|
||||
|
||||
// Max returns the largest integer
|
||||
|
248
test/test_go.rb
248
test/test_go.rb
@@ -148,14 +148,15 @@ class Tmux
|
||||
def prepare
|
||||
tries = 0
|
||||
begin
|
||||
self.until do |lines|
|
||||
send_keys ' ', 'C-u', :Enter, 'hello', :Left, :Right
|
||||
lines[-1] == 'hello'
|
||||
self.until(true) do |lines|
|
||||
message = "Prepare[#{tries}]"
|
||||
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
|
||||
lines[-1] == message
|
||||
end
|
||||
rescue Minitest::Assertion
|
||||
(tries += 1) < 5 ? retry : raise
|
||||
end
|
||||
send_keys 'C-u'
|
||||
send_keys 'C-u', 'C-l'
|
||||
end
|
||||
|
||||
private
|
||||
@@ -1554,8 +1555,8 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
||||
end
|
||||
|
||||
def test_change_top
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
||||
def test_change_first_last
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_equal '> 2', lines[-4] }
|
||||
@@ -1565,6 +1566,10 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_equal '> 10', lines[-4] }
|
||||
tmux.send_keys 1
|
||||
tmux.until { |lines| assert_equal '> 11', lines[-3] }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.until { |lines| assert_equal '> 1', lines[-3] }
|
||||
tmux.send_keys :Escape, 'Z'
|
||||
tmux.until { |lines| assert_equal '> 1000', lines[0] }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
@@ -1654,13 +1659,35 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
||||
end
|
||||
|
||||
def test_phony
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
|
||||
def test_disabled
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}'), :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }
|
||||
tmux.send_keys 'foo'
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }
|
||||
|
||||
# Already disabled, no change
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
|
||||
# Enable search
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.send_keys :BSpace, :BSpace, :BSpace
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' 333 333 ' }
|
||||
|
||||
# Toggle search -> disabled again, but retains the previous result
|
||||
tmux.send_keys 'c'
|
||||
tmux.send_keys 'foo'
|
||||
tmux.until { |lines| assert_includes lines[1], ' 333 333foo ' }
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
|
||||
# Enabled, no match
|
||||
tmux.send_keys 'c'
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' 333foo ' }
|
||||
end
|
||||
|
||||
def test_reload
|
||||
@@ -1823,6 +1850,209 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| lines.item_count == 100 }
|
||||
tmux.until { |lines| lines[1]&.include?('[200]') }
|
||||
end
|
||||
|
||||
def test_change_prompt
|
||||
tmux.send_keys "#{FZF} --bind 'a:change-prompt(a> ),b:change-prompt:b> ' --query foo", :Enter
|
||||
tmux.until { |lines| assert_equal '> foo', lines[-1] }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal 'a> foo', lines[-1] }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_equal 'b> foo', lines[-1] }
|
||||
end
|
||||
|
||||
def test_preview_window_follow
|
||||
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
|
||||
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
|
||||
end
|
||||
|
||||
def test_toggle_preview_wrap
|
||||
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo wrapped; echo 2nd line' --bind ctrl-w:toggle-preview-wrap", :Enter
|
||||
2.times do
|
||||
tmux.until { |lines| assert_includes lines[2], '2nd line' }
|
||||
tmux.send_keys 'C-w'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[2], 'wrapped'
|
||||
assert_includes lines[3], '2nd line'
|
||||
end
|
||||
tmux.send_keys 'C-w'
|
||||
end
|
||||
end
|
||||
|
||||
def test_close
|
||||
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], 'foo' }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.until { |lines| refute_includes lines[1], 'foo' }
|
||||
tmux.send_keys '10'
|
||||
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.send_keys 'C-l', 'closed'
|
||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||
end
|
||||
|
||||
def test_select_deselect
|
||||
tmux.send_keys "seq 3 | #{FZF} --multi --bind up:deselect+up,down:select+down", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
tmux.send_keys :Tab
|
||||
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_equal 0, lines.select_count }
|
||||
tmux.send_keys :Down, :Down
|
||||
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||
tmux.send_keys :Tab
|
||||
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||
tmux.send_keys :Down, :Down
|
||||
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||
end
|
||||
|
||||
def test_interrupt_execute
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
tmux.send_keys 'C-l'
|
||||
tmux.until { |lines| assert lines.any_include?('executing 1') }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
tmux.send_keys 99
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
end
|
||||
|
||||
def test_kill_default_command_on_abort
|
||||
script = tempname + '.sh'
|
||||
writelines(script,
|
||||
['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{script}")
|
||||
|
||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.send_keys 'C-l', 'closed'
|
||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||
wait { refute system("pgrep -f #{script}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{script}")
|
||||
begin
|
||||
File.unlink(script)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def test_kill_default_command_on_accept
|
||||
script = tempname + '.sh'
|
||||
writelines(script,
|
||||
['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{script}")
|
||||
|
||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal 'Started', readonce.chomp
|
||||
wait { refute system("pgrep -f #{script}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{script}")
|
||||
begin
|
||||
File.unlink(script)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def test_kill_reload_command_on_abort
|
||||
script = tempname + '.sh'
|
||||
writelines(script,
|
||||
['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{script}")
|
||||
|
||||
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.send_keys 'C-l', 'closed'
|
||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||
wait { refute system("pgrep -f #{script}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{script}")
|
||||
begin
|
||||
File.unlink(script)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def test_kill_reload_command_on_accept
|
||||
script = tempname + '.sh'
|
||||
writelines(script,
|
||||
['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{script}")
|
||||
|
||||
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal 'Started', readonce.chomp
|
||||
wait { refute system("pgrep -f #{script}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{script}")
|
||||
begin
|
||||
File.unlink(script)
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_header
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '4/1000'
|
||||
assert_equal(%w[1 2 3 4 5], top5[lines])
|
||||
end
|
||||
tmux.send_keys '55'
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert_equal(%w[1 2 3 55 56], top5[lines])
|
||||
end
|
||||
tmux.send_keys 'C-J'
|
||||
tmux.until do |lines|
|
||||
assert_equal(%w[1 2 3 58 59], top5[lines])
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until do |lines|
|
||||
assert_equal 19, lines.match_count
|
||||
assert_equal(%w[1 2 3 5 6], top5[lines])
|
||||
end
|
||||
tmux.send_keys 'C-K'
|
||||
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||
end
|
||||
|
||||
def test_unbind
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d)'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
tmux.send_keys 'ab'
|
||||
tmux.until { |lines| assert_equal '> ab', lines[-1] }
|
||||
tmux.send_keys 'c'
|
||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||
tmux.send_keys 'dabcd'
|
||||
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
@@ -1993,7 +2223,7 @@ module CompletionTest
|
||||
end
|
||||
|
||||
# ~USERNAME**<TAB>
|
||||
user = ENV['USER']
|
||||
user = `whoami`.chomp
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys "cat ~#{user}**", :Tab
|
||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||
|
Reference in New Issue
Block a user