mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-10 06:31:54 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0ef8987fb | ||
|
|
3af5b7f2ac | ||
|
|
7a7cfcacbe | ||
|
|
52594355bf | ||
|
|
0d06c28b19 | ||
|
|
ccc4677252 | ||
|
|
821fc9feed | ||
|
|
82b46726fc | ||
|
|
8df872a482 | ||
|
|
c79c306adb | ||
|
|
51fdaad002 | ||
|
|
885cd8ff04 | ||
|
|
2707af403a | ||
|
|
2d227e5222 | ||
|
|
70529878e2 | ||
|
|
3b7a962dc6 | ||
|
|
6dcf5c3d7d | ||
|
|
b089bb5e7b | ||
|
|
a91a67668e | ||
|
|
220a908118 | ||
|
|
54841248e7 | ||
|
|
a0b42e6538 | ||
|
|
3312cf525d | ||
|
|
2093667548 | ||
|
|
3c868d7961 | ||
|
|
707f4f5816 | ||
|
|
b3ab6311c5 | ||
|
|
d56f605b63 | ||
|
|
f8b713f425 | ||
|
|
5209e95bc7 | ||
|
|
ef67a45702 | ||
|
|
b88eb72ac2 | ||
|
|
32847f7254 | ||
|
|
71df93b534 | ||
|
|
bb028191f8 | ||
|
|
19af8fc7d8 | ||
|
|
a06671b47f | ||
|
|
5f385d88e0 | ||
|
|
9cb7a364a3 | ||
|
|
f68cbc577d | ||
|
|
dc975e8974 | ||
|
|
4311ade535 | ||
|
|
cd23401411 | ||
|
|
176ee6910f | ||
|
|
13c8f3d3aa | ||
|
|
ce9af687bc | ||
|
|
43f0d0cacd | ||
|
|
20b4e6953e | ||
|
|
7da287e3aa | ||
|
|
205f885d69 | ||
|
|
3715cd349d | ||
|
|
e4c3ecc57e | ||
|
|
673c5d886d | ||
|
|
f799b568d1 | ||
|
|
7bff4661f6 | ||
|
|
ffd8bef808 | ||
|
|
02cee2234d | ||
|
|
e0dd2be3fb | ||
|
|
a33c011c21 | ||
|
|
7c3f42bbba | ||
|
|
edac9820b5 | ||
|
|
84a47f7102 | ||
|
|
97ae8afb6f |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|||||||
15
.github/workflows/codeql-analysis.yml
vendored
15
.github/workflows/codeql-analysis.yml
vendored
@@ -8,8 +8,15 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
permissions:
|
||||||
|
actions: read # for github/codeql-action/init to get workflow details
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
security-events: write # for github/codeql-action/autobuild to send a status report
|
||||||
name: Analyze
|
name: Analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -20,18 +27,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@1ed1437484560351c5be56cf73a48a279d116b78
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@1ed1437484560351c5be56cf73a48a279d116b78
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@1ed1437484560351c5be56cf73a48a279d116b78
|
||||||
|
|||||||
14
.github/workflows/linux.yml
vendored
14
.github/workflows/linux.yml
vendored
@@ -8,24 +8,24 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [1.14, 1.16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
|||||||
14
.github/workflows/macos.yml
vendored
14
.github/workflows/macos.yml
vendored
@@ -8,24 +8,24 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [1.14, 1.16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
|||||||
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
golang 1.18
|
||||||
73
ADVANCED.md
73
ADVANCED.md
@@ -17,6 +17,7 @@ Advanced fzf examples
|
|||||||
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
||||||
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
||||||
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
||||||
|
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
|
||||||
* [Log tailing](#log-tailing)
|
* [Log tailing](#log-tailing)
|
||||||
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
||||||
* [Files listed in `git status`](#files-listed-in-git-status)
|
* [Files listed in `git status`](#files-listed-in-git-status)
|
||||||
@@ -405,6 +406,40 @@ IFS=: read -ra selected < <(
|
|||||||
- We reverted `--color` option for customizing how the matching chunks are
|
- We reverted `--color` option for customizing how the matching chunks are
|
||||||
displayed in the second phase
|
displayed in the second phase
|
||||||
|
|
||||||
|
### Switching between Ripgrep mode and fzf mode
|
||||||
|
|
||||||
|
*(Requires fzf 0.30.0 or above)*
|
||||||
|
|
||||||
|
fzf 0.30.0 added `rebind` action so we can "rebind" the bindings that were
|
||||||
|
previously "unbound" via `unbind`.
|
||||||
|
|
||||||
|
This is an improved version of the previous example that allows us to switch
|
||||||
|
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
|
||||||
|
CTRL-F.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
|
||||||
|
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 "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+clear-query+rebind(ctrl-r)" \
|
||||||
|
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)" \
|
||||||
|
--prompt '1. Ripgrep> ' \
|
||||||
|
--delimiter : \
|
||||||
|
--header '╱ CTRL-R (Ripgrep mode) ╱ CTRL-F (fzf mode) ╱' \
|
||||||
|
--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]}"
|
||||||
|
```
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -429,30 +464,34 @@ Admittedly, that was a silly example. Here's a practical one for browsing
|
|||||||
Kubernetes pods.
|
Kubernetes pods.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
pods() {
|
||||||
|
FZF_DEFAULT_COMMAND="kubectl get pods --all-namespaces" \
|
||||||
read -ra tokens < <(
|
fzf --info=inline --layout=reverse --header-lines=1 \
|
||||||
kubectl get pods --all-namespaces |
|
|
||||||
fzf --info=inline --layout=reverse --header-lines=1 --border \
|
|
||||||
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
||||||
--header $'Press CTRL-O to open log in editor\n\n' \
|
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
||||||
--bind ctrl-/:toggle-preview \
|
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --namespace {1} {2}) > /dev/tty' \
|
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
||||||
--preview-window up,follow \
|
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
||||||
--preview 'kubectl logs --follow --tail=100000 --namespace {1} {2}' "$@"
|
--bind 'ctrl-r:reload:$FZF_DEFAULT_COMMAND' \
|
||||||
)
|
--preview-window up:follow \
|
||||||
[ ${#tokens} -gt 1 ] &&
|
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
||||||
kubectl exec -it --namespace "${tokens[0]}" "${tokens[1]}" -- bash
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- The preview window will *"log tail"* the pod
|
- The preview window will *"log tail"* the pod
|
||||||
- Holding on to a large amount of log will consume a lot of memory. So we
|
- Holding on to a large amount of log will consume a lot of memory. So we
|
||||||
limited the initial log amount with `--tail=100000`.
|
limited the initial log amount with `--tail=10000`.
|
||||||
- With `execute` binding, you can press CTRL-O to open the log in your editor
|
- `execute` bindings allow you to run any command without leaving fzf
|
||||||
without leaving fzf
|
- Press enter key on a pod to `kubectl exec` into it
|
||||||
- Select a pod (with an enter key) to `kubectl exec` into it
|
- Press CTRL-O to open the log in your editor
|
||||||
|
- Press CTRL-R to reload the pod list
|
||||||
|
- Press CTRL-/ repeatedly to to rotate through a different sets of preview
|
||||||
|
window options
|
||||||
|
1. `80%,border-bottom`
|
||||||
|
1. `hidden`
|
||||||
|
1. Empty string after `|` translates to the default options from `--preview-window`
|
||||||
|
|
||||||
Key bindings for git objects
|
Key bindings for git objects
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|||||||
2
BUILD.md
2
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.13 or above
|
- Go 1.17 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
|
|||||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,6 +1,72 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.31.0
|
||||||
|
------
|
||||||
|
- Added support for an alternative preview window layout that is activated
|
||||||
|
when the size of the preview window is smaller than a certain threshold.
|
||||||
|
```sh
|
||||||
|
# If the width of the preview window is smaller than 50 columns,
|
||||||
|
# it will be displayed above the search window.
|
||||||
|
fzf --preview 'cat {}' --preview-window 'right,50%,border-left,<50(up,30%,border-bottom)'
|
||||||
|
|
||||||
|
# Or you can just hide it like so
|
||||||
|
fzf --preview 'cat {}' --preview-window '<50(hidden)'
|
||||||
|
```
|
||||||
|
- fzf now uses SGR mouse mode to properly support mouse on larger terminals
|
||||||
|
- You can now use characters that do not satisfy `unicode.IsGraphic` constraint
|
||||||
|
for `--marker`, `--pointer`, and `--ellipsis`. Allows Nerd Fonts and stuff.
|
||||||
|
Use at your own risk.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Shell extension
|
||||||
|
- `kill` completion now requires trigger sequence (`**`) for consistency
|
||||||
|
|
||||||
|
0.30.0
|
||||||
|
------
|
||||||
|
- Fixed cursor flickering over the screen by hiding it during rendering
|
||||||
|
- Added `--ellipsis` option. You can take advantage of it to make fzf
|
||||||
|
effectively search non-visible parts of the item.
|
||||||
|
```sh
|
||||||
|
# Search against hidden line numbers on the far right
|
||||||
|
nl /usr/share/dict/words |
|
||||||
|
awk '{printf "%s%1000s\n", $2, $1}' |
|
||||||
|
fzf --nth=-1 --no-hscroll --ellipsis='' |
|
||||||
|
awk '{print $2}'
|
||||||
|
```
|
||||||
|
- Added `rebind` action for restoring bindings after `unbind`
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.29.0
|
||||||
|
------
|
||||||
|
- Added `change-preview(...)` action to change the `--preview` command
|
||||||
|
- cf. `preview(...)` is a one-off action that doesn't change the default
|
||||||
|
preview command
|
||||||
|
- Added `change-preview-window(...)` action
|
||||||
|
- You can rotate through the different options separated by `|`
|
||||||
|
```sh
|
||||||
|
fzf --preview 'cat {}' --preview-window right:40% \
|
||||||
|
--bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-top|hidden|)'
|
||||||
|
```
|
||||||
|
- Fixed rendering of the prompt line when overflow occurs with `--info=inline`
|
||||||
|
|
||||||
|
0.28.0
|
||||||
|
------
|
||||||
|
- Added `--header-first` option to print header before the prompt line
|
||||||
|
```sh
|
||||||
|
fzf --header $'Welcome to fzf\n▔▔▔▔▔▔▔▔▔▔▔▔▔▔' --reverse --height 30% --border --header-first
|
||||||
|
```
|
||||||
|
- Added `--scroll-off=LINES` option (similar to `scrolloff` option of Vim)
|
||||||
|
- You can set it to a very large number so that the cursor stays in the
|
||||||
|
middle of the screen while scrolling
|
||||||
|
```sh
|
||||||
|
fzf --scroll-off=5
|
||||||
|
fzf --scroll-off=999
|
||||||
|
```
|
||||||
|
- Fixed bug where preview window is not updated on `reload` (#2644)
|
||||||
|
- fzf on Windows will also use `$SHELL` to execute external programs
|
||||||
|
- See #2638 and #2647
|
||||||
|
- Thanks to @rashil2000, @vovcacik, and @janlazo
|
||||||
|
|
||||||
0.27.3
|
0.27.3
|
||||||
------
|
------
|
||||||
- Preview window is `hidden` by default when there are `preview` bindings but
|
- Preview window is `hidden` by default when there are `preview` bindings but
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM archlinux/base:latest
|
FROM archlinux
|
||||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.14.2 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -35,6 +35,7 @@ BINARYARM7 := fzf-$(GOOS)_arm7
|
|||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||||
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
||||||
|
BINARYLOONG64 := fzf-$(GOOS)_loong64
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
# https://en.wikipedia.org/wiki/Uname
|
||||||
UNAME_M := $(shell uname -m)
|
UNAME_M := $(shell uname -m)
|
||||||
@@ -62,6 +63,8 @@ else ifeq ($(UNAME_M),ppc64le)
|
|||||||
BINARY := $(BINARYPPC64LE)
|
BINARY := $(BINARYPPC64LE)
|
||||||
else ifeq ($(UNAME_M),riscv64)
|
else ifeq ($(UNAME_M),riscv64)
|
||||||
BINARY := $(BINARYRISCV64)
|
BINARY := $(BINARYRISCV64)
|
||||||
|
else ifeq ($(UNAME_M),loongarch64)
|
||||||
|
BINARY := $(BINARYLOONG64)
|
||||||
else
|
else
|
||||||
$(error Build on $(UNAME_M) is not supported, yet.)
|
$(error Build on $(UNAME_M) is not supported, yet.)
|
||||||
endif
|
endif
|
||||||
@@ -102,6 +105,7 @@ endif
|
|||||||
grep -qF $(VERSION) install.ps1
|
grep -qF $(VERSION) install.ps1
|
||||||
|
|
||||||
# Make release note out of CHANGELOG.md
|
# Make release note out of CHANGELOG.md
|
||||||
|
mkdir -p tmp
|
||||||
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
|
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
|
||||||
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
|
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
|
||||||
|
|
||||||
@@ -147,6 +151,9 @@ target/$(BINARYPPC64LE): $(SOURCES)
|
|||||||
target/$(BINARYRISCV64): $(SOURCES)
|
target/$(BINARYRISCV64): $(SOURCES)
|
||||||
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYLOONG64): $(SOURCES)
|
||||||
|
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
bin/fzf: target/$(BINARY) | bin
|
bin/fzf: target/$(BINARY) | bin
|
||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -86,7 +86,7 @@ stuff.
|
|||||||
|
|
||||||
### Using Homebrew
|
### Using Homebrew
|
||||||
|
|
||||||
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
|
You can use [Homebrew](https://brew.sh/) (on macOS or Linux)
|
||||||
to install fzf.
|
to install fzf.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -131,6 +131,8 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
>
|
>
|
||||||
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
|
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
|
||||||
|
|
||||||
|
[](https://repology.org/project/fzf/versions)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
@@ -380,12 +382,11 @@ cd ~/github/fzf**<TAB>
|
|||||||
|
|
||||||
#### Process IDs
|
#### Process IDs
|
||||||
|
|
||||||
Fuzzy completion for PIDs is provided for kill command. In this case,
|
Fuzzy completion for PIDs is provided for kill command.
|
||||||
there is no trigger sequence; just press the tab key after the kill command.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
||||||
kill -9 <TAB>
|
kill -9 **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Host names
|
#### Host names
|
||||||
@@ -590,6 +591,9 @@ If ripgrep doesn't find any matches, it will exit with a non-zero exit status,
|
|||||||
and fzf will warn you about it. To suppress the warning message, we added
|
and fzf will warn you about it. To suppress the warning message, we added
|
||||||
`|| true` to the command, so that it always exits with 0.
|
`|| true` to the command, so that it always exits with 0.
|
||||||
|
|
||||||
|
See ["Using fzf as interative Ripgrep launcher"](https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interative-ripgrep-launcher)
|
||||||
|
for a fuller example with preview window options.
|
||||||
|
|
||||||
### Preview window
|
### Preview window
|
||||||
|
|
||||||
When the `--preview` option is set, fzf automatically starts an external process
|
When the `--preview` option is set, fzf automatically starts an external process
|
||||||
|
|||||||
16
go.mod
16
go.mod
@@ -2,16 +2,20 @@ module github.com/junegunn/fzf
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell v1.4.0
|
github.com/gdamore/tcell v1.4.0
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.14
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/mattn/go-runewidth v0.0.13
|
github.com/mattn/go-runewidth v0.0.13
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.2.0
|
github.com/rivo/uniseg v0.2.0
|
||||||
github.com/saracen/walker v0.1.2
|
github.com/saracen/walker v0.1.2
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
|
||||||
golang.org/x/text v0.3.6 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
require (
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -20,12 +20,13 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
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/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-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
9
install
9
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.27.3
|
version=0.31.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -256,7 +256,7 @@ for shell in $shells; do
|
|||||||
# Setup fzf
|
# Setup fzf
|
||||||
# ---------
|
# ---------
|
||||||
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||||
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
@@ -280,11 +280,6 @@ EOF
|
|||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
|
|
||||||
mkdir -p "${fish_dir}/functions"
|
mkdir -p "${fish_dir}/functions"
|
||||||
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
|
||||||
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
|
||||||
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
if [ $key_bindings -ne 0 ]; then
|
if [ $key_bindings -ne 0 ]; then
|
||||||
echo -n "Symlink $fish_binding ... "
|
echo -n "Symlink $fish_binding ... "
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.27.3"
|
$version="0.31.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
4
main.go
4
main.go
@@ -1,11 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/junegunn/fzf/src"
|
fzf "github.com/junegunn/fzf/src"
|
||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.27"
|
var version string = "0.31"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Oct 2021" "fzf 0.27.3" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jul 2022" "fzf 0.31.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
162
man/man1/fzf.1
162
man/man1/fzf.1
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Oct 2021" "fzf 0.27.3" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jul 2022" "fzf 0.31.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -135,10 +135,14 @@ Enable cyclic scroll
|
|||||||
Keep the right end of the line visible when it's too long. Effective only when
|
Keep the right end of the line visible when it's too long. Effective only when
|
||||||
the query string is empty.
|
the query string is empty.
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--scroll-off=" "LINES"
|
||||||
|
Number of screen lines to keep above or below when scrolling to the top or to
|
||||||
|
the bottom (default: 0).
|
||||||
|
.TP
|
||||||
.B "--no-hscroll"
|
.B "--no-hscroll"
|
||||||
Disable horizontal scroll
|
Disable horizontal scroll
|
||||||
.TP
|
.TP
|
||||||
.BI "--hscroll-off=" "COL"
|
.BI "--hscroll-off=" "COLS"
|
||||||
Number of screen columns to keep to the right of the highlighted substring
|
Number of screen columns to keep to the right of the highlighted substring
|
||||||
(default: 10). Setting it to a large value will cause the text to be positioned
|
(default: 10). Setting it to a large value will cause the text to be positioned
|
||||||
on the center of the screen.
|
on the center of the screen.
|
||||||
@@ -295,6 +299,12 @@ are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
|||||||
The first N lines of the input are treated as the sticky header. When
|
The first N lines of the input are treated as the sticky header. When
|
||||||
\fB--with-nth\fR is set, the lines are transformed just like the other
|
\fB--with-nth\fR is set, the lines are transformed just like the other
|
||||||
lines that follow.
|
lines that follow.
|
||||||
|
.TP
|
||||||
|
.B "--header-first"
|
||||||
|
Print header before the prompt line
|
||||||
|
.TP
|
||||||
|
.BI "--ellipsis=" "STR"
|
||||||
|
Ellipsis to show when line is truncated (default: '..')
|
||||||
.SS Display
|
.SS Display
|
||||||
.TP
|
.TP
|
||||||
.B "--ansi"
|
.B "--ansi"
|
||||||
@@ -440,7 +450,7 @@ e.g.
|
|||||||
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||||
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
|
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
@@ -460,7 +470,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"
|
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
@@ -478,9 +488,9 @@ default until \fBtoggle-preview\fR action is triggered.
|
|||||||
execute the command in the background.
|
execute the command in the background.
|
||||||
|
|
||||||
* Long lines are truncated by default. Line wrap can be enabled with
|
* Long lines are truncated by default. Line wrap can be enabled with
|
||||||
\fB:wrap\fR flag.
|
\fBwrap\fR flag.
|
||||||
|
|
||||||
* Preview window will automatically scroll to the bottom when \fB:follow\fR
|
* Preview window will automatically scroll to the bottom when \fBfollow\fR
|
||||||
flag is set, similarly to how \fBtail -f\fR works.
|
flag is set, similarly to how \fBtail -f\fR works.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
@@ -492,7 +502,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
||||||
|
|
||||||
* To change the style of the border of the preview window, specify one of
|
* To change the style of the border of the preview window, specify one of
|
||||||
the options for \fB--border\fR with \fBborder-\fR prefix.
|
the options for \fB--border\fR with \fBborder-\fR prefix.
|
||||||
@@ -542,6 +552,15 @@ e.g.
|
|||||||
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
* You can specify an alternative set of options that are used only when the size
|
||||||
|
of the preview window is below a certain threshold. Note that only one
|
||||||
|
alternative layout is allowed.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview 'cat {}' --preview-window 'right,border-left,<30(up,30%,border-bottom)'\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
.BI "-q, --query=" "STR"
|
.BI "-q, --query=" "STR"
|
||||||
@@ -803,76 +822,80 @@ e.g.
|
|||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
\fBclear-selection\fR (clear multi-selection)
|
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBclear-query\fR (clear query string)
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
\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\fR
|
||||||
\fBdeselect-all\fR (deselect all matches)
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
\fBdisable-search\fR (disable search functionality)
|
\fBdisable-search\fR (disable search functionality)
|
||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBenable-search\fR (enable search functionality)
|
\fBenable-search\fR (enable search functionality)
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBexecute-silent(...)\fR (see below for the details)
|
\fBexecute-silent(...)\fR (see below for the details)
|
||||||
\fBfirst\fR (move to the first match)
|
\fBfirst\fR (move to the first match)
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\fBjump-accept\fR (jump and accept)
|
\fBjump-accept\fR (jump and accept)
|
||||||
\fBkill-line\fR
|
\fBkill-line\fR
|
||||||
\fBkill-word\fR \fIalt-d\fR
|
\fBkill-word\fR \fIalt-d\fR
|
||||||
\fBlast\fR (move to the last match)
|
\fBlast\fR (move to the last match)
|
||||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||||
\fBpage-down\fR \fIpgdn\fR
|
\fBpage-down\fR \fIpgdn\fR
|
||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview(...)\fR (see below for the details)
|
\fBpreview(...)\fR (see below for the details)
|
||||||
\fBpreview-down\fR \fIshift-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR \fIshift-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBpreview-half-page-down\fR
|
\fBpreview-half-page-down\fR
|
||||||
\fBpreview-half-page-up\fR
|
\fBpreview-half-page-up\fR
|
||||||
\fBpreview-bottom\fR
|
\fBpreview-bottom\fR
|
||||||
\fBpreview-top\fR
|
\fBpreview-top\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
|
\fBput\fR (put the character to the prompt)
|
||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreload(...)\fR (see below for the details)
|
||||||
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect\fR
|
\fBselect\fR
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-search\fR (toggle search functionality)
|
\fBtoggle-search\fR (toggle search functionality)
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBunbind(...)\fR (unbind bindings)
|
\fBunbind(...)\fR (unbind bindings)
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
@@ -962,7 +985,6 @@ commands in addition to the default preview command given by \fB--preview\fR
|
|||||||
option.
|
option.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
# Default preview command with an extra preview binding
|
# Default preview command with an extra preview binding
|
||||||
fzf --preview 'file {}' --bind '?:preview:cat {}'
|
fzf --preview 'file {}' --bind '?:preview:cat {}'
|
||||||
|
|
||||||
@@ -973,6 +995,22 @@ e.g.
|
|||||||
# Preview window hidden by default, it appears when you first hit '?'
|
# Preview window hidden by default, it appears when you first hit '?'
|
||||||
fzf --bind '?:preview:cat {}' --preview-window hidden
|
fzf --bind '?:preview:cat {}' --preview-window hidden
|
||||||
|
|
||||||
|
.SS CHANGE PREVIEW WINDOW ATTRIBUTES
|
||||||
|
|
||||||
|
\fBchange-preview-window\fR action can be used to change the properties of the
|
||||||
|
preview window. Unlike the \fB--preview-window\fR option, you can specify
|
||||||
|
multiple sets of options separated by '|' characters.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
# Rotate through the options using CTRL-/
|
||||||
|
fzf --preview 'cat {}' --bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|right)'
|
||||||
|
|
||||||
|
# The default properties given by `--preview-window` are inherited, so an empty string in the list is interpreted as the default
|
||||||
|
fzf --preview 'cat {}' --preview-window 'right,40%,border-left' --bind 'ctrl-/:change-preview-window(70%|down,border-top|hidden|)'
|
||||||
|
|
||||||
|
# This is equivalent to toggle-preview action
|
||||||
|
fzf --preview 'cat {}' --bind 'ctrl-/:change-preview-window(hidden|)'
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,12 @@ function! fzf#shellescape(arg, ...)
|
|||||||
if shell =~# 'cmd.exe$'
|
if shell =~# 'cmd.exe$'
|
||||||
return s:shellesc_cmd(a:arg)
|
return s:shellesc_cmd(a:arg)
|
||||||
endif
|
endif
|
||||||
return s:fzf_call('shellescape', a:arg)
|
try
|
||||||
|
let [shell, &shell] = [&shell, shell]
|
||||||
|
return s:fzf_call('shellescape', a:arg)
|
||||||
|
finally
|
||||||
|
let [shell, &shell] = [&shell, shell]
|
||||||
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_getcwd()
|
function! s:fzf_getcwd()
|
||||||
@@ -159,7 +164,7 @@ function s:get_version(bin)
|
|||||||
if has_key(s:versions, a:bin)
|
if has_key(s:versions, a:bin)
|
||||||
return s:versions[a:bin]
|
return s:versions[a:bin]
|
||||||
end
|
end
|
||||||
let command = a:bin . ' --version --no-height'
|
let command = fzf#shellescape(a:bin) . ' --version --no-height'
|
||||||
let output = systemlist(command)
|
let output = systemlist(command)
|
||||||
if v:shell_error || empty(output)
|
if v:shell_error || empty(output)
|
||||||
return ''
|
return ''
|
||||||
@@ -444,6 +449,12 @@ function! s:use_sh()
|
|||||||
return [shell, shellslash, shellcmdflag, shellxquote]
|
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:writefile(...)
|
||||||
|
if call('writefile', a:000) == -1
|
||||||
|
throw 'Failed to write temporary file. Check if you can write to the path tempname() returns.'
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -471,7 +482,7 @@ try
|
|||||||
let source_command = source
|
let source_command = source
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call writefile(source, temps.input)
|
call s:writefile(source, temps.input)
|
||||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
@@ -515,7 +526,7 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
if len(source_command)
|
if exists('source_command') && len(source_command)
|
||||||
if len(prev_default_command)
|
if len(prev_default_command)
|
||||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
let $FZF_DEFAULT_COMMAND = prev_default_command
|
||||||
else
|
else
|
||||||
@@ -660,7 +671,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
endif
|
endif
|
||||||
if s:is_win
|
if s:is_win
|
||||||
let batchfile = s:fzf_tempname().'.bat'
|
let batchfile = s:fzf_tempname().'.bat'
|
||||||
call writefile(s:wrap_cmds(command), batchfile)
|
call s:writefile(s:wrap_cmds(command), batchfile)
|
||||||
let command = batchfile
|
let command = batchfile
|
||||||
let a:temps.batchfile = batchfile
|
let a:temps.batchfile = batchfile
|
||||||
if has('nvim')
|
if has('nvim')
|
||||||
@@ -678,7 +689,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
endif
|
endif
|
||||||
elseif has('win32unix') && $TERM !=# 'cygwin'
|
elseif has('win32unix') && $TERM !=# 'cygwin'
|
||||||
let shellscript = s:fzf_tempname()
|
let shellscript = s:fzf_tempname()
|
||||||
call writefile([command], shellscript)
|
call s:writefile([command], shellscript)
|
||||||
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
||||||
let a:temps.shellscript = shellscript
|
let a:temps.shellscript = shellscript
|
||||||
endif
|
endif
|
||||||
@@ -877,7 +888,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
call s:pushd(a:dict)
|
call s:pushd(a:dict)
|
||||||
if s:is_win
|
if s:is_win
|
||||||
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||||
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
call s:writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||||
let command = fzf.temps.batchfile
|
let command = fzf.temps.batchfile
|
||||||
else
|
else
|
||||||
let command = a:command
|
let command = a:command
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ fi
|
|||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line'
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
@@ -260,14 +260,6 @@ _fzf_dir_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
local trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
|
||||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
if [[ -z "$cur" ]]; then
|
|
||||||
COMP_WORDS[$COMP_CWORD]=$trigger
|
|
||||||
elif [[ "$cur" != *"$trigger" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_fzf_proc_completion "$@"
|
_fzf_proc_completion "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +296,10 @@ _fzf_alias_completion() {
|
|||||||
|
|
||||||
# fzf options
|
# fzf options
|
||||||
complete -o default -F _fzf_opts_completion fzf
|
complete -o default -F _fzf_opts_completion fzf
|
||||||
|
# fzf-tmux is a thin fzf wrapper that has only a few more options than fzf
|
||||||
|
# itself. As a quick improvement we take fzf's completion. Adding the few extra
|
||||||
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||||
a_cmds="
|
a_cmds="
|
||||||
@@ -348,9 +344,6 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Kill completion (supports empty completion trigger)
|
|
||||||
complete -F _fzf_complete_kill -o default -o bashdefault kill
|
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
@@ -373,9 +366,10 @@ _fzf_setup_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset
|
_fzf_setup_completion 'var' export unset
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -285,12 +285,6 @@ fzf-completion() {
|
|||||||
|
|
||||||
lbuf=$LBUFFER
|
lbuf=$LBUFFER
|
||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
|
||||||
if [ "$cmd" = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
|
||||||
tail=$trigger
|
|
||||||
tokens+=$trigger
|
|
||||||
lbuf="$lbuf$trigger"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
|
|||||||
@@ -14,14 +14,17 @@
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd opts
|
||||||
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
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
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS -m"
|
||||||
printf '%q ' "$item"
|
eval "$cmd" |
|
||||||
done
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
echo
|
while read -r item; do
|
||||||
|
printf '%q ' "$item" # escape special chars
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
@@ -32,24 +35,27 @@ __fzfcmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
local selected="$(__fzf_select__)"
|
local selected="$(__fzf_select__ "$@")"
|
||||||
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
||||||
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd dir
|
local cmd opts 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 \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
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"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS +m"
|
||||||
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output
|
local output opts script
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
|
||||||
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
builtin fc -lnr -2147483648 |
|
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{$_}++' |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
|
||||||
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"
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function fzf_key_bindings
|
|||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
cd $result
|
builtin cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
|||||||
@@ -65,8 +65,10 @@ fzf-file-widget() {
|
|||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey -M emacs '^T' fzf-file-widget
|
||||||
|
bindkey -M vicmd '^T' fzf-file-widget
|
||||||
|
bindkey -M viins '^T' fzf-file-widget
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
@@ -79,21 +81,23 @@ fzf-cd-widget() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="cd ${(q)dir}"
|
BUFFER="builtin cd -- ${(q)dir}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
local ret=$?
|
local ret=$?
|
||||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
bindkey '\ec' fzf-cd-widget
|
bindkey -M emacs '\ec' fzf-cd-widget
|
||||||
|
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||||
|
bindkey -M viins '\ec' fzf-cd-widget
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
|
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^\s*[0-9]+\**\s+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
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)) )
|
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=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -105,8 +109,10 @@ fzf-history-widget() {
|
|||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
bindkey '^R' fzf-history-widget
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
eval $__fzf_key_bindings_options
|
eval $__fzf_key_bindings_options
|
||||||
|
|||||||
12
src/core.go
12
src/core.go
@@ -242,9 +242,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func() []rune {
|
input := func(reloaded bool) []rune {
|
||||||
paused, input := terminal.Input()
|
paused, input := terminal.Input()
|
||||||
if !paused {
|
if reloaded && paused {
|
||||||
|
query = []rune{}
|
||||||
|
} else if !paused {
|
||||||
query = input
|
query = input
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
@@ -274,7 +276,8 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
opts.Sync = false
|
opts.Sync = false
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
reset := clearCache()
|
||||||
|
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *string
|
||||||
@@ -293,7 +296,8 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
reset := clearCache()
|
||||||
|
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
|
|||||||
145
src/options.go
145
src/options.go
@@ -44,8 +44,10 @@ const usage = `usage: fzf [options]
|
|||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
--keep-right Keep the right end of the line visible on overflow
|
--keep-right Keep the right end of the line visible on overflow
|
||||||
|
--scroll-off=LINES Number of screen lines to keep above or below when
|
||||||
|
scrolling to the top or to the bottom (default: 0)
|
||||||
--no-hscroll Disable horizontal scroll
|
--no-hscroll Disable horizontal scroll
|
||||||
--hscroll-off=COL Number of screen columns to keep to the right of the
|
--hscroll-off=COLS Number of screen columns to keep to the right of the
|
||||||
highlighted substring (default: 10)
|
highlighted substring (default: 10)
|
||||||
--filepath-word Make word-wise movements respect path separators
|
--filepath-word Make word-wise movements respect path separators
|
||||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||||
@@ -67,6 +69,8 @@ const usage = `usage: fzf [options]
|
|||||||
--marker=STR Multi-select marker (default: '>')
|
--marker=STR Multi-select marker (default: '>')
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
|
--header-first Print header before the prompt line
|
||||||
|
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
|
||||||
|
|
||||||
Display
|
Display
|
||||||
--ansi Enable processing of ANSI color codes
|
--ansi Enable processing of ANSI color codes
|
||||||
@@ -85,7 +89,7 @@ const usage = `usage: fzf [options]
|
|||||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
||||||
[,border-BORDER_OPT]
|
[,border-BORDER_OPT]
|
||||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[,default]
|
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
@@ -171,6 +175,18 @@ type previewOpts struct {
|
|||||||
follow bool
|
follow bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
headerLines int
|
headerLines int
|
||||||
|
threshold int
|
||||||
|
alternative *previewOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) sameLayout(b previewOpts) bool {
|
||||||
|
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold &&
|
||||||
|
(a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
|
||||||
|
a.alternative == nil && b.alternative == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||||
|
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@@ -200,6 +216,7 @@ type Options struct {
|
|||||||
KeepRight bool
|
KeepRight bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
|
ScrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
@@ -212,7 +229,7 @@ type Options struct {
|
|||||||
Filter *string
|
Filter *string
|
||||||
ToggleSort bool
|
ToggleSort bool
|
||||||
Expect map[tui.Event]string
|
Expect map[tui.Event]string
|
||||||
Keymap map[tui.Event][]action
|
Keymap map[tui.Event][]*action
|
||||||
Preview previewOpts
|
Preview previewOpts
|
||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
@@ -222,6 +239,8 @@ type Options struct {
|
|||||||
History *History
|
History *History
|
||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
|
HeaderFirst bool
|
||||||
|
Ellipsis string
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Padding [4]sizeSpec
|
Padding [4]sizeSpec
|
||||||
BorderShape tui.BorderShape
|
BorderShape tui.BorderShape
|
||||||
@@ -232,7 +251,7 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
@@ -261,6 +280,7 @@ func defaultOptions() *Options {
|
|||||||
KeepRight: false,
|
KeepRight: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
|
ScrollOff: 0,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InfoStyle: infoDefault,
|
InfoStyle: infoDefault,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
@@ -273,7 +293,7 @@ func defaultOptions() *Options {
|
|||||||
Filter: nil,
|
Filter: nil,
|
||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[tui.Event]string),
|
Expect: make(map[tui.Event]string),
|
||||||
Keymap: make(map[tui.Event][]action),
|
Keymap: make(map[tui.Event][]*action),
|
||||||
Preview: defaultPreviewOpts(""),
|
Preview: defaultPreviewOpts(""),
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
@@ -283,6 +303,8 @@ func defaultOptions() *Options {
|
|||||||
History: nil,
|
History: nil,
|
||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
|
HeaderFirst: false,
|
||||||
|
Ellipsis: "..",
|
||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
Unicode: true,
|
Unicode: true,
|
||||||
@@ -780,10 +802,10 @@ func init() {
|
|||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
symbol := ":"
|
symbol := ":"
|
||||||
if strings.HasPrefix(src, "+") {
|
if strings.HasPrefix(src, "+") {
|
||||||
@@ -792,10 +814,16 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
prefix := symbol + "execute"
|
prefix := symbol + "execute"
|
||||||
if strings.HasPrefix(src[1:], "reload") {
|
if strings.HasPrefix(src[1:], "reload") {
|
||||||
prefix = symbol + "reload"
|
prefix = symbol + "reload"
|
||||||
|
} else if strings.HasPrefix(src[1:], "change-preview-window") {
|
||||||
|
prefix = symbol + "change-preview-window"
|
||||||
|
} else if strings.HasPrefix(src[1:], "change-preview") {
|
||||||
|
prefix = symbol + "change-preview"
|
||||||
} else if strings.HasPrefix(src[1:], "preview") {
|
} else if strings.HasPrefix(src[1:], "preview") {
|
||||||
prefix = symbol + "preview"
|
prefix = symbol + "preview"
|
||||||
} else if strings.HasPrefix(src[1:], "unbind") {
|
} else if strings.HasPrefix(src[1:], "unbind") {
|
||||||
prefix = symbol + "unbind"
|
prefix = symbol + "unbind"
|
||||||
|
} else if strings.HasPrefix(src[1:], "rebind") {
|
||||||
|
prefix = symbol + "rebind"
|
||||||
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
||||||
prefix = symbol + "change-prompt"
|
prefix = symbol + "change-prompt"
|
||||||
} else if src[len(prefix)] == '-' {
|
} else if src[len(prefix)] == '-' {
|
||||||
@@ -835,7 +863,7 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
|
|
||||||
idx2 := len(pair[0]) + 1
|
idx2 := len(pair[0]) + 1
|
||||||
specs := strings.Split(pair[1], "+")
|
specs := strings.Split(pair[1], "+")
|
||||||
actions := make([]action, 0, len(specs))
|
actions := make([]*action, 0, len(specs))
|
||||||
appendAction := func(types ...actionType) {
|
appendAction := func(types ...actionType) {
|
||||||
actions = append(actions, toActions(types...)...)
|
actions = append(actions, toActions(types...)...)
|
||||||
}
|
}
|
||||||
@@ -974,6 +1002,12 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
appendAction(actEnableSearch)
|
appendAction(actEnableSearch)
|
||||||
case "disable-search":
|
case "disable-search":
|
||||||
appendAction(actDisableSearch)
|
appendAction(actDisableSearch)
|
||||||
|
case "put":
|
||||||
|
if key.Type == tui.Rune && unicode.IsGraphic(key.Char) {
|
||||||
|
appendAction(actRune)
|
||||||
|
} else {
|
||||||
|
errorExit("unable to put non-printable character: " + pair[0])
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
@@ -989,10 +1023,16 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
offset = len("reload")
|
offset = len("reload")
|
||||||
case actPreview:
|
case actPreview:
|
||||||
offset = len("preview")
|
offset = len("preview")
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
offset = len("change-preview-window")
|
||||||
|
case actChangePreview:
|
||||||
|
offset = len("change-preview")
|
||||||
case actChangePrompt:
|
case actChangePrompt:
|
||||||
offset = len("change-prompt")
|
offset = len("change-prompt")
|
||||||
case actUnbind:
|
case actUnbind:
|
||||||
offset = len("unbind")
|
offset = len("unbind")
|
||||||
|
case actRebind:
|
||||||
|
offset = len("rebind")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@@ -1004,17 +1044,22 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
if spec[offset] == ':' {
|
if spec[offset] == ':' {
|
||||||
if specIndex == len(specs)-1 {
|
if specIndex == len(specs)-1 {
|
||||||
actionArg = spec[offset+1:]
|
actionArg = spec[offset+1:]
|
||||||
actions = append(actions, action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
} else {
|
} else {
|
||||||
prevSpec = spec + "+"
|
prevSpec = spec + "+"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actionArg = spec[offset+1 : len(spec)-1]
|
actionArg = spec[offset+1 : len(spec)-1]
|
||||||
actions = append(actions, action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
}
|
}
|
||||||
if t == actUnbind {
|
if t == actUnbind || t == actRebind {
|
||||||
parseKeyChords(actionArg, "unbind target required")
|
parseKeyChords(actionArg, spec[0:offset]+" target required")
|
||||||
|
} else if t == actChangePreviewWindow {
|
||||||
|
opts := previewOpts{}
|
||||||
|
for _, arg := range strings.Split(actionArg, "|") {
|
||||||
|
parsePreviewWindow(&opts, arg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1038,8 +1083,14 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actReload
|
return actReload
|
||||||
case "unbind":
|
case "unbind":
|
||||||
return actUnbind
|
return actUnbind
|
||||||
|
case "rebind":
|
||||||
|
return actRebind
|
||||||
case "preview":
|
case "preview":
|
||||||
return actPreview
|
return actPreview
|
||||||
|
case "change-preview-window":
|
||||||
|
return actChangePreviewWindow
|
||||||
|
case "change-preview":
|
||||||
|
return actChangePreview
|
||||||
case "change-prompt":
|
case "change-prompt":
|
||||||
return actChangePrompt
|
return actChangePrompt
|
||||||
case "execute":
|
case "execute":
|
||||||
@@ -1052,7 +1103,7 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToggleSort(keymap map[tui.Event][]action, str string) {
|
func parseToggleSort(keymap map[tui.Event][]*action, str string) {
|
||||||
keys := parseKeyChords(str, "key name required")
|
keys := parseKeyChords(str, "key name required")
|
||||||
if len(keys) != 1 {
|
if len(keys) != 1 {
|
||||||
errorExit("multiple keys specified")
|
errorExit("multiple keys specified")
|
||||||
@@ -1122,12 +1173,19 @@ func parseInfoStyle(str string) infoStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
|
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
offsetRegex := regexp.MustCompile(`^(\+{-?[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]*)$")
|
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||||
tokens := delimRegex.Split(input, -1)
|
tokens := tokenRegex.FindAllStringSubmatch(input, -1)
|
||||||
for _, token := range tokens {
|
var alternative string
|
||||||
|
for _, match := range tokens {
|
||||||
|
if len(match[2]) > 0 {
|
||||||
|
opts.threshold = atoi(match[2])
|
||||||
|
alternative = match[3]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
token := match[1]
|
||||||
switch token {
|
switch token {
|
||||||
case "":
|
case "":
|
||||||
case "default":
|
case "default":
|
||||||
@@ -1186,6 +1244,13 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(alternative) > 0 {
|
||||||
|
alternativeOpts := *opts
|
||||||
|
opts.alternative = &alternativeOpts
|
||||||
|
opts.alternative.hidden = false
|
||||||
|
opts.alternative.alternative = nil
|
||||||
|
parsePreviewWindow(opts.alternative, alternative)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMargin(opt string, margin string) [4]sizeSpec {
|
func parseMargin(opt string, margin string) [4]sizeSpec {
|
||||||
@@ -1354,6 +1419,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Hscroll = false
|
opts.Hscroll = false
|
||||||
case "--hscroll-off":
|
case "--hscroll-off":
|
||||||
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
|
opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
|
||||||
|
case "--scroll-off":
|
||||||
|
opts.ScrollOff = nextInt(allArgs, &i, "scroll offset required")
|
||||||
case "--filepath-word":
|
case "--filepath-word":
|
||||||
opts.FileWord = true
|
opts.FileWord = true
|
||||||
case "--no-filepath-word":
|
case "--no-filepath-word":
|
||||||
@@ -1421,6 +1488,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--header-lines":
|
case "--header-lines":
|
||||||
opts.HeaderLines = atoi(
|
opts.HeaderLines = atoi(
|
||||||
nextString(allArgs, &i, "number of header lines required"))
|
nextString(allArgs, &i, "number of header lines required"))
|
||||||
|
case "--header-first":
|
||||||
|
opts.HeaderFirst = true
|
||||||
|
case "--no-header-first":
|
||||||
|
opts.HeaderFirst = false
|
||||||
|
case "--ellipsis":
|
||||||
|
opts.Ellipsis = nextString(allArgs, &i, "ellipsis string required")
|
||||||
case "--preview":
|
case "--preview":
|
||||||
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
||||||
case "--no-preview":
|
case "--no-preview":
|
||||||
@@ -1518,6 +1591,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Header = strLines(value)
|
opts.Header = strLines(value)
|
||||||
} else if match, value := optString(arg, "--header-lines="); match {
|
} else if match, value := optString(arg, "--header-lines="); match {
|
||||||
opts.HeaderLines = atoi(value)
|
opts.HeaderLines = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--ellipsis="); match {
|
||||||
|
opts.Ellipsis = value
|
||||||
} else if match, value := optString(arg, "--preview="); match {
|
} else if match, value := optString(arg, "--preview="); match {
|
||||||
opts.Preview.command = value
|
opts.Preview.command = value
|
||||||
} else if match, value := optString(arg, "--preview-window="); match {
|
} else if match, value := optString(arg, "--preview-window="); match {
|
||||||
@@ -1530,6 +1605,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--scroll-off="); match {
|
||||||
|
opts.ScrollOff = atoi(value)
|
||||||
} else if match, value := optString(arg, "--jump-labels="); match {
|
} else if match, value := optString(arg, "--jump-labels="); match {
|
||||||
opts.JumpLabels = value
|
opts.JumpLabels = value
|
||||||
validateJumpLabels = true
|
validateJumpLabels = true
|
||||||
@@ -1547,6 +1624,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
errorExit("hscroll offset must be a non-negative integer")
|
errorExit("hscroll offset must be a non-negative integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.ScrollOff < 0 {
|
||||||
|
errorExit("scroll offset must be a non-negative integer")
|
||||||
|
}
|
||||||
|
|
||||||
if opts.Tabstop < 1 {
|
if opts.Tabstop < 1 {
|
||||||
errorExit("tab stop must be a positive integer")
|
errorExit("tab stop must be a positive integer")
|
||||||
}
|
}
|
||||||
@@ -1580,11 +1661,6 @@ func validateSign(sign string, signOptName string) error {
|
|||||||
if sign == "" {
|
if sign == "" {
|
||||||
return fmt.Errorf("%v cannot be empty", signOptName)
|
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||||
}
|
}
|
||||||
for _, r := range sign {
|
|
||||||
if !unicode.IsGraphic(r) {
|
|
||||||
return fmt.Errorf("invalid character in %v", signOptName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if runewidth.StringWidth(sign) > 2 {
|
if runewidth.StringWidth(sign) > 2 {
|
||||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||||
}
|
}
|
||||||
@@ -1608,11 +1684,29 @@ func postProcessOptions(opts *Options) {
|
|||||||
// Extend the default key map
|
// Extend the default key map
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
for key, actions := range opts.Keymap {
|
for key, actions := range opts.Keymap {
|
||||||
|
var lastChangePreviewWindow *action
|
||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
if act.t == actToggleSort {
|
switch act.t {
|
||||||
|
case actToggleSort:
|
||||||
|
// To display "+S"/"-S" on info line
|
||||||
opts.ToggleSort = true
|
opts.ToggleSort = true
|
||||||
|
case actChangePreviewWindow:
|
||||||
|
lastChangePreviewWindow = act
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Re-organize actions so that we only keep the last change-preview-window
|
||||||
|
// and it comes first in the list.
|
||||||
|
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
|
||||||
|
// -> change-preview-window(up,+20)+preview(sleep 3; cat {})
|
||||||
|
if lastChangePreviewWindow != nil {
|
||||||
|
reordered := []*action{lastChangePreviewWindow}
|
||||||
|
for _, act := range actions {
|
||||||
|
if act.t != actChangePreviewWindow {
|
||||||
|
reordered = append(reordered, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions = reordered
|
||||||
|
}
|
||||||
keymap[key] = actions
|
keymap[key] = actions
|
||||||
}
|
}
|
||||||
opts.Keymap = keymap
|
opts.Keymap = keymap
|
||||||
@@ -1652,6 +1746,13 @@ func postProcessOptions(opts *Options) {
|
|||||||
func ParseOptions() *Options {
|
func ParseOptions() *Options {
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
|
|
||||||
|
for _, arg := range os.Args[1:] {
|
||||||
|
if arg == "--version" {
|
||||||
|
opts.Version = true
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Options from Env var
|
// Options from Env var
|
||||||
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
||||||
if len(words) > 0 {
|
if len(words) > 0 {
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ func TestDelimiterRegexRegex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDelimiterRegexRegexCaret(t *testing.T) {
|
||||||
|
delim := delimiterRegexp(`(^\s*|\s+)`)
|
||||||
|
tokens := Tokenize("foo bar baz", delim)
|
||||||
|
if delim.str != nil ||
|
||||||
|
len(tokens) != 4 ||
|
||||||
|
tokens[0].text.ToString() != "" ||
|
||||||
|
tokens[1].text.ToString() != "foo " ||
|
||||||
|
tokens[2].text.ToString() != "bar " ||
|
||||||
|
tokens[3].text.ToString() != "baz" {
|
||||||
|
t.Errorf("%s %d", tokens, len(tokens))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSplitNth(t *testing.T) {
|
func TestSplitNth(t *testing.T) {
|
||||||
{
|
{
|
||||||
ranges := splitNth("..")
|
ranges := splitNth("..")
|
||||||
@@ -440,8 +453,6 @@ func TestValidateSign(t *testing.T) {
|
|||||||
{"😀", true},
|
{"😀", true},
|
||||||
{"", false},
|
{"", false},
|
||||||
{">>>", false},
|
{">>>", false},
|
||||||
{"\n", false},
|
|
||||||
{"\t", false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build !openbsd
|
//go:build !openbsd
|
||||||
|
|
||||||
package protector
|
package protector
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build openbsd
|
//go:build openbsd
|
||||||
|
|
||||||
package protector
|
package protector
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build !386,!amd64
|
//go:build !386 && !amd64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// +build !tcell
|
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build 386 amd64
|
//go:build 386 || amd64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
|||||||
702
src/terminal.go
702
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -288,6 +288,7 @@ func TestUnixCommands(t *testing.T) {
|
|||||||
{give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}},
|
{give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}},
|
||||||
|
|
||||||
// 2) problematic examples
|
// 2) problematic examples
|
||||||
|
// (not necessarily unexpected)
|
||||||
|
|
||||||
// paths that need to expand some part of it won't work (special characters and variables)
|
// paths that need to expand some part of it won't work (special characters and variables)
|
||||||
{give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}},
|
{give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}},
|
||||||
@@ -315,6 +316,7 @@ func TestWindowsCommands(t *testing.T) {
|
|||||||
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}},
|
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}},
|
||||||
|
|
||||||
// 2) problematic examples
|
// 2) problematic examples
|
||||||
|
// (not necessarily unexpected)
|
||||||
|
|
||||||
// notepad++'s parser can't handle `-n"12"` generate by fzf, expects `-n12`
|
// notepad++'s parser can't handle `-n"12"` generate by fzf, expects `-n12`
|
||||||
{give{`notepad++ -n{1} {2}`, ``, newItems(`12 C:\Work\Test Folder\File.txt`)}, want{output: `notepad++ -n^"12^" ^"C:\\Work\\Test Folder\\File.txt^"`}},
|
{give{`notepad++ -n{1} {2}`, ``, newItems(`12 C:\Work\Test Folder\File.txt`)}, want{output: `notepad++ -n^"12^" ^"C:\\Work\\Test Folder\\File.txt^"`}},
|
||||||
@@ -327,11 +329,97 @@ func TestWindowsCommands(t *testing.T) {
|
|||||||
|
|
||||||
// the "file" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it
|
// the "file" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it
|
||||||
// the temp file contains: `cat "C:\test.txt"`
|
// the temp file contains: `cat "C:\test.txt"`
|
||||||
|
// TODO this should actually work
|
||||||
{give{`cmd /c {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^cmd /c .*\fzf-preview-[0-9]{9}$`}},
|
{give{`cmd /c {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^cmd /c .*\fzf-preview-[0-9]{9}$`}},
|
||||||
}
|
}
|
||||||
testCommands(t, tests)
|
testCommands(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows in Powershell
|
||||||
|
func TestPowershellCommands(t *testing.T) {
|
||||||
|
if !util.IsWindows() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
// reference: give{template, query, items}, want{output OR match}
|
||||||
|
|
||||||
|
/*
|
||||||
|
You can read each line in the following table as a pipeline that
|
||||||
|
consist of series of parsers that act upon your input (col. 1) and
|
||||||
|
each cell represents the output value.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- exec.Command("program.exe", `\''`)
|
||||||
|
- goes to win32 api which will process it transparently as it contains no special characters, see [CommandLineToArgvW][].
|
||||||
|
- powershell command will receive it as is, that is two arguments: a literal backslash and empty string in single quotes
|
||||||
|
- native command run via/from powershell will receive only one argument: a literal backslash. Because extra parsing rules apply, see [NativeCallsFromPowershell][].
|
||||||
|
- some¹ apps have internal parser, that requires one more level of escaping (yes, this is completely application-specific, but see terminal_test.go#TestWindowsCommands)
|
||||||
|
|
||||||
|
Character⁰ CommandLineToArgvW Powershell commands Native commands from Powershell Apps requiring escapes¹ | Being tested below
|
||||||
|
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
||||||
|
" empty string² missing argument error ... ... |
|
||||||
|
\" literal " unbalanced quote error ... ... |
|
||||||
|
'\"' literal '"' literal " empty string empty string (match all) | yes
|
||||||
|
'\\\"' literal '\"' literal \" literal " literal " |
|
||||||
|
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
||||||
|
\ transparent transparent transparent regex error |
|
||||||
|
'\' transparent literal \ literal \ regex error | yes
|
||||||
|
\\ transparent transparent transparent literal \ |
|
||||||
|
'\\' transparent literal \\ literal \\ literal \ |
|
||||||
|
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
||||||
|
' transparent unbalanced quote error ... ... |
|
||||||
|
\' transparent literal \ and unb. quote error ... ... |
|
||||||
|
\'' transparent literal \ and empty string literal \ regex error | no, but given as example above
|
||||||
|
''' transparent unbalanced quote error ... ... |
|
||||||
|
'''' transparent literal ' literal ' literal ' | yes
|
||||||
|
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
|
||||||
|
|
||||||
|
⁰: charatecter or characters 'x' as an argument to a program in go's call: exec.Command("program.exe", `x`)
|
||||||
|
¹: native commands like grep, git grep, ripgrep
|
||||||
|
²: interpreted as a grouping quote, affects argument parser and gets removed from the result
|
||||||
|
|
||||||
|
[CommandLineToArgvW]: https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
|
||||||
|
[NativeCallsFromPowershell]: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.1#passing-arguments-that-contain-quote-characters
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1) working examples
|
||||||
|
|
||||||
|
{give{`Get-Content {}`, ``, newItems(`C:\test.txt`)}, want{output: `Get-Content 'C:\test.txt'`}},
|
||||||
|
{give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" '.\test.go'`}},
|
||||||
|
|
||||||
|
// example of escaping single quotes
|
||||||
|
{give{`rg -- {}`, ``, newItems(`'foobar'`)}, want{output: `rg -- '''foobar'''`}},
|
||||||
|
|
||||||
|
// chaining powershells
|
||||||
|
{give{`powershell -NoProfile -Command {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `powershell -NoProfile -Command 'cat \"C:\test.txt\"'`}},
|
||||||
|
|
||||||
|
// 2) problematic examples
|
||||||
|
// (not necessarily unexpected)
|
||||||
|
|
||||||
|
// looking for a path string will only work with escaped backslashes
|
||||||
|
{give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- 'C:\test.txt'`}},
|
||||||
|
// looking for a literal double quote will only work with triple escaped double quotes
|
||||||
|
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- '\"C:\test.txt\"'`}},
|
||||||
|
|
||||||
|
// Get-Content (i.e. cat alias) is parsing `"` as a part of the file path, returns an error:
|
||||||
|
// Get-Content : Cannot find drive. A drive with the name '"C:' does not exist.
|
||||||
|
{give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat '\"C:\test.txt\"'`}},
|
||||||
|
|
||||||
|
// the "file" flag in the pattern won't create *.ps1 file so the powershell will offload this "unknown" filetype
|
||||||
|
// to explorer, which will prompt user to pick editing program for the fzf-preview file
|
||||||
|
// the temp file contains: `cat "C:\test.txt"`
|
||||||
|
// TODO this should actually work
|
||||||
|
{give{`powershell -NoProfile -Command {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^powershell -NoProfile -Command .*\fzf-preview-[0-9]{9}$`}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// to force powershell-style escaping we temporarily set environment variable that fzf honors
|
||||||
|
shellBackup := os.Getenv("SHELL")
|
||||||
|
os.Setenv("SHELL", "powershell")
|
||||||
|
testCommands(t, tests)
|
||||||
|
os.Setenv("SHELL", shellBackup)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Test typical valid placeholders and parsing of them.
|
Test typical valid placeholders and parsing of them.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
@@ -21,10 +21,25 @@ func notifyOnCont(resizeChan chan<- os.Signal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
func quoteEntry(entry string) string {
|
||||||
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
shell := os.Getenv("SHELL")
|
||||||
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
if len(shell) == 0 {
|
||||||
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
shell = "cmd"
|
||||||
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
}
|
||||||
return "^" + match
|
|
||||||
})
|
if strings.Contains(shell, "cmd") {
|
||||||
|
// backslash escaping is done here for applications
|
||||||
|
// (see ripgrep test case in terminal_test.go#TestWindowsCommands)
|
||||||
|
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
||||||
|
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
||||||
|
// caret is the escape character for cmd shell
|
||||||
|
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
||||||
|
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
||||||
|
return "^" + match
|
||||||
|
})
|
||||||
|
} else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
||||||
|
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
||||||
|
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
||||||
|
} else {
|
||||||
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,14 +156,14 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
// FIXME performance
|
// FIXME performance
|
||||||
var tokens []string
|
var tokens []string
|
||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
for len(text) > 0 {
|
locs := delimiter.regex.FindAllStringIndex(text, -1)
|
||||||
loc := delimiter.regex.FindStringIndex(text)
|
begin := 0
|
||||||
if len(loc) < 2 {
|
for _, loc := range locs {
|
||||||
loc = []int{0, len(text)}
|
tokens = append(tokens, text[begin:loc[1]])
|
||||||
}
|
begin = loc[1]
|
||||||
last := util.Max(loc[1], 1)
|
}
|
||||||
tokens = append(tokens, text[:last])
|
if begin < len(text) {
|
||||||
text = text[last:]
|
tokens = append(tokens, text[begin:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return withPrefixLengths(tokens, 0)
|
return withPrefixLengths(tokens, 0)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// +build !ncurses
|
//go:build !tcell && !windows
|
||||||
// +build !tcell
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
107
src/tui/light.go
107
src/tui/light.go
@@ -23,7 +23,7 @@ const (
|
|||||||
defaultEscDelay = 100
|
defaultEscDelay = 100
|
||||||
escPollInterval = 5
|
escPollInterval = 5
|
||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
maxInputBuffer = 10 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
@@ -60,7 +60,7 @@ func (r *LightRenderer) csi(code string) {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(os.Stderr, r.queued.String())
|
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +176,7 @@ func (r *LightRenderer) Init() {
|
|||||||
|
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000h")
|
r.csi("?1000h")
|
||||||
|
r.csi("?1006h")
|
||||||
}
|
}
|
||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
@@ -378,7 +379,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 'F':
|
case 'F':
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case 'M':
|
case '<':
|
||||||
return r.mouseSequence(sz)
|
return r.mouseSequence(sz)
|
||||||
case 'P':
|
case 'P':
|
||||||
return Event{F1, 0, nil}
|
return Event{F1, 0, nil}
|
||||||
@@ -519,47 +520,73 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||||
if len(r.buffer) < 6 || !r.mouse {
|
// "\e[<0;0;0M"
|
||||||
|
if len(r.buffer) < 9 || !r.mouse {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 6
|
|
||||||
switch r.buffer[3] {
|
|
||||||
case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
|
||||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
|
||||||
mod := r.buffer[3] >= 36
|
|
||||||
left := r.buffer[3] == 32
|
|
||||||
down := r.buffer[3]%2 == 0
|
|
||||||
x := int(r.buffer[4] - 33)
|
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
|
||||||
double := false
|
|
||||||
if down {
|
|
||||||
now := time.Now()
|
|
||||||
if !left { // Right double click is not allowed
|
|
||||||
r.clickY = []int{}
|
|
||||||
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
r.clickY = append(r.clickY, y)
|
|
||||||
} else {
|
|
||||||
r.clickY = []int{y}
|
|
||||||
}
|
|
||||||
r.prevDownTime = now
|
|
||||||
} else {
|
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
|
||||||
time.Since(r.prevDownTime) < doubleClickDuration {
|
|
||||||
double = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
rest := r.buffer[*sz:]
|
||||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
end := bytes.IndexAny(rest, "mM")
|
||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
if end == -1 {
|
||||||
mod := r.buffer[3] >= 100
|
return Event{Invalid, 0, nil}
|
||||||
s := 1 - int(r.buffer[3]%2)*2
|
|
||||||
x := int(r.buffer[4] - 33)
|
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
|
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
|
elems := strings.SplitN(string(rest[:end]), ";", 3)
|
||||||
|
if len(elems) != 3 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := atoi(elems[0], -1)
|
||||||
|
x := atoi(elems[1], -1) - 1
|
||||||
|
y := atoi(elems[2], -1) - 1
|
||||||
|
if t < 0 || x < 0 || y < 0 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz += end + 1
|
||||||
|
|
||||||
|
down := rest[end] == 'M'
|
||||||
|
|
||||||
|
scroll := 0
|
||||||
|
if t >= 64 {
|
||||||
|
t -= 64
|
||||||
|
if t&0b1 == 1 {
|
||||||
|
scroll = -1
|
||||||
|
} else {
|
||||||
|
scroll = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// middle := t & 0b1
|
||||||
|
left := t&0b11 == 0
|
||||||
|
|
||||||
|
// shift := t & 0b100
|
||||||
|
// ctrl := t & 0b1000
|
||||||
|
mod := t&0b1100 > 0
|
||||||
|
|
||||||
|
if scroll != 0 {
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, mod}}
|
||||||
|
}
|
||||||
|
|
||||||
|
double := false
|
||||||
|
if down {
|
||||||
|
now := time.Now()
|
||||||
|
if !left { // Right double click is not allowed
|
||||||
|
r.clickY = []int{}
|
||||||
|
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
|
r.clickY = append(r.clickY, y)
|
||||||
|
} else {
|
||||||
|
r.clickY = []int{y}
|
||||||
|
}
|
||||||
|
r.prevDownTime = now
|
||||||
|
} else {
|
||||||
|
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||||
|
time.Since(r.prevDownTime) < doubleClickDuration {
|
||||||
|
double = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) smcup() {
|
func (r *LightRenderer) smcup() {
|
||||||
@@ -597,6 +624,7 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
// It's highly likely that the offset we obtained at the beginning is
|
// It's highly likely that the offset we obtained at the beginning is
|
||||||
// no longer correct, so we simply disable mouse input.
|
// no longer correct, so we simply disable mouse input.
|
||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
|
r.csi("?1006l")
|
||||||
r.mouse = false
|
r.mouse = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -636,6 +664,7 @@ func (r *LightRenderer) Close() {
|
|||||||
}
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
|
r.csi("?1006l")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//+build windows
|
//go:build windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build tcell windows
|
//go:build tcell || windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build tcell windows
|
//go:build tcell || windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
||||||
}
|
}
|
||||||
width += w
|
width += w
|
||||||
if limit > 0 && width > limit {
|
if width > limit {
|
||||||
return width, idx
|
return width, idx
|
||||||
}
|
}
|
||||||
idx += len(rs)
|
idx += len(rs)
|
||||||
@@ -34,6 +34,23 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
return width, -1
|
return width, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate returns the truncated runes and its width
|
||||||
|
func Truncate(input string, limit int) ([]rune, int) {
|
||||||
|
runes := []rune{}
|
||||||
|
width := 0
|
||||||
|
gr := uniseg.NewGraphemes(input)
|
||||||
|
for gr.Next() {
|
||||||
|
rs := gr.Runes()
|
||||||
|
w := runewidth.StringWidth(string(rs))
|
||||||
|
if width+w > limit {
|
||||||
|
return runes, width
|
||||||
|
}
|
||||||
|
width += w
|
||||||
|
runes = append(runes, rs...)
|
||||||
|
}
|
||||||
|
return runes, width
|
||||||
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
func Max(first int, second int) int {
|
func Max(first int, second int) int {
|
||||||
if first >= second {
|
if first >= second {
|
||||||
|
|||||||
@@ -38,3 +38,29 @@ func TestOnce(t *testing.T) {
|
|||||||
t.Error("Expected: false")
|
t.Error("Expected: false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunesWidth(t *testing.T) {
|
||||||
|
for _, args := range [][]int{
|
||||||
|
{100, 5, -1},
|
||||||
|
{3, 4, 3},
|
||||||
|
{0, 1, 0},
|
||||||
|
} {
|
||||||
|
width, overflowIdx := RunesWidth([]rune("hello"), 0, 0, args[0])
|
||||||
|
if width != args[1] {
|
||||||
|
t.Errorf("Expected width: %d, actual: %d", args[1], width)
|
||||||
|
}
|
||||||
|
if overflowIdx != args[2] {
|
||||||
|
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
truncated, width := Truncate("가나다라마", 7)
|
||||||
|
if string(truncated) != "가나다" {
|
||||||
|
t.Errorf("Expected: 가나다, actual: %s", string(truncated))
|
||||||
|
}
|
||||||
|
if width != 6 {
|
||||||
|
t.Errorf("Expected: 6, actual: %d", width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
@@ -6,23 +6,57 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with cmd
|
var shellPath atomic.Value
|
||||||
|
|
||||||
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
return ExecCommandWith("cmd", command, setpgid)
|
var shell string
|
||||||
|
if cached := shellPath.Load(); cached != nil {
|
||||||
|
shell = cached.(string)
|
||||||
|
} else {
|
||||||
|
shell = os.Getenv("SHELL")
|
||||||
|
if len(shell) == 0 {
|
||||||
|
shell = "cmd"
|
||||||
|
} else if strings.Contains(shell, "/") {
|
||||||
|
out, err := exec.Command("cygpath", "-w", shell).Output()
|
||||||
|
if err == nil {
|
||||||
|
shell = strings.Trim(string(out), "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shellPath.Store(shell)
|
||||||
|
}
|
||||||
|
return ExecCommandWith(shell, command, setpgid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with cmd. _shell parameter is
|
// ExecCommandWith executes the given command with the specified shell
|
||||||
// ignored on Windows.
|
|
||||||
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
||||||
// can kill preview process with its child processes at once.
|
// can kill preview process with its child processes at once.
|
||||||
func ExecCommandWith(_shell string, command string, setpgid bool) *exec.Cmd {
|
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
|
||||||
cmd := exec.Command("cmd")
|
// but it is left as is now because no adverse effect has been observed.
|
||||||
|
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if strings.Contains(shell, "cmd") {
|
||||||
|
cmd = exec.Command(shell)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: false,
|
||||||
|
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
||||||
|
CreationFlags: 0,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
||||||
|
cmd = exec.Command(shell, "-NoProfile", "-Command", command)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command(shell, "-c", command)
|
||||||
|
}
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
HideWindow: false,
|
HideWindow: false,
|
||||||
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
|
||||||
CreationFlags: 0,
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
162
test/test_go.rb
162
test/test_go.rb
@@ -2043,8 +2043,8 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unbind
|
def test_unbind_rebind
|
||||||
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d)'", :Enter
|
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d),e:rebind(c,d)'", :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
tmux.send_keys 'ab'
|
tmux.send_keys 'ab'
|
||||||
tmux.until { |lines| assert_equal '> ab', lines[-1] }
|
tmux.until { |lines| assert_equal '> ab', lines[-1] }
|
||||||
@@ -2052,6 +2052,8 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
tmux.send_keys 'dabcd'
|
tmux.send_keys 'dabcd'
|
||||||
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
||||||
|
tmux.send_keys 'ecabddc'
|
||||||
|
tmux.until { |lines| assert_equal '> abdc', lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_item_index_reset_on_reload
|
def test_item_index_reset_on_reload
|
||||||
@@ -2069,6 +2071,160 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| assert_includes lines[1], '[[99]]' }
|
tmux.until { |lines| assert_includes lines[1], '[[99]]' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_should_update_preview
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload:echo 4' --preview 'echo {}' --preview-window 'nohidden'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '1' }
|
||||||
|
tmux.send_keys 'C-t'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_and_change_preview_should_update_preview
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload(echo 4)+change-preview(echo {})'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||||
|
tmux.send_keys 'C-t'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_scroll_off
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
|
height = tmux.until { |lines| lines }.first.to_i
|
||||||
|
tmux.send_keys :PgUp
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal height + 3, lines.first.to_i
|
||||||
|
assert_equal "> #{height}", lines[3].strip
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal "> #{height + 1}", lines[3].strip }
|
||||||
|
tmux.send_keys 'l'
|
||||||
|
tmux.until { |lines| assert_equal '> 1000', lines.first.strip }
|
||||||
|
tmux.send_keys :PgDn
|
||||||
|
tmux.until { |lines| assert_equal "> #{1000 - height + 1}", lines.reverse[5].strip }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_equal "> #{1000 - height}", lines.reverse[5].strip }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_scroll_off_large
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=9999", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
|
height = tmux.until { |lines| lines }.first.to_i
|
||||||
|
tmux.send_keys :PgUp
|
||||||
|
tmux.until { |lines| assert_equal "> #{height}", lines[height / 2].strip }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal "> #{height + 1}", lines[height / 2].strip }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal "> #{height + 2}", lines[height / 2].strip }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_equal "> #{height + 1}", lines[height / 2].strip }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_first
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 4
|
||||||
|
997/997
|
||||||
|
>
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
foobar
|
||||||
|
OUTPUT
|
||||||
|
|
||||||
|
assert_equal expected.chomp, lines.reverse.take(7).reverse.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_first_reverse
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
foobar
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
> < 997/997
|
||||||
|
> 4
|
||||||
|
OUTPUT
|
||||||
|
|
||||||
|
assert_equal expected.chomp, lines.take(6).join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \
|
||||||
|
'a:change-preview(echo __{}__),' \
|
||||||
|
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
||||||
|
'c:change-preview(),d:change-preview-window(hidden),' \
|
||||||
|
"e:preview(printf ::%${FZF_PREVIEW_COLUMNS}s{})+change-preview-window(up),f:change-preview-window(up,wrap)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '[[1]]' }
|
||||||
|
|
||||||
|
# change-preview action permanently changes the preview command set by --preview
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '__1__' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[0], '__2__' }
|
||||||
|
|
||||||
|
# When multiple change-preview-window actions are bound to a single key,
|
||||||
|
# the last one wins and the updated options are immediately applied to the new preview
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_equal '==2==', lines[0] }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal '==3==', lines[0] }
|
||||||
|
|
||||||
|
# change-preview with an empty preview command closes the preview window
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], '==' }
|
||||||
|
|
||||||
|
# change-preview again to re-open the preview window
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_equal '__3__', lines[0] }
|
||||||
|
|
||||||
|
# Hide the preview window with hidden flag
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], '__3__' }
|
||||||
|
|
||||||
|
# One-off preview
|
||||||
|
tmux.send_keys 'e'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal '::', lines[0]
|
||||||
|
refute_includes lines[1], '3'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wrapped
|
||||||
|
tmux.send_keys 'f'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal '::', lines[0]
|
||||||
|
assert_equal ' 3', lines[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window_rotate
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||||
|
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||||
|
3.times do
|
||||||
|
tmux.until { |lines| lines[0].start_with?('hello') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines[0].end_with?('hello') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines[-1].start_with?('hello') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_equal 'hello', lines[0] }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| refute_includes lines[0], 'hello' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ellipsis
|
||||||
|
tmux.send_keys 'seq 1000 | tr "\n" , | fzf --ellipsis=SNIPSNIP -e -q500', :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -2322,7 +2478,7 @@ module CompletionTest
|
|||||||
pid = lines[-1]&.split&.last
|
pid = lines[-1]&.split&.last
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
tmux.send_keys 'kill ', :Tab
|
tmux.send_keys 'kill **', :Tab
|
||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
tmux.send_keys 'sleep12345'
|
tmux.send_keys 'sleep12345'
|
||||||
tmux.until { |lines| assert lines.any_include?('sleep 12345') }
|
tmux.until { |lines| assert lines.any_include?('sleep 12345') }
|
||||||
|
|||||||
14
uninstall
14
uninstall
@@ -51,13 +51,8 @@ remove() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_line() {
|
remove_line() {
|
||||||
src=$(readlink "$1")
|
src=$1
|
||||||
if [ $? -eq 0 ]; then
|
echo "Remove from $1:"
|
||||||
echo "Remove from $1 ($src):"
|
|
||||||
else
|
|
||||||
src=$1
|
|
||||||
echo "Remove from $1:"
|
|
||||||
fi
|
|
||||||
|
|
||||||
shift
|
shift
|
||||||
line_no=1
|
line_no=1
|
||||||
@@ -75,8 +70,9 @@ remove_line() {
|
|||||||
echo " - Line #$line_no: $content"
|
echo " - Line #$line_no: $content"
|
||||||
[ "$content" = "$1" ] || ask " - Remove?"
|
[ "$content" = "$1" ] || ask " - Remove?"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
temp=$(mktemp)
|
||||||
mv "$src.bak" "$src" || break
|
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$temp" &&
|
||||||
|
cat "$temp" > "$src" && rm -f "$temp" || break
|
||||||
echo " - Removed"
|
echo " - Removed"
|
||||||
else
|
else
|
||||||
echo " - Skipped"
|
echo " - Skipped"
|
||||||
|
|||||||
Reference in New Issue
Block a user